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.
8 #include "base/bind_helpers.h"
9 #include "base/command_line.h"
10 #include "base/logging.h"
11 #include "base/nix/mime_util_xdg.h"
12 #include "base/nix/xdg_util.h"
13 #include "base/process/launch.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/strings/string_util.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "base/threading/thread_restrictions.h"
18 #include "chrome/browser/ui/libgtk2ui/select_file_dialog_impl.h"
20 #include "content/public/browser/browser_thread.h"
21 #include "grit/generated_resources.h"
22 #include "grit/ui_strings.h"
23 #include "ui/aura/root_window.h"
24 #include "ui/base/l10n/l10n_util.h"
26 // These conflict with base/tracked_objects.h, so need to come last.
30 using content::BrowserThread
;
34 std::string
GetTitle(const std::string
& title
, int message_id
) {
35 return title
.empty() ? l10n_util::GetStringUTF8(message_id
) : title
;
38 const char kKdialogBinary
[] = "kdialog";
44 // Implementation of SelectFileDialog that shows a KDE common dialog for
45 // choosing a file or folder. This acts as a modal dialog.
46 class SelectFileDialogImplKDE
: public SelectFileDialogImpl
{
48 SelectFileDialogImplKDE(Listener
* listener
,
49 ui::SelectFilePolicy
* policy
,
50 base::nix::DesktopEnvironment desktop
);
53 virtual ~SelectFileDialogImplKDE();
55 // SelectFileDialog implementation.
56 // |params| is user data we pass back via the Listener interface.
57 virtual void SelectFileImpl(
59 const base::string16
& title
,
60 const base::FilePath
& default_path
,
61 const FileTypeInfo
* file_types
,
63 const base::FilePath::StringType
& default_extension
,
64 gfx::NativeWindow owning_window
,
65 void* params
) OVERRIDE
;
68 virtual bool HasMultipleFileTypeChoicesImpl() OVERRIDE
;
70 struct KDialogParams
{
71 KDialogParams(const std::string
& type
, const std::string
& title
,
72 const base::FilePath
& default_path
, gfx::NativeWindow parent
,
73 bool file_operation
, bool multiple_selection
,
75 void (SelectFileDialogImplKDE::*callback
)(const std::string
&,
77 : type(type
), title(title
), default_path(default_path
), parent(parent
),
78 file_operation(file_operation
),
79 multiple_selection(multiple_selection
),
80 kdialog_params(kdialog_params
), callback(callback
) {
85 base::FilePath default_path
;
86 gfx::NativeWindow parent
;
88 bool multiple_selection
;
90 void (SelectFileDialogImplKDE::*callback
)(const std::string
&, int, void*);
93 // Get the filters from |file_types_| and concatenate them into
95 std::string
GetMimeTypeFilterString();
97 // Get KDialog command line representing the Argv array for KDialog.
98 void GetKDialogCommandLine(const std::string
& type
, const std::string
& title
,
99 const base::FilePath
& default_path
, gfx::NativeWindow parent
,
100 bool file_operation
, bool multiple_selection
, CommandLine
* command_line
);
102 // Call KDialog on the FILE thread and post results back to the UI thread.
103 void CallKDialogOutput(const KDialogParams
& params
);
105 // Notifies the listener that a single file was chosen.
106 void FileSelected(const base::FilePath
& path
, void* params
);
108 // Notifies the listener that multiple files were chosen.
109 void MultiFilesSelected(const std::vector
<base::FilePath
>& files
,
112 // Notifies the listener that no file was chosen (the action was canceled).
113 // Dialog is passed so we can find that |params| pointer that was passed to
114 // us when we were told to show the dialog.
115 void FileNotSelected(void *params
);
117 void CreateSelectFolderDialog(Type type
,
118 const std::string
& title
,
119 const base::FilePath
& default_path
,
120 gfx::NativeWindow parent
, void* params
);
122 void CreateFileOpenDialog(const std::string
& title
,
123 const base::FilePath
& default_path
,
124 gfx::NativeWindow parent
, void* params
);
126 void CreateMultiFileOpenDialog(const std::string
& title
,
127 const base::FilePath
& default_path
,
128 gfx::NativeWindow parent
, void* params
);
130 void CreateSaveAsDialog(const std::string
& title
,
131 const base::FilePath
& default_path
,
132 gfx::NativeWindow parent
, void* params
);
134 // Common function for OnSelectSingleFileDialogResponse and
135 // OnSelectSingleFolderDialogResponse.
136 void SelectSingleFileHelper(const std::string
& output
, int exit_code
,
137 void* params
, bool allow_folder
);
139 void OnSelectSingleFileDialogResponse(const std::string
& output
,
140 int exit_code
, void* params
);
141 void OnSelectMultiFileDialogResponse(const std::string
& output
,
142 int exit_code
, void* params
);
143 void OnSelectSingleFolderDialogResponse(const std::string
& output
,
144 int exit_code
, void* params
);
146 // Should be either DESKTOP_ENVIRONMENT_KDE3 or DESKTOP_ENVIRONMENT_KDE4.
147 base::nix::DesktopEnvironment desktop_
;
149 DISALLOW_COPY_AND_ASSIGN(SelectFileDialogImplKDE
);
153 bool SelectFileDialogImpl::CheckKDEDialogWorksOnUIThread() {
154 // No choice. UI thread can't continue without an answer here. Fortunately we
155 // only do this once, the first time a file dialog is displayed.
156 base::ThreadRestrictions::ScopedAllowIO allow_io
;
158 CommandLine::StringVector cmd_vector
;
159 cmd_vector
.push_back(kKdialogBinary
);
160 cmd_vector
.push_back("--version");
161 CommandLine
command_line(cmd_vector
);
163 return base::GetAppOutput(command_line
, &dummy
);
167 SelectFileDialogImpl
* SelectFileDialogImpl::NewSelectFileDialogImplKDE(
169 ui::SelectFilePolicy
* policy
,
170 base::nix::DesktopEnvironment desktop
) {
171 return new SelectFileDialogImplKDE(listener
, policy
, desktop
);
174 SelectFileDialogImplKDE::SelectFileDialogImplKDE(
176 ui::SelectFilePolicy
* policy
,
177 base::nix::DesktopEnvironment desktop
)
178 : SelectFileDialogImpl(listener
, policy
),
180 DCHECK(desktop_
== base::nix::DESKTOP_ENVIRONMENT_KDE3
||
181 desktop_
== base::nix::DESKTOP_ENVIRONMENT_KDE4
);
184 SelectFileDialogImplKDE::~SelectFileDialogImplKDE() {
187 // We ignore |default_extension|.
188 void SelectFileDialogImplKDE::SelectFileImpl(
190 const base::string16
& title
,
191 const base::FilePath
& default_path
,
192 const FileTypeInfo
* file_types
,
194 const base::FilePath::StringType
& default_extension
,
195 gfx::NativeWindow owning_window
,
198 // |owning_window| can be null when user right-clicks on a downloadable item
199 // and chooses 'Open Link in New Tab' when 'Ask where to save each file
200 // before downloading.' preference is turned on. (http://crbug.com/29213)
202 parents_
.insert(owning_window
);
204 std::string title_string
= base::UTF16ToUTF8(title
);
206 file_type_index_
= file_type_index
;
208 file_types_
= *file_types
;
210 file_types_
.include_all_files
= true;
214 case SELECT_UPLOAD_FOLDER
:
215 CreateSelectFolderDialog(type
, title_string
, default_path
,
216 owning_window
, params
);
218 case SELECT_OPEN_FILE
:
219 CreateFileOpenDialog(title_string
, default_path
, owning_window
,
222 case SELECT_OPEN_MULTI_FILE
:
223 CreateMultiFileOpenDialog(title_string
, default_path
,
224 owning_window
, params
);
226 case SELECT_SAVEAS_FILE
:
227 CreateSaveAsDialog(title_string
, default_path
, owning_window
,
236 bool SelectFileDialogImplKDE::HasMultipleFileTypeChoicesImpl() {
237 return file_types_
.extensions
.size() > 1;
240 std::string
SelectFileDialogImplKDE::GetMimeTypeFilterString() {
241 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
242 std::string filter_string
;
243 // We need a filter set because the same mime type can appear multiple times.
244 std::set
<std::string
> filter_set
;
245 for (size_t i
= 0; i
< file_types_
.extensions
.size(); ++i
) {
246 for (size_t j
= 0; j
< file_types_
.extensions
[i
].size(); ++j
) {
247 if (!file_types_
.extensions
[i
][j
].empty()) {
248 std::string mime_type
= base::nix::GetFileMimeType(base::FilePath(
249 "name").ReplaceExtension(file_types_
.extensions
[i
][j
]));
250 filter_set
.insert(mime_type
);
254 // Add the *.* filter, but only if we have added other filters (otherwise it
256 if (file_types_
.include_all_files
&& !file_types_
.extensions
.empty())
257 filter_set
.insert("application/octet-stream");
258 // Create the final output string.
259 filter_string
.clear();
260 for (std::set
<std::string
>::iterator it
= filter_set
.begin();
261 it
!= filter_set
.end(); ++it
) {
262 filter_string
.append(*it
+ " ");
264 return filter_string
;
267 void SelectFileDialogImplKDE::CallKDialogOutput(const KDialogParams
& params
) {
268 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
269 CommandLine::StringVector cmd_vector
;
270 cmd_vector
.push_back(kKdialogBinary
);
271 CommandLine
command_line(cmd_vector
);
272 GetKDialogCommandLine(params
.type
, params
.title
, params
.default_path
,
273 params
.parent
, params
.file_operation
,
274 params
.multiple_selection
, &command_line
);
277 // Get output from KDialog
278 base::GetAppOutputWithExitCode(command_line
, &output
, &exit_code
);
280 output
.erase(output
.size() - 1);
282 // Now the dialog is no longer showing. We can erase its parent from the
284 // TODO(erg): FIX THIS.
285 // std::set<GtkWindow*>::iterator iter = parents_.find(params.parent);
286 // if (iter != parents_.end())
287 // parents_.erase(iter);
289 BrowserThread::PostTask(
290 BrowserThread::UI
, FROM_HERE
,
291 base::Bind(params
.callback
, this, output
, exit_code
,
292 params
.kdialog_params
));
295 void SelectFileDialogImplKDE::GetKDialogCommandLine(const std::string
& type
,
296 const std::string
& title
, const base::FilePath
& path
,
297 gfx::NativeWindow parent
, bool file_operation
, bool multiple_selection
,
298 CommandLine
* command_line
) {
301 // Attach to the current Chrome window.
302 int window_id
= parent
->GetDispatcher()->host()->GetAcceleratedWidget();
303 command_line
->AppendSwitchNative(
304 desktop_
== base::nix::DESKTOP_ENVIRONMENT_KDE3
? "--embed" : "--attach",
305 base::IntToString(window_id
));
306 // Set the correct title for the dialog.
308 command_line
->AppendSwitchNative("--title", title
);
309 // Enable multiple file selection if we need to.
310 if (multiple_selection
) {
311 command_line
->AppendSwitch("--multiple");
312 command_line
->AppendSwitch("--separate-output");
314 command_line
->AppendSwitch(type
);
315 // The path should never be empty. If it is, set it to PWD.
317 command_line
->AppendArgPath(base::FilePath("."));
319 command_line
->AppendArgPath(path
);
320 // Depending on the type of the operation we need, get the path to the
321 // file/folder and set up mime type filters.
323 command_line
->AppendArg(GetMimeTypeFilterString());
324 VLOG(1) << "KDialog command line: " << command_line
->GetCommandLineString();
327 void SelectFileDialogImplKDE::FileSelected(const base::FilePath
& path
,
329 if (type_
== SELECT_SAVEAS_FILE
)
330 *last_saved_path_
= path
.DirName();
331 else if (type_
== SELECT_OPEN_FILE
)
332 *last_opened_path_
= path
.DirName();
333 else if (type_
== SELECT_FOLDER
)
334 *last_opened_path_
= path
;
337 if (listener_
) { // What does the filter index actually do?
338 // TODO(dfilimon): Get a reasonable index value from somewhere.
339 listener_
->FileSelected(path
, 1, params
);
343 void SelectFileDialogImplKDE::MultiFilesSelected(
344 const std::vector
<base::FilePath
>& files
, void* params
) {
345 *last_opened_path_
= files
[0].DirName();
347 listener_
->MultiFilesSelected(files
, params
);
350 void SelectFileDialogImplKDE::FileNotSelected(void* params
) {
352 listener_
->FileSelectionCanceled(params
);
355 void SelectFileDialogImplKDE::CreateSelectFolderDialog(
356 Type type
, const std::string
& title
, const base::FilePath
& default_path
,
357 gfx::NativeWindow parent
, void *params
) {
358 int title_message_id
= (type
== SELECT_UPLOAD_FOLDER
)
359 ? IDS_SELECT_UPLOAD_FOLDER_DIALOG_TITLE
360 : IDS_SELECT_FOLDER_DIALOG_TITLE
;
361 BrowserThread::PostTask(
362 BrowserThread::FILE, FROM_HERE
,
364 &SelectFileDialogImplKDE::CallKDialogOutput
,
367 "--getexistingdirectory",
368 GetTitle(title
, title_message_id
),
369 default_path
.empty() ? *last_opened_path_
: default_path
,
370 parent
, false, false, params
,
371 &SelectFileDialogImplKDE::OnSelectSingleFolderDialogResponse
)));
374 void SelectFileDialogImplKDE::CreateFileOpenDialog(
375 const std::string
& title
, const base::FilePath
& default_path
,
376 gfx::NativeWindow parent
, void* params
) {
377 BrowserThread::PostTask(
378 BrowserThread::FILE, FROM_HERE
,
380 &SelectFileDialogImplKDE::CallKDialogOutput
,
384 GetTitle(title
, IDS_OPEN_FILE_DIALOG_TITLE
),
385 default_path
.empty() ? *last_opened_path_
: default_path
,
386 parent
, true, false, params
,
387 &SelectFileDialogImplKDE::OnSelectSingleFileDialogResponse
)));
390 void SelectFileDialogImplKDE::CreateMultiFileOpenDialog(
391 const std::string
& title
, const base::FilePath
& default_path
,
392 gfx::NativeWindow parent
, void* params
) {
393 BrowserThread::PostTask(
394 BrowserThread::FILE, FROM_HERE
,
396 &SelectFileDialogImplKDE::CallKDialogOutput
,
400 GetTitle(title
, IDS_OPEN_FILES_DIALOG_TITLE
),
401 default_path
.empty() ? *last_opened_path_
: default_path
,
402 parent
, true, true, params
,
403 &SelectFileDialogImplKDE::OnSelectMultiFileDialogResponse
)));
406 void SelectFileDialogImplKDE::CreateSaveAsDialog(
407 const std::string
& title
, const base::FilePath
& default_path
,
408 gfx::NativeWindow parent
, void* params
) {
409 BrowserThread::PostTask(
410 BrowserThread::FILE, FROM_HERE
,
412 &SelectFileDialogImplKDE::CallKDialogOutput
,
416 GetTitle(title
, IDS_SAVE_AS_DIALOG_TITLE
),
417 default_path
.empty() ? *last_saved_path_
: default_path
,
418 parent
, true, false, params
,
419 &SelectFileDialogImplKDE::OnSelectSingleFileDialogResponse
)));
422 void SelectFileDialogImplKDE::SelectSingleFileHelper(const std::string
& output
,
423 int exit_code
, void* params
, bool allow_folder
) {
424 VLOG(1) << "[kdialog] SingleFileResponse: " << output
;
425 if (exit_code
!= 0 || output
.empty()) {
426 FileNotSelected(params
);
430 base::FilePath
path(output
);
432 FileSelected(path
, params
);
436 if (CallDirectoryExistsOnUIThread(path
))
437 FileNotSelected(params
);
439 FileSelected(path
, params
);
442 void SelectFileDialogImplKDE::OnSelectSingleFileDialogResponse(
443 const std::string
& output
, int exit_code
, void* params
) {
444 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
445 SelectSingleFileHelper(output
, exit_code
, params
, false);
448 void SelectFileDialogImplKDE::OnSelectSingleFolderDialogResponse(
449 const std::string
& output
, int exit_code
, void* params
) {
450 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
451 SelectSingleFileHelper(output
, exit_code
, params
, true);
454 void SelectFileDialogImplKDE::OnSelectMultiFileDialogResponse(
455 const std::string
& output
, int exit_code
, void* params
) {
456 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
457 VLOG(1) << "[kdialog] MultiFileResponse: " << output
;
459 if (exit_code
!= 0 || output
.empty()) {
460 FileNotSelected(params
);
464 std::vector
<std::string
> filenames
;
465 Tokenize(output
, "\n", &filenames
);
466 std::vector
<base::FilePath
> filenames_fp
;
467 for (std::vector
<std::string
>::iterator iter
= filenames
.begin();
468 iter
!= filenames
.end(); ++iter
) {
469 base::FilePath
path(*iter
);
470 if (CallDirectoryExistsOnUIThread(path
))
472 filenames_fp
.push_back(path
);
475 if (filenames_fp
.empty()) {
476 FileNotSelected(params
);
479 MultiFilesSelected(filenames_fp
, params
);
482 } // namespace libgtk2ui