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/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/launch.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "base/threading/thread_restrictions.h"
20 #include "base/threading/worker_pool.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 base::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,
66 const std::string& title,
67 const base::FilePath& default_path,
68 gfx::NativeWindow parent,
70 bool multiple_selection,
72 void(SelectFileDialogImplKDE::* callback)(const std::string&,
77 default_path(default_path),
79 file_operation(file_operation),
80 multiple_selection(multiple_selection),
81 kdialog_params(kdialog_params),
83 base::MessageLoopForUI::current()->message_loop_proxy()),
88 base::FilePath default_path;
89 gfx::NativeWindow parent;
91 bool multiple_selection;
93 scoped_refptr<base::MessageLoopProxy> ui_loop_proxy;
95 void (SelectFileDialogImplKDE::*callback)(const std::string&, int, void*);
98 // Get the filters from |file_types_| and concatenate them into
100 std::string GetMimeTypeFilterString();
102 // Get KDialog command line representing the Argv array for KDialog.
103 void GetKDialogCommandLine(const std::string& type, const std::string& title,
104 const base::FilePath& default_path, gfx::NativeWindow parent,
105 bool file_operation, bool multiple_selection, CommandLine* command_line);
107 // Call KDialog on a worker thread and post results back to the caller
109 void CallKDialogOutput(const KDialogParams& params);
111 // Notifies the listener that a single file was chosen.
112 void FileSelected(const base::FilePath& path, void* params);
114 // Notifies the listener that multiple files were chosen.
115 void MultiFilesSelected(const std::vector<base::FilePath>& files,
118 // Notifies the listener that no file was chosen (the action was canceled).
119 // Dialog is passed so we can find that |params| pointer that was passed to
120 // us when we were told to show the dialog.
121 void FileNotSelected(void *params);
123 void CreateSelectFolderDialog(Type type,
124 const std::string& title,
125 const base::FilePath& default_path,
126 gfx::NativeWindow parent, void* params);
128 void CreateFileOpenDialog(const std::string& title,
129 const base::FilePath& default_path,
130 gfx::NativeWindow parent, void* params);
132 void CreateMultiFileOpenDialog(const std::string& title,
133 const base::FilePath& default_path,
134 gfx::NativeWindow parent, void* params);
136 void CreateSaveAsDialog(const std::string& title,
137 const base::FilePath& default_path,
138 gfx::NativeWindow parent, void* params);
140 // Common function for OnSelectSingleFileDialogResponse and
141 // OnSelectSingleFolderDialogResponse.
142 void SelectSingleFileHelper(const std::string& output, int exit_code,
143 void* params, bool allow_folder);
145 void OnSelectSingleFileDialogResponse(const std::string& output,
146 int exit_code, void* params);
147 void OnSelectMultiFileDialogResponse(const std::string& output,
148 int exit_code, void* params);
149 void OnSelectSingleFolderDialogResponse(const std::string& output,
150 int exit_code, void* params);
152 // Should be either DESKTOP_ENVIRONMENT_KDE3 or DESKTOP_ENVIRONMENT_KDE4.
153 base::nix::DesktopEnvironment desktop_;
155 DISALLOW_COPY_AND_ASSIGN(SelectFileDialogImplKDE);
158 SelectFileDialogImplKDE::SelectFileDialogImplKDE(
160 ui::SelectFilePolicy* policy,
161 base::nix::DesktopEnvironment desktop)
162 : SelectFileDialogImpl(listener, policy),
164 DCHECK(desktop_ == base::nix::DESKTOP_ENVIRONMENT_KDE3 ||
165 desktop_ == base::nix::DESKTOP_ENVIRONMENT_KDE4);
168 SelectFileDialogImplKDE::~SelectFileDialogImplKDE() {
171 // We ignore |default_extension|.
172 void SelectFileDialogImplKDE::SelectFileImpl(
174 const base::string16& title,
175 const base::FilePath& default_path,
176 const FileTypeInfo* file_types,
178 const base::FilePath::StringType& default_extension,
179 gfx::NativeWindow owning_window,
182 // |owning_window| can be null when user right-clicks on a downloadable item
183 // and chooses 'Open Link in New Tab' when 'Ask where to save each file
184 // before downloading.' preference is turned on. (http://crbug.com/29213)
186 parents_.insert(owning_window);
188 std::string title_string = UTF16ToUTF8(title);
190 file_type_index_ = file_type_index;
192 file_types_ = *file_types;
194 file_types_.include_all_files = true;
198 case SELECT_UPLOAD_FOLDER:
199 CreateSelectFolderDialog(type, title_string, default_path,
200 owning_window, params);
202 case SELECT_OPEN_FILE:
203 CreateFileOpenDialog(title_string, default_path, owning_window,
206 case SELECT_OPEN_MULTI_FILE:
207 CreateMultiFileOpenDialog(title_string, default_path,
208 owning_window, params);
210 case SELECT_SAVEAS_FILE:
211 CreateSaveAsDialog(title_string, default_path, owning_window,
220 bool SelectFileDialogImplKDE::HasMultipleFileTypeChoicesImpl() {
221 return file_types_.extensions.size() > 1;
224 std::string SelectFileDialogImplKDE::GetMimeTypeFilterString() {
225 std::string filter_string;
226 // We need a filter set because the same mime type can appear multiple times.
227 std::set<std::string> filter_set;
228 for (size_t i = 0; i < file_types_.extensions.size(); ++i) {
229 for (size_t j = 0; j < file_types_.extensions[i].size(); ++j) {
230 if (!file_types_.extensions[i][j].empty()) {
231 std::string mime_type = base::nix::GetFileMimeType(
232 base::FilePath("name").ReplaceExtension(
233 file_types_.extensions[i][j]));
234 filter_set.insert(mime_type);
238 // Add the *.* filter, but only if we have added other filters (otherwise it
240 if (file_types_.include_all_files && !file_types_.extensions.empty())
241 filter_set.insert("application/octet-stream");
242 // Create the final output string.
243 filter_string.clear();
244 for (std::set<std::string>::iterator it = filter_set.begin();
245 it != filter_set.end(); ++it) {
246 filter_string.append(*it + " ");
248 return filter_string;
251 void SelectFileDialogImplKDE::CallKDialogOutput(const KDialogParams& params) {
252 CommandLine::StringVector cmd_vector;
253 cmd_vector.push_back(kKdialogBinary);
254 CommandLine command_line(cmd_vector);
255 GetKDialogCommandLine(params.type, params.title, params.default_path,
256 params.parent, params.file_operation,
257 params.multiple_selection, &command_line);
260 // Get output from KDialog
261 base::GetAppOutputWithExitCode(command_line, &output, &exit_code);
263 output.erase(output.size() - 1);
264 // Now the dialog is no longer showing. We can erase its parent from the
266 std::set<GtkWindow*>::iterator iter = parents_.find(params.parent);
267 if (iter != parents_.end())
268 parents_.erase(iter);
269 params.ui_loop_proxy->PostTask(FROM_HERE,
270 base::Bind(params.callback, this, output, exit_code,
271 params.kdialog_params));
274 void SelectFileDialogImplKDE::GetKDialogCommandLine(const std::string& type,
275 const std::string& title, const base::FilePath& path,
276 gfx::NativeWindow parent, bool file_operation, bool multiple_selection,
277 CommandLine* command_line) {
280 // Attach to the current Chrome window.
281 GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET((parent)));
282 int window_id = GDK_DRAWABLE_XID(gdk_window);
283 command_line->AppendSwitchNative(
284 desktop_ == base::nix::DESKTOP_ENVIRONMENT_KDE3 ? "--embed" : "--attach",
285 base::IntToString(window_id));
286 // Set the correct title for the dialog.
288 command_line->AppendSwitchNative("--title", title);
289 // Enable multiple file selection if we need to.
290 if (multiple_selection) {
291 command_line->AppendSwitch("--multiple");
292 command_line->AppendSwitch("--separate-output");
294 command_line->AppendSwitch(type);
295 // The path should never be empty. If it is, set it to PWD.
297 command_line->AppendArgPath(base::FilePath("."));
299 command_line->AppendArgPath(path);
300 // Depending on the type of the operation we need, get the path to the
301 // file/folder and set up mime type filters.
303 command_line->AppendArg(GetMimeTypeFilterString());
304 VLOG(1) << "KDialog command line: " << command_line->GetCommandLineString();
307 void SelectFileDialogImplKDE::FileSelected(const base::FilePath& path,
309 if (type_ == SELECT_SAVEAS_FILE)
310 *last_saved_path_ = path.DirName();
311 else if (type_ == SELECT_OPEN_FILE)
312 *last_opened_path_ = path.DirName();
313 else if (type_ == SELECT_FOLDER)
314 *last_opened_path_ = path;
317 if (listener_) { // What does the filter index actually do?
318 // TODO(dfilimon): Get a reasonable index value from somewhere.
319 listener_->FileSelected(path, 1, params);
323 void SelectFileDialogImplKDE::MultiFilesSelected(
324 const std::vector<base::FilePath>& files, void* params) {
325 *last_opened_path_ = files[0].DirName();
327 listener_->MultiFilesSelected(files, params);
330 void SelectFileDialogImplKDE::FileNotSelected(void* params) {
332 listener_->FileSelectionCanceled(params);
335 void SelectFileDialogImplKDE::CreateSelectFolderDialog(
336 Type type, const std::string& title, const base::FilePath& default_path,
337 gfx::NativeWindow parent, void *params) {
338 int title_message_id = (type == SELECT_UPLOAD_FOLDER) ?
339 IDS_SELECT_UPLOAD_FOLDER_DIALOG_TITLE :
340 IDS_SELECT_FOLDER_DIALOG_TITLE;
341 base::WorkerPool::PostTask(FROM_HERE,
343 &SelectFileDialogImplKDE::CallKDialogOutput,
346 "--getexistingdirectory",
347 GetTitle(title, title_message_id),
348 default_path.empty() ? *last_opened_path_ : default_path,
349 parent, false, false, params,
350 &SelectFileDialogImplKDE::OnSelectSingleFolderDialogResponse)),
354 void SelectFileDialogImplKDE::CreateFileOpenDialog(
355 const std::string& title, const base::FilePath& default_path,
356 gfx::NativeWindow parent, void* params) {
357 base::WorkerPool::PostTask(FROM_HERE,
359 &SelectFileDialogImplKDE::CallKDialogOutput,
363 GetTitle(title, IDS_OPEN_FILE_DIALOG_TITLE),
364 default_path.empty() ? *last_opened_path_ : default_path,
365 parent, true, false, params,
366 &SelectFileDialogImplKDE::OnSelectSingleFileDialogResponse)),
370 void SelectFileDialogImplKDE::CreateMultiFileOpenDialog(
371 const std::string& title, const base::FilePath& default_path,
372 gfx::NativeWindow parent, void* params) {
373 base::WorkerPool::PostTask(FROM_HERE,
375 &SelectFileDialogImplKDE::CallKDialogOutput,
379 GetTitle(title, IDS_OPEN_FILES_DIALOG_TITLE),
380 default_path.empty() ? *last_opened_path_ : default_path,
381 parent, true, true, params,
382 &SelectFileDialogImplKDE::OnSelectMultiFileDialogResponse)),
386 void SelectFileDialogImplKDE::CreateSaveAsDialog(
387 const std::string& title, const base::FilePath& default_path,
388 gfx::NativeWindow parent, void* params) {
389 base::WorkerPool::PostTask(FROM_HERE,
391 &SelectFileDialogImplKDE::CallKDialogOutput,
395 GetTitle(title, IDS_SAVE_AS_DIALOG_TITLE),
396 default_path.empty() ? *last_saved_path_ : default_path,
397 parent, true, false, params,
398 &SelectFileDialogImplKDE::OnSelectSingleFileDialogResponse)),
402 void SelectFileDialogImplKDE::SelectSingleFileHelper(const std::string& output,
403 int exit_code, void* params, bool allow_folder) {
404 VLOG(1) << "[kdialog] SingleFileResponse: " << output;
405 if (exit_code != 0 || output.empty()) {
406 FileNotSelected(params);
410 base::FilePath path(output);
412 FileSelected(path, params);
416 if (CallDirectoryExistsOnUIThread(path))
417 FileNotSelected(params);
419 FileSelected(path, params);
422 void SelectFileDialogImplKDE::OnSelectSingleFileDialogResponse(
423 const std::string& output, int exit_code, void* params) {
424 SelectSingleFileHelper(output, exit_code, params, false);
427 void SelectFileDialogImplKDE::OnSelectSingleFolderDialogResponse(
428 const std::string& output, int exit_code, void* params) {
429 SelectSingleFileHelper(output, exit_code, params, true);
432 void SelectFileDialogImplKDE::OnSelectMultiFileDialogResponse(
433 const std::string& output, int exit_code, void* params) {
434 VLOG(1) << "[kdialog] MultiFileResponse: " << output;
436 if (exit_code != 0 || output.empty()) {
437 FileNotSelected(params);
441 std::vector<std::string> filenames;
442 Tokenize(output, "\n", &filenames);
443 std::vector<base::FilePath> filenames_fp;
444 for (std::vector<std::string>::iterator iter = filenames.begin();
445 iter != filenames.end(); ++iter) {
446 base::FilePath path(*iter);
447 if (CallDirectoryExistsOnUIThread(path))
449 filenames_fp.push_back(path);
452 if (filenames_fp.empty()) {
453 FileNotSelected(params);
456 MultiFilesSelected(filenames_fp, params);
464 bool SelectFileDialogImpl::CheckKDEDialogWorksOnUIThread() {
465 // No choice. UI thread can't continue without an answer here. Fortunately we
466 // only do this once, the first time a file dialog is displayed.
467 base::ThreadRestrictions::ScopedAllowIO allow_io;
469 CommandLine::StringVector cmd_vector;
470 cmd_vector.push_back(kKdialogBinary);
471 cmd_vector.push_back("--version");
472 CommandLine command_line(cmd_vector);
474 return base::GetAppOutput(command_line, &dummy);
478 SelectFileDialogImpl* SelectFileDialogImpl::NewSelectFileDialogImplKDE(
480 ui::SelectFilePolicy* policy,
481 base::nix::DesktopEnvironment desktop) {
482 return new SelectFileDialogImplKDE(listener, policy, desktop);