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"
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.
31 using content::BrowserThread
;
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";
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
{
49 SelectFileDialogImplKDE(Listener
* listener
,
50 ui::SelectFilePolicy
* policy
,
51 base::nix::DesktopEnvironment desktop
);
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
,
66 const base::FilePath::StringType
& default_extension
,
67 gfx::NativeWindow owning_window
,
68 void* params
) override
;
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
,
78 void (SelectFileDialogImplKDE::*callback
)(XID
,
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
) {
89 base::FilePath default_path
;
92 bool multiple_selection
;
94 void (SelectFileDialogImplKDE::*callback
)(XID
, const std::string
&,
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
,
104 const std::string
& title
,
105 const base::FilePath
& default_path
,
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
,
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
);
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
);
179 return base::GetAppOutput(command_line
, &dummy
);
183 SelectFileDialogImpl
* SelectFileDialogImpl::NewSelectFileDialogImplKDE(
185 ui::SelectFilePolicy
* policy
,
186 base::nix::DesktopEnvironment desktop
) {
187 return new SelectFileDialogImplKDE(listener
, policy
, desktop
);
190 SelectFileDialogImplKDE::SelectFileDialogImplKDE(
192 ui::SelectFilePolicy
* policy
,
193 base::nix::DesktopEnvironment desktop
)
194 : SelectFileDialogImpl(listener
, policy
),
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();
213 // We ignore |default_extension|.
214 void SelectFileDialogImplKDE::SelectFileImpl(
216 const base::string16
& title
,
217 const base::FilePath
& default_path
,
218 const FileTypeInfo
* file_types
,
220 const base::FilePath::StringType
& default_extension
,
221 gfx::NativeWindow owning_window
,
223 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
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
;
239 file_types_
= *file_types
;
241 file_types_
.include_all_files
= true;
245 case SELECT_UPLOAD_FOLDER
:
246 CreateSelectFolderDialog(type
, title_string
, default_path
,
249 case SELECT_OPEN_FILE
:
250 CreateFileOpenDialog(title_string
, default_path
, window_xid
, params
);
252 case SELECT_OPEN_MULTI_FILE
:
253 CreateMultiFileOpenDialog(title_string
, default_path
, window_xid
, params
);
255 case SELECT_SAVEAS_FILE
:
256 CreateSaveAsDialog(title_string
, default_path
, window_xid
, params
);
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
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
);
305 // Get output from KDialog
306 base::GetAppOutputWithExitCode(command_line
, &output
, &exit_code
);
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
,
324 bool multiple_selection
,
325 base::CommandLine
* 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.
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.
347 command_line
->AppendArgPath(base::FilePath("."));
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.
353 command_line
->AppendArg(GetMimeTypeFilterString());
354 VLOG(1) << "KDialog command line: " << command_line
->GetCommandLineString();
357 void SelectFileDialogImplKDE::FileSelected(const base::FilePath
& path
,
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
;
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();
377 listener_
->MultiFilesSelected(files
, params
);
380 void SelectFileDialogImplKDE::FileNotSelected(void* params
) {
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
,
394 &SelectFileDialogImplKDE::CallKDialogOutput
,
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
,
410 &SelectFileDialogImplKDE::CallKDialogOutput
,
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
,
426 &SelectFileDialogImplKDE::CallKDialogOutput
,
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
,
442 &SelectFileDialogImplKDE::CallKDialogOutput
,
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
);
460 base::FilePath
path(output
);
462 FileSelected(path
, params
);
466 if (CallDirectoryExistsOnUIThread(path
))
467 FileNotSelected(params
);
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
);
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
))
506 filenames_fp
.push_back(path
);
509 if (filenames_fp
.empty()) {
510 FileNotSelected(params
);
513 MultiFilesSelected(filenames_fp
, params
);
516 } // namespace libgtk2ui