1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
10 #include "base/bind_helpers.h"
11 #include "base/command_line.h"
12 #include "base/logging.h"
13 #include "base/nix/mime_util_xdg.h"
14 #include "base/nix/xdg_util.h"
15 #include "base/process/launch.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/string_split.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/threading/thread_restrictions.h"
21 #include "chrome/browser/ui/libgtk2ui/select_file_dialog_impl.h"
22 #include "chrome/grit/generated_resources.h"
23 #include "content/public/browser/browser_thread.h"
24 #include "ui/aura/window_tree_host.h"
25 #include "ui/base/l10n/l10n_util.h"
26 #include "ui/strings/grit/ui_strings.h"
28 // These conflict with base/tracked_objects.h, so need to come last.
32 using content::BrowserThread
;
36 std::string
GetTitle(const std::string
& title
, int message_id
) {
37 return title
.empty() ? l10n_util::GetStringUTF8(message_id
) : title
;
40 const char kKdialogBinary
[] = "kdialog";
46 // Implementation of SelectFileDialog that shows a KDE common dialog for
47 // choosing a file or folder. This acts as a modal dialog.
48 class SelectFileDialogImplKDE
: public SelectFileDialogImpl
{
50 SelectFileDialogImplKDE(Listener
* listener
,
51 ui::SelectFilePolicy
* policy
,
52 base::nix::DesktopEnvironment desktop
);
55 ~SelectFileDialogImplKDE() override
;
57 // BaseShellDialog implementation:
58 bool IsRunning(gfx::NativeWindow parent_window
) const override
;
60 // SelectFileDialog implementation.
61 // |params| is user data we pass back via the Listener interface.
62 void SelectFileImpl(Type type
,
63 const base::string16
& title
,
64 const base::FilePath
& default_path
,
65 const FileTypeInfo
* file_types
,
67 const base::FilePath::StringType
& default_extension
,
68 gfx::NativeWindow owning_window
,
69 void* params
) override
;
72 bool HasMultipleFileTypeChoicesImpl() override
;
74 struct KDialogParams
{
75 KDialogParams(const std::string
& type
, const std::string
& title
,
76 const base::FilePath
& default_path
, XID parent
,
77 bool file_operation
, bool multiple_selection
,
79 void (SelectFileDialogImplKDE::*callback
)(XID
,
82 : type(type
), title(title
), default_path(default_path
), parent(parent
),
83 file_operation(file_operation
),
84 multiple_selection(multiple_selection
),
85 kdialog_params(kdialog_params
), callback(callback
) {
90 base::FilePath default_path
;
93 bool multiple_selection
;
95 void (SelectFileDialogImplKDE::*callback
)(XID
, const std::string
&,
99 // Get the filters from |file_types_| and concatenate them into
101 std::string
GetMimeTypeFilterString();
103 // Get KDialog command line representing the Argv array for KDialog.
104 void GetKDialogCommandLine(const std::string
& type
,
105 const std::string
& title
,
106 const base::FilePath
& default_path
,
109 bool multiple_selection
,
110 base::CommandLine
* command_line
);
112 // Call KDialog on the FILE thread and post results back to the UI thread.
113 void CallKDialogOutput(const KDialogParams
& params
);
115 // Notifies the listener that a single file was chosen.
116 void FileSelected(const base::FilePath
& path
, void* params
);
118 // Notifies the listener that multiple files were chosen.
119 void MultiFilesSelected(const std::vector
<base::FilePath
>& files
,
122 // Notifies the listener that no file was chosen (the action was canceled).
123 // Dialog is passed so we can find that |params| pointer that was passed to
124 // us when we were told to show the dialog.
125 void FileNotSelected(void *params
);
127 void CreateSelectFolderDialog(Type type
,
128 const std::string
& title
,
129 const base::FilePath
& default_path
,
130 XID parent
, void* params
);
132 void CreateFileOpenDialog(const std::string
& title
,
133 const base::FilePath
& default_path
,
134 XID parent
, void* params
);
136 void CreateMultiFileOpenDialog(const std::string
& title
,
137 const base::FilePath
& default_path
,
138 XID parent
, void* params
);
140 void CreateSaveAsDialog(const std::string
& title
,
141 const base::FilePath
& default_path
,
142 XID parent
, void* params
);
144 // Common function for OnSelectSingleFileDialogResponse and
145 // OnSelectSingleFolderDialogResponse.
146 void SelectSingleFileHelper(const std::string
& output
, int exit_code
,
147 void* params
, bool allow_folder
);
149 void OnSelectSingleFileDialogResponse(XID parent
,
150 const std::string
& output
,
151 int exit_code
, void* params
);
152 void OnSelectMultiFileDialogResponse(XID parent
,
153 const std::string
& output
,
154 int exit_code
, void* params
);
155 void OnSelectSingleFolderDialogResponse(XID parent
,
156 const std::string
& output
,
157 int exit_code
, void* params
);
159 // Should be either DESKTOP_ENVIRONMENT_KDE3 or DESKTOP_ENVIRONMENT_KDE4.
160 base::nix::DesktopEnvironment desktop_
;
162 // The set of all parent windows for which we are currently running
163 // dialogs. This should only be accessed on the UI thread.
164 std::set
<XID
> parents_
;
166 DISALLOW_COPY_AND_ASSIGN(SelectFileDialogImplKDE
);
170 bool SelectFileDialogImpl::CheckKDEDialogWorksOnUIThread() {
171 // No choice. UI thread can't continue without an answer here. Fortunately we
172 // only do this once, the first time a file dialog is displayed.
173 base::ThreadRestrictions::ScopedAllowIO allow_io
;
175 base::CommandLine::StringVector cmd_vector
;
176 cmd_vector
.push_back(kKdialogBinary
);
177 cmd_vector
.push_back("--version");
178 base::CommandLine
command_line(cmd_vector
);
180 return base::GetAppOutput(command_line
, &dummy
);
184 SelectFileDialogImpl
* SelectFileDialogImpl::NewSelectFileDialogImplKDE(
186 ui::SelectFilePolicy
* policy
,
187 base::nix::DesktopEnvironment desktop
) {
188 return new SelectFileDialogImplKDE(listener
, policy
, desktop
);
191 SelectFileDialogImplKDE::SelectFileDialogImplKDE(
193 ui::SelectFilePolicy
* policy
,
194 base::nix::DesktopEnvironment desktop
)
195 : SelectFileDialogImpl(listener
, policy
),
197 DCHECK(desktop_
== base::nix::DESKTOP_ENVIRONMENT_KDE3
||
198 desktop_
== base::nix::DESKTOP_ENVIRONMENT_KDE4
);
201 SelectFileDialogImplKDE::~SelectFileDialogImplKDE() {
204 bool SelectFileDialogImplKDE::IsRunning(gfx::NativeWindow parent_window
) const {
205 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
206 if (parent_window
&& parent_window
->GetHost()) {
207 XID xid
= parent_window
->GetHost()->GetAcceleratedWidget();
208 return parents_
.find(xid
) != parents_
.end();
214 // We ignore |default_extension|.
215 void SelectFileDialogImplKDE::SelectFileImpl(
217 const base::string16
& title
,
218 const base::FilePath
& default_path
,
219 const FileTypeInfo
* file_types
,
221 const base::FilePath::StringType
& default_extension
,
222 gfx::NativeWindow owning_window
,
224 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
227 XID window_xid
= None
;
228 if (owning_window
&& owning_window
->GetHost()) {
229 // |owning_window| can be null when user right-clicks on a downloadable item
230 // and chooses 'Open Link in New Tab' when 'Ask where to save each file
231 // before downloading.' preference is turned on. (http://crbug.com/29213)
232 window_xid
= owning_window
->GetHost()->GetAcceleratedWidget();
233 parents_
.insert(window_xid
);
236 std::string title_string
= base::UTF16ToUTF8(title
);
238 file_type_index_
= file_type_index
;
240 file_types_
= *file_types
;
242 file_types_
.include_all_files
= true;
246 case SELECT_UPLOAD_FOLDER
:
247 CreateSelectFolderDialog(type
, title_string
, default_path
,
250 case SELECT_OPEN_FILE
:
251 CreateFileOpenDialog(title_string
, default_path
, window_xid
, params
);
253 case SELECT_OPEN_MULTI_FILE
:
254 CreateMultiFileOpenDialog(title_string
, default_path
, window_xid
, params
);
256 case SELECT_SAVEAS_FILE
:
257 CreateSaveAsDialog(title_string
, default_path
, window_xid
, params
);
265 bool SelectFileDialogImplKDE::HasMultipleFileTypeChoicesImpl() {
266 return file_types_
.extensions
.size() > 1;
269 std::string
SelectFileDialogImplKDE::GetMimeTypeFilterString() {
270 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
271 std::string filter_string
;
272 // We need a filter set because the same mime type can appear multiple times.
273 std::set
<std::string
> filter_set
;
274 for (size_t i
= 0; i
< file_types_
.extensions
.size(); ++i
) {
275 for (size_t j
= 0; j
< file_types_
.extensions
[i
].size(); ++j
) {
276 if (!file_types_
.extensions
[i
][j
].empty()) {
277 std::string mime_type
= base::nix::GetFileMimeType(base::FilePath(
278 "name").ReplaceExtension(file_types_
.extensions
[i
][j
]));
279 filter_set
.insert(mime_type
);
283 // Add the *.* filter, but only if we have added other filters (otherwise it
285 if (file_types_
.include_all_files
&& !file_types_
.extensions
.empty())
286 filter_set
.insert("application/octet-stream");
287 // Create the final output string.
288 filter_string
.clear();
289 for (std::set
<std::string
>::iterator it
= filter_set
.begin();
290 it
!= filter_set
.end(); ++it
) {
291 filter_string
.append(*it
+ " ");
293 return filter_string
;
296 void SelectFileDialogImplKDE::CallKDialogOutput(const KDialogParams
& params
) {
297 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
298 base::CommandLine::StringVector cmd_vector
;
299 cmd_vector
.push_back(kKdialogBinary
);
300 base::CommandLine
command_line(cmd_vector
);
301 GetKDialogCommandLine(params
.type
, params
.title
, params
.default_path
,
302 params
.parent
, params
.file_operation
,
303 params
.multiple_selection
, &command_line
);
306 // Get output from KDialog
307 base::GetAppOutputWithExitCode(command_line
, &output
, &exit_code
);
309 output
.erase(output
.size() - 1);
311 // Now the dialog is no longer showing, but we can't erase its parent from the
312 // parent set yet because we're on the FILE thread.
313 BrowserThread::PostTask(
314 BrowserThread::UI
, FROM_HERE
,
315 base::Bind(params
.callback
, this, params
.parent
, output
, exit_code
,
316 params
.kdialog_params
));
319 void SelectFileDialogImplKDE::GetKDialogCommandLine(
320 const std::string
& type
,
321 const std::string
& title
,
322 const base::FilePath
& path
,
325 bool multiple_selection
,
326 base::CommandLine
* command_line
) {
329 // Attach to the current Chrome window.
330 if (parent
!= None
) {
331 command_line
->AppendSwitchNative(
332 desktop_
== base::nix::DESKTOP_ENVIRONMENT_KDE3
?
333 "--embed" : "--attach",
334 base::Uint64ToString(parent
));
337 // Set the correct title for the dialog.
339 command_line
->AppendSwitchNative("--title", title
);
340 // Enable multiple file selection if we need to.
341 if (multiple_selection
) {
342 command_line
->AppendSwitch("--multiple");
343 command_line
->AppendSwitch("--separate-output");
345 command_line
->AppendSwitch(type
);
346 // The path should never be empty. If it is, set it to PWD.
348 command_line
->AppendArgPath(base::FilePath("."));
350 command_line
->AppendArgPath(path
);
351 // Depending on the type of the operation we need, get the path to the
352 // file/folder and set up mime type filters.
354 command_line
->AppendArg(GetMimeTypeFilterString());
355 VLOG(1) << "KDialog command line: " << command_line
->GetCommandLineString();
358 void SelectFileDialogImplKDE::FileSelected(const base::FilePath
& path
,
360 if (type_
== SELECT_SAVEAS_FILE
)
361 *last_saved_path_
= path
.DirName();
362 else if (type_
== SELECT_OPEN_FILE
)
363 *last_opened_path_
= path
.DirName();
364 else if (type_
== SELECT_FOLDER
|| type_
== SELECT_UPLOAD_FOLDER
)
365 *last_opened_path_
= path
;
368 if (listener_
) { // What does the filter index actually do?
369 // TODO(dfilimon): Get a reasonable index value from somewhere.
370 listener_
->FileSelected(path
, 1, params
);
374 void SelectFileDialogImplKDE::MultiFilesSelected(
375 const std::vector
<base::FilePath
>& files
, void* params
) {
376 *last_opened_path_
= files
[0].DirName();
378 listener_
->MultiFilesSelected(files
, params
);
381 void SelectFileDialogImplKDE::FileNotSelected(void* params
) {
383 listener_
->FileSelectionCanceled(params
);
386 void SelectFileDialogImplKDE::CreateSelectFolderDialog(
387 Type type
, const std::string
& title
, const base::FilePath
& default_path
,
388 XID parent
, void *params
) {
389 int title_message_id
= (type
== SELECT_UPLOAD_FOLDER
)
390 ? IDS_SELECT_UPLOAD_FOLDER_DIALOG_TITLE
391 : IDS_SELECT_FOLDER_DIALOG_TITLE
;
392 BrowserThread::PostTask(
393 BrowserThread::FILE, FROM_HERE
,
395 &SelectFileDialogImplKDE::CallKDialogOutput
,
398 "--getexistingdirectory",
399 GetTitle(title
, title_message_id
),
400 default_path
.empty() ? *last_opened_path_
: default_path
,
401 parent
, false, false, params
,
402 &SelectFileDialogImplKDE::OnSelectSingleFolderDialogResponse
)));
405 void SelectFileDialogImplKDE::CreateFileOpenDialog(
406 const std::string
& title
, const base::FilePath
& default_path
,
407 XID parent
, void* params
) {
408 BrowserThread::PostTask(
409 BrowserThread::FILE, FROM_HERE
,
411 &SelectFileDialogImplKDE::CallKDialogOutput
,
415 GetTitle(title
, IDS_OPEN_FILE_DIALOG_TITLE
),
416 default_path
.empty() ? *last_opened_path_
: default_path
,
417 parent
, true, false, params
,
418 &SelectFileDialogImplKDE::OnSelectSingleFileDialogResponse
)));
421 void SelectFileDialogImplKDE::CreateMultiFileOpenDialog(
422 const std::string
& title
, const base::FilePath
& default_path
,
423 XID parent
, void* params
) {
424 BrowserThread::PostTask(
425 BrowserThread::FILE, FROM_HERE
,
427 &SelectFileDialogImplKDE::CallKDialogOutput
,
431 GetTitle(title
, IDS_OPEN_FILES_DIALOG_TITLE
),
432 default_path
.empty() ? *last_opened_path_
: default_path
,
433 parent
, true, true, params
,
434 &SelectFileDialogImplKDE::OnSelectMultiFileDialogResponse
)));
437 void SelectFileDialogImplKDE::CreateSaveAsDialog(
438 const std::string
& title
, const base::FilePath
& default_path
,
439 XID parent
, void* params
) {
440 BrowserThread::PostTask(
441 BrowserThread::FILE, FROM_HERE
,
443 &SelectFileDialogImplKDE::CallKDialogOutput
,
447 GetTitle(title
, IDS_SAVE_AS_DIALOG_TITLE
),
448 default_path
.empty() ? *last_saved_path_
: default_path
,
449 parent
, true, false, params
,
450 &SelectFileDialogImplKDE::OnSelectSingleFileDialogResponse
)));
453 void SelectFileDialogImplKDE::SelectSingleFileHelper(const std::string
& output
,
454 int exit_code
, void* params
, bool allow_folder
) {
455 VLOG(1) << "[kdialog] SingleFileResponse: " << output
;
456 if (exit_code
!= 0 || output
.empty()) {
457 FileNotSelected(params
);
461 base::FilePath
path(output
);
463 FileSelected(path
, params
);
467 if (CallDirectoryExistsOnUIThread(path
))
468 FileNotSelected(params
);
470 FileSelected(path
, params
);
473 void SelectFileDialogImplKDE::OnSelectSingleFileDialogResponse(
474 XID parent
, const std::string
& output
, int exit_code
, void* params
) {
475 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
476 parents_
.erase(parent
);
477 SelectSingleFileHelper(output
, exit_code
, params
, false);
480 void SelectFileDialogImplKDE::OnSelectSingleFolderDialogResponse(
481 XID parent
, const std::string
& output
, int exit_code
, void* params
) {
482 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
483 parents_
.erase(parent
);
484 SelectSingleFileHelper(output
, exit_code
, params
, true);
487 void SelectFileDialogImplKDE::OnSelectMultiFileDialogResponse(
488 XID parent
, const std::string
& output
, int exit_code
, void* params
) {
489 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
490 VLOG(1) << "[kdialog] MultiFileResponse: " << output
;
492 parents_
.erase(parent
);
494 if (exit_code
!= 0 || output
.empty()) {
495 FileNotSelected(params
);
499 std::vector
<base::FilePath
> filenames_fp
;
500 for (const base::StringPiece
& line
:
501 base::SplitStringPiece(output
, "\n", base::KEEP_WHITESPACE
,
502 base::SPLIT_WANT_NONEMPTY
)) {
503 base::FilePath
path(line
);
504 if (CallDirectoryExistsOnUIThread(path
))
506 filenames_fp
.push_back(path
);
509 if (filenames_fp
.empty()) {
510 FileNotSelected(params
);
513 MultiFilesSelected(filenames_fp
, params
);
516 } // namespace libgtk2ui