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/utf_string_conversions.h"
19 #include "base/threading/thread_restrictions.h"
20 #include "chrome/browser/ui/libgtk2ui/select_file_dialog_impl.h"
22 #include "content/public/browser/browser_thread.h"
23 #include "grit/generated_resources.h"
24 #include "grit/ui_strings.h"
25 #include "ui/aura/window_tree_host.h"
26 #include "ui/base/l10n/l10n_util.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 virtual ~SelectFileDialogImplKDE();
57 // BaseShellDialog implementation:
58 virtual bool IsRunning(gfx::NativeWindow parent_window
) const OVERRIDE
;
60 // SelectFileDialog implementation.
61 // |params| is user data we pass back via the Listener interface.
62 virtual void SelectFileImpl(
64 const base::string16
& title
,
65 const base::FilePath
& default_path
,
66 const FileTypeInfo
* file_types
,
68 const base::FilePath::StringType
& default_extension
,
69 gfx::NativeWindow owning_window
,
70 void* params
) OVERRIDE
;
73 virtual bool HasMultipleFileTypeChoicesImpl() OVERRIDE
;
75 struct KDialogParams
{
76 KDialogParams(const std::string
& type
, const std::string
& title
,
77 const base::FilePath
& default_path
, XID parent
,
78 bool file_operation
, bool multiple_selection
,
80 void (SelectFileDialogImplKDE::*callback
)(XID
,
83 : type(type
), title(title
), default_path(default_path
), parent(parent
),
84 file_operation(file_operation
),
85 multiple_selection(multiple_selection
),
86 kdialog_params(kdialog_params
), callback(callback
) {
91 base::FilePath default_path
;
94 bool multiple_selection
;
96 void (SelectFileDialogImplKDE::*callback
)(XID
, const std::string
&,
100 // Get the filters from |file_types_| and concatenate them into
102 std::string
GetMimeTypeFilterString();
104 // Get KDialog command line representing the Argv array for KDialog.
105 void GetKDialogCommandLine(const std::string
& type
, const std::string
& title
,
106 const base::FilePath
& default_path
, XID parent
,
107 bool file_operation
, bool multiple_selection
, CommandLine
* command_line
);
109 // Call KDialog on the FILE thread and post results back to the UI thread.
110 void CallKDialogOutput(const KDialogParams
& params
);
112 // Notifies the listener that a single file was chosen.
113 void FileSelected(const base::FilePath
& path
, void* params
);
115 // Notifies the listener that multiple files were chosen.
116 void MultiFilesSelected(const std::vector
<base::FilePath
>& files
,
119 // Notifies the listener that no file was chosen (the action was canceled).
120 // Dialog is passed so we can find that |params| pointer that was passed to
121 // us when we were told to show the dialog.
122 void FileNotSelected(void *params
);
124 void CreateSelectFolderDialog(Type type
,
125 const std::string
& title
,
126 const base::FilePath
& default_path
,
127 XID parent
, void* params
);
129 void CreateFileOpenDialog(const std::string
& title
,
130 const base::FilePath
& default_path
,
131 XID parent
, void* params
);
133 void CreateMultiFileOpenDialog(const std::string
& title
,
134 const base::FilePath
& default_path
,
135 XID parent
, void* params
);
137 void CreateSaveAsDialog(const std::string
& title
,
138 const base::FilePath
& default_path
,
139 XID parent
, void* params
);
141 // Common function for OnSelectSingleFileDialogResponse and
142 // OnSelectSingleFolderDialogResponse.
143 void SelectSingleFileHelper(const std::string
& output
, int exit_code
,
144 void* params
, bool allow_folder
);
146 void OnSelectSingleFileDialogResponse(XID parent
,
147 const std::string
& output
,
148 int exit_code
, void* params
);
149 void OnSelectMultiFileDialogResponse(XID parent
,
150 const std::string
& output
,
151 int exit_code
, void* params
);
152 void OnSelectSingleFolderDialogResponse(XID parent
,
153 const std::string
& output
,
154 int exit_code
, void* params
);
156 // Should be either DESKTOP_ENVIRONMENT_KDE3 or DESKTOP_ENVIRONMENT_KDE4.
157 base::nix::DesktopEnvironment desktop_
;
159 // The set of all parent windows for which we are currently running
160 // dialogs. This should only be accessed on the UI thread.
161 std::set
<XID
> parents_
;
163 DISALLOW_COPY_AND_ASSIGN(SelectFileDialogImplKDE
);
167 bool SelectFileDialogImpl::CheckKDEDialogWorksOnUIThread() {
168 // No choice. UI thread can't continue without an answer here. Fortunately we
169 // only do this once, the first time a file dialog is displayed.
170 base::ThreadRestrictions::ScopedAllowIO allow_io
;
172 CommandLine::StringVector cmd_vector
;
173 cmd_vector
.push_back(kKdialogBinary
);
174 cmd_vector
.push_back("--version");
175 CommandLine
command_line(cmd_vector
);
177 return base::GetAppOutput(command_line
, &dummy
);
181 SelectFileDialogImpl
* SelectFileDialogImpl::NewSelectFileDialogImplKDE(
183 ui::SelectFilePolicy
* policy
,
184 base::nix::DesktopEnvironment desktop
) {
185 return new SelectFileDialogImplKDE(listener
, policy
, desktop
);
188 SelectFileDialogImplKDE::SelectFileDialogImplKDE(
190 ui::SelectFilePolicy
* policy
,
191 base::nix::DesktopEnvironment desktop
)
192 : SelectFileDialogImpl(listener
, policy
),
194 DCHECK(desktop_
== base::nix::DESKTOP_ENVIRONMENT_KDE3
||
195 desktop_
== base::nix::DESKTOP_ENVIRONMENT_KDE4
);
198 SelectFileDialogImplKDE::~SelectFileDialogImplKDE() {
201 bool SelectFileDialogImplKDE::IsRunning(gfx::NativeWindow parent_window
) const {
202 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
203 if (parent_window
&& parent_window
->GetHost()) {
204 XID xid
= parent_window
->GetHost()->GetAcceleratedWidget();
205 return parents_
.find(xid
) != parents_
.end();
211 // We ignore |default_extension|.
212 void SelectFileDialogImplKDE::SelectFileImpl(
214 const base::string16
& title
,
215 const base::FilePath
& default_path
,
216 const FileTypeInfo
* file_types
,
218 const base::FilePath::StringType
& default_extension
,
219 gfx::NativeWindow owning_window
,
221 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
224 XID window_xid
= None
;
225 if (owning_window
&& owning_window
->GetHost()) {
226 // |owning_window| can be null when user right-clicks on a downloadable item
227 // and chooses 'Open Link in New Tab' when 'Ask where to save each file
228 // before downloading.' preference is turned on. (http://crbug.com/29213)
229 window_xid
= owning_window
->GetHost()->GetAcceleratedWidget();
230 parents_
.insert(window_xid
);
233 std::string title_string
= base::UTF16ToUTF8(title
);
235 file_type_index_
= file_type_index
;
237 file_types_
= *file_types
;
239 file_types_
.include_all_files
= true;
243 case SELECT_UPLOAD_FOLDER
:
244 CreateSelectFolderDialog(type
, title_string
, default_path
,
247 case SELECT_OPEN_FILE
:
248 CreateFileOpenDialog(title_string
, default_path
, window_xid
, params
);
250 case SELECT_OPEN_MULTI_FILE
:
251 CreateMultiFileOpenDialog(title_string
, default_path
, window_xid
, params
);
253 case SELECT_SAVEAS_FILE
:
254 CreateSaveAsDialog(title_string
, default_path
, window_xid
, params
);
262 bool SelectFileDialogImplKDE::HasMultipleFileTypeChoicesImpl() {
263 return file_types_
.extensions
.size() > 1;
266 std::string
SelectFileDialogImplKDE::GetMimeTypeFilterString() {
267 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
268 std::string filter_string
;
269 // We need a filter set because the same mime type can appear multiple times.
270 std::set
<std::string
> filter_set
;
271 for (size_t i
= 0; i
< file_types_
.extensions
.size(); ++i
) {
272 for (size_t j
= 0; j
< file_types_
.extensions
[i
].size(); ++j
) {
273 if (!file_types_
.extensions
[i
][j
].empty()) {
274 std::string mime_type
= base::nix::GetFileMimeType(base::FilePath(
275 "name").ReplaceExtension(file_types_
.extensions
[i
][j
]));
276 filter_set
.insert(mime_type
);
280 // Add the *.* filter, but only if we have added other filters (otherwise it
282 if (file_types_
.include_all_files
&& !file_types_
.extensions
.empty())
283 filter_set
.insert("application/octet-stream");
284 // Create the final output string.
285 filter_string
.clear();
286 for (std::set
<std::string
>::iterator it
= filter_set
.begin();
287 it
!= filter_set
.end(); ++it
) {
288 filter_string
.append(*it
+ " ");
290 return filter_string
;
293 void SelectFileDialogImplKDE::CallKDialogOutput(const KDialogParams
& params
) {
294 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
295 CommandLine::StringVector cmd_vector
;
296 cmd_vector
.push_back(kKdialogBinary
);
297 CommandLine
command_line(cmd_vector
);
298 GetKDialogCommandLine(params
.type
, params
.title
, params
.default_path
,
299 params
.parent
, params
.file_operation
,
300 params
.multiple_selection
, &command_line
);
303 // Get output from KDialog
304 base::GetAppOutputWithExitCode(command_line
, &output
, &exit_code
);
306 output
.erase(output
.size() - 1);
308 // Now the dialog is no longer showing, but we can't erase its parent from the
309 // parent set yet because we're on the FILE thread.
310 BrowserThread::PostTask(
311 BrowserThread::UI
, FROM_HERE
,
312 base::Bind(params
.callback
, this, params
.parent
, output
, exit_code
,
313 params
.kdialog_params
));
316 void SelectFileDialogImplKDE::GetKDialogCommandLine(const std::string
& type
,
317 const std::string
& title
, const base::FilePath
& path
,
318 XID parent
, bool file_operation
, bool multiple_selection
,
319 CommandLine
* command_line
) {
322 // Attach to the current Chrome window.
323 if (parent
!= None
) {
324 command_line
->AppendSwitchNative(
325 desktop_
== base::nix::DESKTOP_ENVIRONMENT_KDE3
?
326 "--embed" : "--attach",
327 base::IntToString(parent
));
330 // Set the correct title for the dialog.
332 command_line
->AppendSwitchNative("--title", title
);
333 // Enable multiple file selection if we need to.
334 if (multiple_selection
) {
335 command_line
->AppendSwitch("--multiple");
336 command_line
->AppendSwitch("--separate-output");
338 command_line
->AppendSwitch(type
);
339 // The path should never be empty. If it is, set it to PWD.
341 command_line
->AppendArgPath(base::FilePath("."));
343 command_line
->AppendArgPath(path
);
344 // Depending on the type of the operation we need, get the path to the
345 // file/folder and set up mime type filters.
347 command_line
->AppendArg(GetMimeTypeFilterString());
348 VLOG(1) << "KDialog command line: " << command_line
->GetCommandLineString();
351 void SelectFileDialogImplKDE::FileSelected(const base::FilePath
& path
,
353 if (type_
== SELECT_SAVEAS_FILE
)
354 *last_saved_path_
= path
.DirName();
355 else if (type_
== SELECT_OPEN_FILE
)
356 *last_opened_path_
= path
.DirName();
357 else if (type_
== SELECT_FOLDER
)
358 *last_opened_path_
= path
;
361 if (listener_
) { // What does the filter index actually do?
362 // TODO(dfilimon): Get a reasonable index value from somewhere.
363 listener_
->FileSelected(path
, 1, params
);
367 void SelectFileDialogImplKDE::MultiFilesSelected(
368 const std::vector
<base::FilePath
>& files
, void* params
) {
369 *last_opened_path_
= files
[0].DirName();
371 listener_
->MultiFilesSelected(files
, params
);
374 void SelectFileDialogImplKDE::FileNotSelected(void* params
) {
376 listener_
->FileSelectionCanceled(params
);
379 void SelectFileDialogImplKDE::CreateSelectFolderDialog(
380 Type type
, const std::string
& title
, const base::FilePath
& default_path
,
381 XID parent
, void *params
) {
382 int title_message_id
= (type
== SELECT_UPLOAD_FOLDER
)
383 ? IDS_SELECT_UPLOAD_FOLDER_DIALOG_TITLE
384 : IDS_SELECT_FOLDER_DIALOG_TITLE
;
385 BrowserThread::PostTask(
386 BrowserThread::FILE, FROM_HERE
,
388 &SelectFileDialogImplKDE::CallKDialogOutput
,
391 "--getexistingdirectory",
392 GetTitle(title
, title_message_id
),
393 default_path
.empty() ? *last_opened_path_
: default_path
,
394 parent
, false, false, params
,
395 &SelectFileDialogImplKDE::OnSelectSingleFolderDialogResponse
)));
398 void SelectFileDialogImplKDE::CreateFileOpenDialog(
399 const std::string
& title
, const base::FilePath
& default_path
,
400 XID parent
, void* params
) {
401 BrowserThread::PostTask(
402 BrowserThread::FILE, FROM_HERE
,
404 &SelectFileDialogImplKDE::CallKDialogOutput
,
408 GetTitle(title
, IDS_OPEN_FILE_DIALOG_TITLE
),
409 default_path
.empty() ? *last_opened_path_
: default_path
,
410 parent
, true, false, params
,
411 &SelectFileDialogImplKDE::OnSelectSingleFileDialogResponse
)));
414 void SelectFileDialogImplKDE::CreateMultiFileOpenDialog(
415 const std::string
& title
, const base::FilePath
& default_path
,
416 XID parent
, void* params
) {
417 BrowserThread::PostTask(
418 BrowserThread::FILE, FROM_HERE
,
420 &SelectFileDialogImplKDE::CallKDialogOutput
,
424 GetTitle(title
, IDS_OPEN_FILES_DIALOG_TITLE
),
425 default_path
.empty() ? *last_opened_path_
: default_path
,
426 parent
, true, true, params
,
427 &SelectFileDialogImplKDE::OnSelectMultiFileDialogResponse
)));
430 void SelectFileDialogImplKDE::CreateSaveAsDialog(
431 const std::string
& title
, const base::FilePath
& default_path
,
432 XID parent
, void* params
) {
433 BrowserThread::PostTask(
434 BrowserThread::FILE, FROM_HERE
,
436 &SelectFileDialogImplKDE::CallKDialogOutput
,
440 GetTitle(title
, IDS_SAVE_AS_DIALOG_TITLE
),
441 default_path
.empty() ? *last_saved_path_
: default_path
,
442 parent
, true, false, params
,
443 &SelectFileDialogImplKDE::OnSelectSingleFileDialogResponse
)));
446 void SelectFileDialogImplKDE::SelectSingleFileHelper(const std::string
& output
,
447 int exit_code
, void* params
, bool allow_folder
) {
448 VLOG(1) << "[kdialog] SingleFileResponse: " << output
;
449 if (exit_code
!= 0 || output
.empty()) {
450 FileNotSelected(params
);
454 base::FilePath
path(output
);
456 FileSelected(path
, params
);
460 if (CallDirectoryExistsOnUIThread(path
))
461 FileNotSelected(params
);
463 FileSelected(path
, params
);
466 void SelectFileDialogImplKDE::OnSelectSingleFileDialogResponse(
467 XID parent
, const std::string
& output
, int exit_code
, void* params
) {
468 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
469 parents_
.erase(parent
);
470 SelectSingleFileHelper(output
, exit_code
, params
, false);
473 void SelectFileDialogImplKDE::OnSelectSingleFolderDialogResponse(
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
, true);
480 void SelectFileDialogImplKDE::OnSelectMultiFileDialogResponse(
481 XID parent
, const std::string
& output
, int exit_code
, void* params
) {
482 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
483 VLOG(1) << "[kdialog] MultiFileResponse: " << output
;
485 parents_
.erase(parent
);
487 if (exit_code
!= 0 || output
.empty()) {
488 FileNotSelected(params
);
492 std::vector
<std::string
> filenames
;
493 Tokenize(output
, "\n", &filenames
);
494 std::vector
<base::FilePath
> filenames_fp
;
495 for (std::vector
<std::string
>::iterator iter
= filenames
.begin();
496 iter
!= filenames
.end(); ++iter
) {
497 base::FilePath
path(*iter
);
498 if (CallDirectoryExistsOnUIThread(path
))
500 filenames_fp
.push_back(path
);
503 if (filenames_fp
.empty()) {
504 FileNotSelected(params
);
507 MultiFilesSelected(filenames_fp
, params
);
510 } // namespace libgtk2ui