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