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