1 // Copyright (c) 2013 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.
8 #include "base/bind_helpers.h"
9 #include "base/command_line.h"
10 #include "base/logging.h"
11 #include "base/message_loop.h"
12 #include "base/message_loop/message_loop_proxy.h"
13 #include "base/nix/mime_util_xdg.h"
14 #include "base/nix/xdg_util.h"
15 #include "base/process_util.h"
16 #include "base/string_number_conversions.h"
17 #include "base/string_util.h"
18 #include "base/threading/thread_restrictions.h"
19 #include "base/threading/worker_pool.h"
20 #include "base/utf_string_conversions.h"
21 #include "grit/ui_strings.h"
22 #include "ui/base/l10n/l10n_util.h"
23 #include "ui/shell_dialogs/gtk/select_file_dialog_impl.h"
25 // These conflict with base/tracked_objects.h, so need to come last.
31 std::string
GetTitle(const std::string
& title
, int message_id
) {
32 return title
.empty() ? l10n_util::GetStringUTF8(message_id
) : title
;
35 const char kKdialogBinary
[] = "kdialog";
37 // Implementation of SelectFileDialog that shows a KDE common dialog for
38 // choosing a file or folder. This acts as a modal dialog.
39 class SelectFileDialogImplKDE
: public ui::SelectFileDialogImpl
{
41 SelectFileDialogImplKDE(Listener
* listener
,
42 ui::SelectFilePolicy
* policy
,
43 base::nix::DesktopEnvironment desktop
);
46 virtual ~SelectFileDialogImplKDE();
48 // SelectFileDialog implementation.
49 // |params| is user data we pass back via the Listener interface.
50 virtual void SelectFileImpl(
52 const string16
& title
,
53 const base::FilePath
& default_path
,
54 const FileTypeInfo
* file_types
,
56 const base::FilePath::StringType
& default_extension
,
57 gfx::NativeWindow owning_window
,
58 void* params
) OVERRIDE
;
61 virtual bool HasMultipleFileTypeChoicesImpl() OVERRIDE
;
63 struct KDialogParams
{
64 // This constructor can only be run from the UI thread.
65 KDialogParams(const std::string
& type
, const std::string
& title
,
66 const base::FilePath
& default_path
, gfx::NativeWindow parent
,
67 bool file_operation
, bool multiple_selection
,
69 void (SelectFileDialogImplKDE::*callback
)(const std::string
&,
71 : type(type
), title(title
), default_path(default_path
), parent(parent
),
72 file_operation(file_operation
),
73 multiple_selection(multiple_selection
),
74 kdialog_params(kdialog_params
),
75 ui_loop_proxy(MessageLoopForUI::current()->message_loop_proxy()),
81 base::FilePath default_path
;
82 gfx::NativeWindow parent
;
84 bool multiple_selection
;
86 scoped_refptr
<base::MessageLoopProxy
> ui_loop_proxy
;
88 void (SelectFileDialogImplKDE::*callback
)(const std::string
&, int, void*);
91 // Get the filters from |file_types_| and concatenate them into
93 std::string
GetMimeTypeFilterString();
95 // Get KDialog command line representing the Argv array for KDialog.
96 void GetKDialogCommandLine(const std::string
& type
, const std::string
& title
,
97 const base::FilePath
& default_path
, gfx::NativeWindow parent
,
98 bool file_operation
, bool multiple_selection
, CommandLine
* command_line
);
100 // Call KDialog on a worker thread and post results back to the caller
102 void CallKDialogOutput(const KDialogParams
& params
);
104 // Notifies the listener that a single file was chosen.
105 void FileSelected(const base::FilePath
& path
, void* params
);
107 // Notifies the listener that multiple files were chosen.
108 void MultiFilesSelected(const std::vector
<base::FilePath
>& files
,
111 // Notifies the listener that no file was chosen (the action was canceled).
112 // Dialog is passed so we can find that |params| pointer that was passed to
113 // us when we were told to show the dialog.
114 void FileNotSelected(void *params
);
116 void CreateSelectFolderDialog(const std::string
& title
,
117 const base::FilePath
& default_path
,
118 gfx::NativeWindow parent
, void* params
);
120 void CreateFileOpenDialog(const std::string
& title
,
121 const base::FilePath
& default_path
,
122 gfx::NativeWindow parent
, void* params
);
124 void CreateMultiFileOpenDialog(const std::string
& title
,
125 const base::FilePath
& default_path
,
126 gfx::NativeWindow parent
, void* params
);
128 void CreateSaveAsDialog(const std::string
& title
,
129 const base::FilePath
& default_path
,
130 gfx::NativeWindow parent
, void* params
);
132 // Common function for OnSelectSingleFileDialogResponse and
133 // OnSelectSingleFolderDialogResponse.
134 void SelectSingleFileHelper(const std::string
& output
, int exit_code
,
135 void* params
, bool allow_folder
);
137 void OnSelectSingleFileDialogResponse(const std::string
& output
,
138 int exit_code
, void* params
);
139 void OnSelectMultiFileDialogResponse(const std::string
& output
,
140 int exit_code
, void* params
);
141 void OnSelectSingleFolderDialogResponse(const std::string
& output
,
142 int exit_code
, void* params
);
144 // Should be either DESKTOP_ENVIRONMENT_KDE3 or DESKTOP_ENVIRONMENT_KDE4.
145 base::nix::DesktopEnvironment desktop_
;
147 DISALLOW_COPY_AND_ASSIGN(SelectFileDialogImplKDE
);
150 SelectFileDialogImplKDE::SelectFileDialogImplKDE(
152 ui::SelectFilePolicy
* policy
,
153 base::nix::DesktopEnvironment desktop
)
154 : SelectFileDialogImpl(listener
, policy
),
156 DCHECK(desktop_
== base::nix::DESKTOP_ENVIRONMENT_KDE3
||
157 desktop_
== base::nix::DESKTOP_ENVIRONMENT_KDE4
);
160 SelectFileDialogImplKDE::~SelectFileDialogImplKDE() {
163 // We ignore |default_extension|.
164 void SelectFileDialogImplKDE::SelectFileImpl(
166 const string16
& title
,
167 const base::FilePath
& default_path
,
168 const FileTypeInfo
* file_types
,
170 const base::FilePath::StringType
& default_extension
,
171 gfx::NativeWindow owning_window
,
174 // |owning_window| can be null when user right-clicks on a downloadable item
175 // and chooses 'Open Link in New Tab' when 'Ask where to save each file
176 // before downloading.' preference is turned on. (http://crbug.com/29213)
178 parents_
.insert(owning_window
);
180 std::string title_string
= UTF16ToUTF8(title
);
182 file_type_index_
= file_type_index
;
184 file_types_
= *file_types
;
186 file_types_
.include_all_files
= true;
190 CreateSelectFolderDialog(title_string
, default_path
,
191 owning_window
, params
);
193 case SELECT_OPEN_FILE
:
194 CreateFileOpenDialog(title_string
, default_path
, owning_window
,
197 case SELECT_OPEN_MULTI_FILE
:
198 CreateMultiFileOpenDialog(title_string
, default_path
,
199 owning_window
, params
);
201 case SELECT_SAVEAS_FILE
:
202 CreateSaveAsDialog(title_string
, default_path
, owning_window
,
211 bool SelectFileDialogImplKDE::HasMultipleFileTypeChoicesImpl() {
212 return file_types_
.extensions
.size() > 1;
215 std::string
SelectFileDialogImplKDE::GetMimeTypeFilterString() {
216 std::string filter_string
;
217 // We need a filter set because the same mime type can appear multiple times.
218 std::set
<std::string
> filter_set
;
219 for (size_t i
= 0; i
< file_types_
.extensions
.size(); ++i
) {
220 for (size_t j
= 0; j
< file_types_
.extensions
[i
].size(); ++j
) {
221 if (!file_types_
.extensions
[i
][j
].empty()) {
222 std::string mime_type
= base::nix::GetFileMimeType(
223 base::FilePath("name").ReplaceExtension(
224 file_types_
.extensions
[i
][j
]));
225 filter_set
.insert(mime_type
);
229 // Add the *.* filter, but only if we have added other filters (otherwise it
231 if (file_types_
.include_all_files
&& !file_types_
.extensions
.empty())
232 filter_set
.insert("application/octet-stream");
233 // Create the final output string.
234 filter_string
.clear();
235 for (std::set
<std::string
>::iterator it
= filter_set
.begin();
236 it
!= filter_set
.end(); ++it
) {
237 filter_string
.append(*it
+ " ");
239 return filter_string
;
242 void SelectFileDialogImplKDE::CallKDialogOutput(const KDialogParams
& params
) {
243 CommandLine::StringVector cmd_vector
;
244 cmd_vector
.push_back(kKdialogBinary
);
245 CommandLine
command_line(cmd_vector
);
246 GetKDialogCommandLine(params
.type
, params
.title
, params
.default_path
,
247 params
.parent
, params
.file_operation
,
248 params
.multiple_selection
, &command_line
);
251 // Get output from KDialog
252 base::GetAppOutputWithExitCode(command_line
, &output
, &exit_code
);
254 output
.erase(output
.size() - 1);
255 // Now the dialog is no longer showing. We can erase its parent from the
257 std::set
<GtkWindow
*>::iterator iter
= parents_
.find(params
.parent
);
258 if (iter
!= parents_
.end())
259 parents_
.erase(iter
);
260 params
.ui_loop_proxy
->PostTask(FROM_HERE
,
261 base::Bind(params
.callback
, this, output
, exit_code
,
262 params
.kdialog_params
));
265 void SelectFileDialogImplKDE::GetKDialogCommandLine(const std::string
& type
,
266 const std::string
& title
, const base::FilePath
& path
,
267 gfx::NativeWindow parent
, bool file_operation
, bool multiple_selection
,
268 CommandLine
* command_line
) {
271 // Attach to the current Chrome window.
272 GdkWindow
* gdk_window
= gtk_widget_get_window(GTK_WIDGET((parent
)));
273 int window_id
= GDK_DRAWABLE_XID(gdk_window
);
274 command_line
->AppendSwitchNative(
275 desktop_
== base::nix::DESKTOP_ENVIRONMENT_KDE3
? "--embed" : "--attach",
276 base::IntToString(window_id
));
277 // Set the correct title for the dialog.
279 command_line
->AppendSwitchNative("--title", title
);
280 // Enable multiple file selection if we need to.
281 if (multiple_selection
) {
282 command_line
->AppendSwitch("--multiple");
283 command_line
->AppendSwitch("--separate-output");
285 command_line
->AppendSwitch(type
);
286 // The path should never be empty. If it is, set it to PWD.
288 command_line
->AppendArgPath(base::FilePath("."));
290 command_line
->AppendArgPath(path
);
291 // Depending on the type of the operation we need, get the path to the
292 // file/folder and set up mime type filters.
294 command_line
->AppendArg(GetMimeTypeFilterString());
295 VLOG(1) << "KDialog command line: " << command_line
->GetCommandLineString();
298 void SelectFileDialogImplKDE::FileSelected(const base::FilePath
& path
,
300 if (type_
== SELECT_SAVEAS_FILE
)
301 *last_saved_path_
= path
.DirName();
302 else if (type_
== SELECT_OPEN_FILE
)
303 *last_opened_path_
= path
.DirName();
304 else if (type_
== SELECT_FOLDER
)
305 *last_opened_path_
= path
;
308 if (listener_
) { // What does the filter index actually do?
309 // TODO(dfilimon): Get a reasonable index value from somewhere.
310 listener_
->FileSelected(path
, 1, params
);
314 void SelectFileDialogImplKDE::MultiFilesSelected(
315 const std::vector
<base::FilePath
>& files
, void* params
) {
316 *last_opened_path_
= files
[0].DirName();
318 listener_
->MultiFilesSelected(files
, params
);
321 void SelectFileDialogImplKDE::FileNotSelected(void* params
) {
323 listener_
->FileSelectionCanceled(params
);
326 void SelectFileDialogImplKDE::CreateSelectFolderDialog(
327 const std::string
& title
, const base::FilePath
& default_path
,
328 gfx::NativeWindow parent
, void *params
) {
329 base::WorkerPool::PostTask(FROM_HERE
,
331 &SelectFileDialogImplKDE::CallKDialogOutput
,
334 "--getexistingdirectory",
335 GetTitle(title
, IDS_SELECT_FOLDER_DIALOG_TITLE
),
336 default_path
.empty() ? *last_opened_path_
: default_path
,
337 parent
, false, false, params
,
338 &SelectFileDialogImplKDE::OnSelectSingleFolderDialogResponse
)),
342 void SelectFileDialogImplKDE::CreateFileOpenDialog(
343 const std::string
& title
, const base::FilePath
& default_path
,
344 gfx::NativeWindow parent
, void* params
) {
345 base::WorkerPool::PostTask(FROM_HERE
,
347 &SelectFileDialogImplKDE::CallKDialogOutput
,
351 GetTitle(title
, IDS_OPEN_FILE_DIALOG_TITLE
),
352 default_path
.empty() ? *last_opened_path_
: default_path
,
353 parent
, true, false, params
,
354 &SelectFileDialogImplKDE::OnSelectSingleFileDialogResponse
)),
358 void SelectFileDialogImplKDE::CreateMultiFileOpenDialog(
359 const std::string
& title
, const base::FilePath
& default_path
,
360 gfx::NativeWindow parent
, void* params
) {
361 base::WorkerPool::PostTask(FROM_HERE
,
363 &SelectFileDialogImplKDE::CallKDialogOutput
,
367 GetTitle(title
, IDS_OPEN_FILES_DIALOG_TITLE
),
368 default_path
.empty() ? *last_opened_path_
: default_path
,
369 parent
, true, true, params
,
370 &SelectFileDialogImplKDE::OnSelectMultiFileDialogResponse
)),
374 void SelectFileDialogImplKDE::CreateSaveAsDialog(
375 const std::string
& title
, const base::FilePath
& default_path
,
376 gfx::NativeWindow parent
, void* params
) {
377 base::WorkerPool::PostTask(FROM_HERE
,
379 &SelectFileDialogImplKDE::CallKDialogOutput
,
383 GetTitle(title
, IDS_SAVE_AS_DIALOG_TITLE
),
384 default_path
.empty() ? *last_saved_path_
: default_path
,
385 parent
, true, false, params
,
386 &SelectFileDialogImplKDE::OnSelectSingleFileDialogResponse
)),
390 void SelectFileDialogImplKDE::SelectSingleFileHelper(const std::string
& output
,
391 int exit_code
, void* params
, bool allow_folder
) {
392 VLOG(1) << "[kdialog] SingleFileResponse: " << output
;
393 if (exit_code
!= 0 || output
.empty()) {
394 FileNotSelected(params
);
398 base::FilePath
path(output
);
400 FileSelected(path
, params
);
404 if (CallDirectoryExistsOnUIThread(path
))
405 FileNotSelected(params
);
407 FileSelected(path
, params
);
410 void SelectFileDialogImplKDE::OnSelectSingleFileDialogResponse(
411 const std::string
& output
, int exit_code
, void* params
) {
412 SelectSingleFileHelper(output
, exit_code
, params
, false);
415 void SelectFileDialogImplKDE::OnSelectSingleFolderDialogResponse(
416 const std::string
& output
, int exit_code
, void* params
) {
417 SelectSingleFileHelper(output
, exit_code
, params
, true);
420 void SelectFileDialogImplKDE::OnSelectMultiFileDialogResponse(
421 const std::string
& output
, int exit_code
, void* params
) {
422 VLOG(1) << "[kdialog] MultiFileResponse: " << output
;
424 if (exit_code
!= 0 || output
.empty()) {
425 FileNotSelected(params
);
429 std::vector
<std::string
> filenames
;
430 Tokenize(output
, "\n", &filenames
);
431 std::vector
<base::FilePath
> filenames_fp
;
432 for (std::vector
<std::string
>::iterator iter
= filenames
.begin();
433 iter
!= filenames
.end(); ++iter
) {
434 base::FilePath
path(*iter
);
435 if (CallDirectoryExistsOnUIThread(path
))
437 filenames_fp
.push_back(path
);
440 if (filenames_fp
.empty()) {
441 FileNotSelected(params
);
444 MultiFilesSelected(filenames_fp
, params
);
452 bool SelectFileDialogImpl::CheckKDEDialogWorksOnUIThread() {
453 // No choice. UI thread can't continue without an answer here. Fortunately we
454 // only do this once, the first time a file dialog is displayed.
455 base::ThreadRestrictions::ScopedAllowIO allow_io
;
457 CommandLine::StringVector cmd_vector
;
458 cmd_vector
.push_back(kKdialogBinary
);
459 cmd_vector
.push_back("--version");
460 CommandLine
command_line(cmd_vector
);
462 return base::GetAppOutput(command_line
, &dummy
);
466 SelectFileDialogImpl
* SelectFileDialogImpl::NewSelectFileDialogImplKDE(
468 ui::SelectFilePolicy
* policy
,
469 base::nix::DesktopEnvironment desktop
) {
470 return new SelectFileDialogImplKDE(listener
, policy
, desktop
);