Add ENABLE_MEDIA_ROUTER define to builds other than Android and iOS.
[chromium-blink-merge.git] / chrome / browser / ui / libgtk2ui / select_file_dialog_impl_kde.cc
blobd707185d509bcdfb90a7471344b3116a9a058aec
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.
5 #include <set>
7 #include <X11/Xlib.h>
9 #include "base/bind.h"
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/utf_string_conversions.h"
19 #include "base/threading/thread_restrictions.h"
20 #include "chrome/browser/ui/libgtk2ui/select_file_dialog_impl.h"
21 #include "chrome/grit/generated_resources.h"
22 #include "content/public/browser/browser_thread.h"
23 #include "ui/aura/window_tree_host.h"
24 #include "ui/base/l10n/l10n_util.h"
25 #include "ui/strings/grit/ui_strings.h"
27 // These conflict with base/tracked_objects.h, so need to come last.
28 #include <gdk/gdkx.h>
29 #include <gtk/gtk.h>
31 using content::BrowserThread;
33 namespace {
35 std::string GetTitle(const std::string& title, int message_id) {
36 return title.empty() ? l10n_util::GetStringUTF8(message_id) : title;
39 const char kKdialogBinary[] = "kdialog";
41 } // namespace
43 namespace libgtk2ui {
45 // Implementation of SelectFileDialog that shows a KDE common dialog for
46 // choosing a file or folder. This acts as a modal dialog.
47 class SelectFileDialogImplKDE : public SelectFileDialogImpl {
48 public:
49 SelectFileDialogImplKDE(Listener* listener,
50 ui::SelectFilePolicy* policy,
51 base::nix::DesktopEnvironment desktop);
53 protected:
54 ~SelectFileDialogImplKDE() override;
56 // BaseShellDialog implementation:
57 bool IsRunning(gfx::NativeWindow parent_window) const override;
59 // SelectFileDialog implementation.
60 // |params| is user data we pass back via the Listener interface.
61 void SelectFileImpl(Type type,
62 const base::string16& title,
63 const base::FilePath& default_path,
64 const FileTypeInfo* file_types,
65 int file_type_index,
66 const base::FilePath::StringType& default_extension,
67 gfx::NativeWindow owning_window,
68 void* params) override;
70 private:
71 bool HasMultipleFileTypeChoicesImpl() override;
73 struct KDialogParams {
74 KDialogParams(const std::string& type, const std::string& title,
75 const base::FilePath& default_path, XID parent,
76 bool file_operation, bool multiple_selection,
77 void* kdialog_params,
78 void (SelectFileDialogImplKDE::*callback)(XID,
79 const std::string&,
80 int, void*))
81 : type(type), title(title), default_path(default_path), parent(parent),
82 file_operation(file_operation),
83 multiple_selection(multiple_selection),
84 kdialog_params(kdialog_params), callback(callback) {
87 std::string type;
88 std::string title;
89 base::FilePath default_path;
90 XID parent;
91 bool file_operation;
92 bool multiple_selection;
93 void* kdialog_params;
94 void (SelectFileDialogImplKDE::*callback)(XID, const std::string&,
95 int, void*);
98 // Get the filters from |file_types_| and concatenate them into
99 // |filter_string|.
100 std::string GetMimeTypeFilterString();
102 // Get KDialog command line representing the Argv array for KDialog.
103 void GetKDialogCommandLine(const std::string& type,
104 const std::string& title,
105 const base::FilePath& default_path,
106 XID parent,
107 bool file_operation,
108 bool multiple_selection,
109 base::CommandLine* command_line);
111 // Call KDialog on the FILE thread and post results back to the UI thread.
112 void CallKDialogOutput(const KDialogParams& params);
114 // Notifies the listener that a single file was chosen.
115 void FileSelected(const base::FilePath& path, void* params);
117 // Notifies the listener that multiple files were chosen.
118 void MultiFilesSelected(const std::vector<base::FilePath>& files,
119 void* params);
121 // Notifies the listener that no file was chosen (the action was canceled).
122 // Dialog is passed so we can find that |params| pointer that was passed to
123 // us when we were told to show the dialog.
124 void FileNotSelected(void *params);
126 void CreateSelectFolderDialog(Type type,
127 const std::string& title,
128 const base::FilePath& default_path,
129 XID parent, void* params);
131 void CreateFileOpenDialog(const std::string& title,
132 const base::FilePath& default_path,
133 XID parent, void* params);
135 void CreateMultiFileOpenDialog(const std::string& title,
136 const base::FilePath& default_path,
137 XID parent, void* params);
139 void CreateSaveAsDialog(const std::string& title,
140 const base::FilePath& default_path,
141 XID parent, void* params);
143 // Common function for OnSelectSingleFileDialogResponse and
144 // OnSelectSingleFolderDialogResponse.
145 void SelectSingleFileHelper(const std::string& output, int exit_code,
146 void* params, bool allow_folder);
148 void OnSelectSingleFileDialogResponse(XID parent,
149 const std::string& output,
150 int exit_code, void* params);
151 void OnSelectMultiFileDialogResponse(XID parent,
152 const std::string& output,
153 int exit_code, void* params);
154 void OnSelectSingleFolderDialogResponse(XID parent,
155 const std::string& output,
156 int exit_code, void* params);
158 // Should be either DESKTOP_ENVIRONMENT_KDE3 or DESKTOP_ENVIRONMENT_KDE4.
159 base::nix::DesktopEnvironment desktop_;
161 // The set of all parent windows for which we are currently running
162 // dialogs. This should only be accessed on the UI thread.
163 std::set<XID> parents_;
165 DISALLOW_COPY_AND_ASSIGN(SelectFileDialogImplKDE);
168 // static
169 bool SelectFileDialogImpl::CheckKDEDialogWorksOnUIThread() {
170 // No choice. UI thread can't continue without an answer here. Fortunately we
171 // only do this once, the first time a file dialog is displayed.
172 base::ThreadRestrictions::ScopedAllowIO allow_io;
174 base::CommandLine::StringVector cmd_vector;
175 cmd_vector.push_back(kKdialogBinary);
176 cmd_vector.push_back("--version");
177 base::CommandLine command_line(cmd_vector);
178 std::string dummy;
179 return base::GetAppOutput(command_line, &dummy);
182 // static
183 SelectFileDialogImpl* SelectFileDialogImpl::NewSelectFileDialogImplKDE(
184 Listener* listener,
185 ui::SelectFilePolicy* policy,
186 base::nix::DesktopEnvironment desktop) {
187 return new SelectFileDialogImplKDE(listener, policy, desktop);
190 SelectFileDialogImplKDE::SelectFileDialogImplKDE(
191 Listener* listener,
192 ui::SelectFilePolicy* policy,
193 base::nix::DesktopEnvironment desktop)
194 : SelectFileDialogImpl(listener, policy),
195 desktop_(desktop) {
196 DCHECK(desktop_ == base::nix::DESKTOP_ENVIRONMENT_KDE3 ||
197 desktop_ == base::nix::DESKTOP_ENVIRONMENT_KDE4);
200 SelectFileDialogImplKDE::~SelectFileDialogImplKDE() {
203 bool SelectFileDialogImplKDE::IsRunning(gfx::NativeWindow parent_window) const {
204 DCHECK_CURRENTLY_ON(BrowserThread::UI);
205 if (parent_window && parent_window->GetHost()) {
206 XID xid = parent_window->GetHost()->GetAcceleratedWidget();
207 return parents_.find(xid) != parents_.end();
210 return false;
213 // We ignore |default_extension|.
214 void SelectFileDialogImplKDE::SelectFileImpl(
215 Type type,
216 const base::string16& title,
217 const base::FilePath& default_path,
218 const FileTypeInfo* file_types,
219 int file_type_index,
220 const base::FilePath::StringType& default_extension,
221 gfx::NativeWindow owning_window,
222 void* params) {
223 DCHECK_CURRENTLY_ON(BrowserThread::UI);
224 type_ = type;
226 XID window_xid = None;
227 if (owning_window && owning_window->GetHost()) {
228 // |owning_window| can be null when user right-clicks on a downloadable item
229 // and chooses 'Open Link in New Tab' when 'Ask where to save each file
230 // before downloading.' preference is turned on. (http://crbug.com/29213)
231 window_xid = owning_window->GetHost()->GetAcceleratedWidget();
232 parents_.insert(window_xid);
235 std::string title_string = base::UTF16ToUTF8(title);
237 file_type_index_ = file_type_index;
238 if (file_types)
239 file_types_ = *file_types;
240 else
241 file_types_.include_all_files = true;
243 switch (type) {
244 case SELECT_FOLDER:
245 case SELECT_UPLOAD_FOLDER:
246 CreateSelectFolderDialog(type, title_string, default_path,
247 window_xid, params);
248 return;
249 case SELECT_OPEN_FILE:
250 CreateFileOpenDialog(title_string, default_path, window_xid, params);
251 return;
252 case SELECT_OPEN_MULTI_FILE:
253 CreateMultiFileOpenDialog(title_string, default_path, window_xid, params);
254 return;
255 case SELECT_SAVEAS_FILE:
256 CreateSaveAsDialog(title_string, default_path, window_xid, params);
257 return;
258 default:
259 NOTREACHED();
260 return;
264 bool SelectFileDialogImplKDE::HasMultipleFileTypeChoicesImpl() {
265 return file_types_.extensions.size() > 1;
268 std::string SelectFileDialogImplKDE::GetMimeTypeFilterString() {
269 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
270 std::string filter_string;
271 // We need a filter set because the same mime type can appear multiple times.
272 std::set<std::string> filter_set;
273 for (size_t i = 0; i < file_types_.extensions.size(); ++i) {
274 for (size_t j = 0; j < file_types_.extensions[i].size(); ++j) {
275 if (!file_types_.extensions[i][j].empty()) {
276 std::string mime_type = base::nix::GetFileMimeType(base::FilePath(
277 "name").ReplaceExtension(file_types_.extensions[i][j]));
278 filter_set.insert(mime_type);
282 // Add the *.* filter, but only if we have added other filters (otherwise it
283 // is implied).
284 if (file_types_.include_all_files && !file_types_.extensions.empty())
285 filter_set.insert("application/octet-stream");
286 // Create the final output string.
287 filter_string.clear();
288 for (std::set<std::string>::iterator it = filter_set.begin();
289 it != filter_set.end(); ++it) {
290 filter_string.append(*it + " ");
292 return filter_string;
295 void SelectFileDialogImplKDE::CallKDialogOutput(const KDialogParams& params) {
296 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
297 base::CommandLine::StringVector cmd_vector;
298 cmd_vector.push_back(kKdialogBinary);
299 base::CommandLine command_line(cmd_vector);
300 GetKDialogCommandLine(params.type, params.title, params.default_path,
301 params.parent, params.file_operation,
302 params.multiple_selection, &command_line);
303 std::string output;
304 int exit_code;
305 // Get output from KDialog
306 base::GetAppOutputWithExitCode(command_line, &output, &exit_code);
307 if (!output.empty())
308 output.erase(output.size() - 1);
310 // Now the dialog is no longer showing, but we can't erase its parent from the
311 // parent set yet because we're on the FILE thread.
312 BrowserThread::PostTask(
313 BrowserThread::UI, FROM_HERE,
314 base::Bind(params.callback, this, params.parent, output, exit_code,
315 params.kdialog_params));
318 void SelectFileDialogImplKDE::GetKDialogCommandLine(
319 const std::string& type,
320 const std::string& title,
321 const base::FilePath& path,
322 XID parent,
323 bool file_operation,
324 bool multiple_selection,
325 base::CommandLine* command_line) {
326 CHECK(command_line);
328 // Attach to the current Chrome window.
329 if (parent != None) {
330 command_line->AppendSwitchNative(
331 desktop_ == base::nix::DESKTOP_ENVIRONMENT_KDE3 ?
332 "--embed" : "--attach",
333 base::IntToString(parent));
336 // Set the correct title for the dialog.
337 if (!title.empty())
338 command_line->AppendSwitchNative("--title", title);
339 // Enable multiple file selection if we need to.
340 if (multiple_selection) {
341 command_line->AppendSwitch("--multiple");
342 command_line->AppendSwitch("--separate-output");
344 command_line->AppendSwitch(type);
345 // The path should never be empty. If it is, set it to PWD.
346 if (path.empty())
347 command_line->AppendArgPath(base::FilePath("."));
348 else
349 command_line->AppendArgPath(path);
350 // Depending on the type of the operation we need, get the path to the
351 // file/folder and set up mime type filters.
352 if (file_operation)
353 command_line->AppendArg(GetMimeTypeFilterString());
354 VLOG(1) << "KDialog command line: " << command_line->GetCommandLineString();
357 void SelectFileDialogImplKDE::FileSelected(const base::FilePath& path,
358 void* params) {
359 if (type_ == SELECT_SAVEAS_FILE)
360 *last_saved_path_ = path.DirName();
361 else if (type_ == SELECT_OPEN_FILE)
362 *last_opened_path_ = path.DirName();
363 else if (type_ == SELECT_FOLDER || type_ == SELECT_UPLOAD_FOLDER)
364 *last_opened_path_ = path;
365 else
366 NOTREACHED();
367 if (listener_) { // What does the filter index actually do?
368 // TODO(dfilimon): Get a reasonable index value from somewhere.
369 listener_->FileSelected(path, 1, params);
373 void SelectFileDialogImplKDE::MultiFilesSelected(
374 const std::vector<base::FilePath>& files, void* params) {
375 *last_opened_path_ = files[0].DirName();
376 if (listener_)
377 listener_->MultiFilesSelected(files, params);
380 void SelectFileDialogImplKDE::FileNotSelected(void* params) {
381 if (listener_)
382 listener_->FileSelectionCanceled(params);
385 void SelectFileDialogImplKDE::CreateSelectFolderDialog(
386 Type type, const std::string& title, const base::FilePath& default_path,
387 XID parent, void *params) {
388 int title_message_id = (type == SELECT_UPLOAD_FOLDER)
389 ? IDS_SELECT_UPLOAD_FOLDER_DIALOG_TITLE
390 : IDS_SELECT_FOLDER_DIALOG_TITLE;
391 BrowserThread::PostTask(
392 BrowserThread::FILE, FROM_HERE,
393 base::Bind(
394 &SelectFileDialogImplKDE::CallKDialogOutput,
395 this,
396 KDialogParams(
397 "--getexistingdirectory",
398 GetTitle(title, title_message_id),
399 default_path.empty() ? *last_opened_path_ : default_path,
400 parent, false, false, params,
401 &SelectFileDialogImplKDE::OnSelectSingleFolderDialogResponse)));
404 void SelectFileDialogImplKDE::CreateFileOpenDialog(
405 const std::string& title, const base::FilePath& default_path,
406 XID parent, void* params) {
407 BrowserThread::PostTask(
408 BrowserThread::FILE, FROM_HERE,
409 base::Bind(
410 &SelectFileDialogImplKDE::CallKDialogOutput,
411 this,
412 KDialogParams(
413 "--getopenfilename",
414 GetTitle(title, IDS_OPEN_FILE_DIALOG_TITLE),
415 default_path.empty() ? *last_opened_path_ : default_path,
416 parent, true, false, params,
417 &SelectFileDialogImplKDE::OnSelectSingleFileDialogResponse)));
420 void SelectFileDialogImplKDE::CreateMultiFileOpenDialog(
421 const std::string& title, const base::FilePath& default_path,
422 XID parent, void* params) {
423 BrowserThread::PostTask(
424 BrowserThread::FILE, FROM_HERE,
425 base::Bind(
426 &SelectFileDialogImplKDE::CallKDialogOutput,
427 this,
428 KDialogParams(
429 "--getopenfilename",
430 GetTitle(title, IDS_OPEN_FILES_DIALOG_TITLE),
431 default_path.empty() ? *last_opened_path_ : default_path,
432 parent, true, true, params,
433 &SelectFileDialogImplKDE::OnSelectMultiFileDialogResponse)));
436 void SelectFileDialogImplKDE::CreateSaveAsDialog(
437 const std::string& title, const base::FilePath& default_path,
438 XID parent, void* params) {
439 BrowserThread::PostTask(
440 BrowserThread::FILE, FROM_HERE,
441 base::Bind(
442 &SelectFileDialogImplKDE::CallKDialogOutput,
443 this,
444 KDialogParams(
445 "--getsavefilename",
446 GetTitle(title, IDS_SAVE_AS_DIALOG_TITLE),
447 default_path.empty() ? *last_saved_path_ : default_path,
448 parent, true, false, params,
449 &SelectFileDialogImplKDE::OnSelectSingleFileDialogResponse)));
452 void SelectFileDialogImplKDE::SelectSingleFileHelper(const std::string& output,
453 int exit_code, void* params, bool allow_folder) {
454 VLOG(1) << "[kdialog] SingleFileResponse: " << output;
455 if (exit_code != 0 || output.empty()) {
456 FileNotSelected(params);
457 return;
460 base::FilePath path(output);
461 if (allow_folder) {
462 FileSelected(path, params);
463 return;
466 if (CallDirectoryExistsOnUIThread(path))
467 FileNotSelected(params);
468 else
469 FileSelected(path, params);
472 void SelectFileDialogImplKDE::OnSelectSingleFileDialogResponse(
473 XID parent, const std::string& output, int exit_code, void* params) {
474 DCHECK_CURRENTLY_ON(BrowserThread::UI);
475 parents_.erase(parent);
476 SelectSingleFileHelper(output, exit_code, params, false);
479 void SelectFileDialogImplKDE::OnSelectSingleFolderDialogResponse(
480 XID parent, const std::string& output, int exit_code, void* params) {
481 DCHECK_CURRENTLY_ON(BrowserThread::UI);
482 parents_.erase(parent);
483 SelectSingleFileHelper(output, exit_code, params, true);
486 void SelectFileDialogImplKDE::OnSelectMultiFileDialogResponse(
487 XID parent, const std::string& output, int exit_code, void* params) {
488 DCHECK_CURRENTLY_ON(BrowserThread::UI);
489 VLOG(1) << "[kdialog] MultiFileResponse: " << output;
491 parents_.erase(parent);
493 if (exit_code != 0 || output.empty()) {
494 FileNotSelected(params);
495 return;
498 std::vector<std::string> filenames;
499 Tokenize(output, "\n", &filenames);
500 std::vector<base::FilePath> filenames_fp;
501 for (std::vector<std::string>::iterator iter = filenames.begin();
502 iter != filenames.end(); ++iter) {
503 base::FilePath path(*iter);
504 if (CallDirectoryExistsOnUIThread(path))
505 continue;
506 filenames_fp.push_back(path);
509 if (filenames_fp.empty()) {
510 FileNotSelected(params);
511 return;
513 MultiFilesSelected(filenames_fp, params);
516 } // namespace libgtk2ui