select_file_dialog_impl_kde.cc revision f2477e01787aa58f445919b809d89e252beef54f
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 <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/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" 19 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" 25 26// These conflict with base/tracked_objects.h, so need to come last. 27#include <gdk/gdkx.h> 28#include <gtk/gtk.h> 29 30using content::BrowserThread; 31 32namespace { 33 34std::string GetTitle(const std::string& title, int message_id) { 35 return title.empty() ? l10n_util::GetStringUTF8(message_id) : title; 36} 37 38const char kKdialogBinary[] = "kdialog"; 39 40} // namespace 41 42namespace libgtk2ui { 43 44// Implementation of SelectFileDialog that shows a KDE common dialog for 45// choosing a file or folder. This acts as a modal dialog. 46class SelectFileDialogImplKDE : public SelectFileDialogImpl { 47 public: 48 SelectFileDialogImplKDE(Listener* listener, 49 ui::SelectFilePolicy* policy, 50 base::nix::DesktopEnvironment desktop); 51 52 protected: 53 virtual ~SelectFileDialogImplKDE(); 54 55 // SelectFileDialog implementation. 56 // |params| is user data we pass back via the Listener interface. 57 virtual void SelectFileImpl( 58 Type type, 59 const string16& title, 60 const base::FilePath& default_path, 61 const FileTypeInfo* file_types, 62 int file_type_index, 63 const base::FilePath::StringType& default_extension, 64 gfx::NativeWindow owning_window, 65 void* params) OVERRIDE; 66 67 private: 68 virtual bool HasMultipleFileTypeChoicesImpl() OVERRIDE; 69 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, 74 void* kdialog_params, 75 void (SelectFileDialogImplKDE::*callback)(const std::string&, 76 int, void*)) 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) { 81 } 82 83 std::string type; 84 std::string title; 85 base::FilePath default_path; 86 gfx::NativeWindow parent; 87 bool file_operation; 88 bool multiple_selection; 89 void* kdialog_params; 90 void (SelectFileDialogImplKDE::*callback)(const std::string&, int, void*); 91 }; 92 93 // Get the filters from |file_types_| and concatenate them into 94 // |filter_string|. 95 std::string GetMimeTypeFilterString(); 96 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); 101 102 // Call KDialog on the FILE thread and post results back to the UI thread. 103 void CallKDialogOutput(const KDialogParams& params); 104 105 // Notifies the listener that a single file was chosen. 106 void FileSelected(const base::FilePath& path, void* params); 107 108 // Notifies the listener that multiple files were chosen. 109 void MultiFilesSelected(const std::vector<base::FilePath>& files, 110 void* params); 111 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); 116 117 void CreateSelectFolderDialog(Type type, 118 const std::string& title, 119 const base::FilePath& default_path, 120 gfx::NativeWindow parent, void* params); 121 122 void CreateFileOpenDialog(const std::string& title, 123 const base::FilePath& default_path, 124 gfx::NativeWindow parent, void* params); 125 126 void CreateMultiFileOpenDialog(const std::string& title, 127 const base::FilePath& default_path, 128 gfx::NativeWindow parent, void* params); 129 130 void CreateSaveAsDialog(const std::string& title, 131 const base::FilePath& default_path, 132 gfx::NativeWindow parent, void* params); 133 134 // Common function for OnSelectSingleFileDialogResponse and 135 // OnSelectSingleFolderDialogResponse. 136 void SelectSingleFileHelper(const std::string& output, int exit_code, 137 void* params, bool allow_folder); 138 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); 145 146 // Should be either DESKTOP_ENVIRONMENT_KDE3 or DESKTOP_ENVIRONMENT_KDE4. 147 base::nix::DesktopEnvironment desktop_; 148 149 DISALLOW_COPY_AND_ASSIGN(SelectFileDialogImplKDE); 150}; 151 152// static 153bool 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; 157 158 CommandLine::StringVector cmd_vector; 159 cmd_vector.push_back(kKdialogBinary); 160 cmd_vector.push_back("--version"); 161 CommandLine command_line(cmd_vector); 162 std::string dummy; 163 return base::GetAppOutput(command_line, &dummy); 164} 165 166// static 167SelectFileDialogImpl* SelectFileDialogImpl::NewSelectFileDialogImplKDE( 168 Listener* listener, 169 ui::SelectFilePolicy* policy, 170 base::nix::DesktopEnvironment desktop) { 171 return new SelectFileDialogImplKDE(listener, policy, desktop); 172} 173 174SelectFileDialogImplKDE::SelectFileDialogImplKDE( 175 Listener* listener, 176 ui::SelectFilePolicy* policy, 177 base::nix::DesktopEnvironment desktop) 178 : SelectFileDialogImpl(listener, policy), 179 desktop_(desktop) { 180 DCHECK(desktop_ == base::nix::DESKTOP_ENVIRONMENT_KDE3 || 181 desktop_ == base::nix::DESKTOP_ENVIRONMENT_KDE4); 182} 183 184SelectFileDialogImplKDE::~SelectFileDialogImplKDE() { 185} 186 187// We ignore |default_extension|. 188void SelectFileDialogImplKDE::SelectFileImpl( 189 Type type, 190 const string16& title, 191 const base::FilePath& default_path, 192 const FileTypeInfo* file_types, 193 int file_type_index, 194 const base::FilePath::StringType& default_extension, 195 gfx::NativeWindow owning_window, 196 void* params) { 197 type_ = type; 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) 201 if (owning_window) 202 parents_.insert(owning_window); 203 204 std::string title_string = UTF16ToUTF8(title); 205 206 file_type_index_ = file_type_index; 207 if (file_types) 208 file_types_ = *file_types; 209 else 210 file_types_.include_all_files = true; 211 212 switch (type) { 213 case SELECT_FOLDER: 214 case SELECT_UPLOAD_FOLDER: 215 CreateSelectFolderDialog(type, title_string, default_path, 216 owning_window, params); 217 return; 218 case SELECT_OPEN_FILE: 219 CreateFileOpenDialog(title_string, default_path, owning_window, 220 params); 221 return; 222 case SELECT_OPEN_MULTI_FILE: 223 CreateMultiFileOpenDialog(title_string, default_path, 224 owning_window, params); 225 return; 226 case SELECT_SAVEAS_FILE: 227 CreateSaveAsDialog(title_string, default_path, owning_window, 228 params); 229 return; 230 default: 231 NOTREACHED(); 232 return; 233 } 234} 235 236bool SelectFileDialogImplKDE::HasMultipleFileTypeChoicesImpl() { 237 return file_types_.extensions.size() > 1; 238} 239 240std::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); 251 } 252 } 253 } 254 // Add the *.* filter, but only if we have added other filters (otherwise it 255 // is implied). 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 + " "); 263 } 264 return filter_string; 265} 266 267void 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); 275 std::string output; 276 int exit_code; 277 // Get output from KDialog 278 base::GetAppOutputWithExitCode(command_line, &output, &exit_code); 279 if (!output.empty()) 280 output.erase(output.size() - 1); 281 282 // Now the dialog is no longer showing. We can erase its parent from the 283 // parent set. 284 // TODO(erg): FIX THIS. 285 // std::set<GtkWindow*>::iterator iter = parents_.find(params.parent); 286 // if (iter != parents_.end()) 287 // parents_.erase(iter); 288 289 BrowserThread::PostTask( 290 BrowserThread::UI, FROM_HERE, 291 base::Bind(params.callback, this, output, exit_code, 292 params.kdialog_params)); 293} 294 295void 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) { 299 CHECK(command_line); 300 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. 307 if (!title.empty()) 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"); 313 } 314 command_line->AppendSwitch(type); 315 // The path should never be empty. If it is, set it to PWD. 316 if (path.empty()) 317 command_line->AppendArgPath(base::FilePath(".")); 318 else 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. 322 if (file_operation) 323 command_line->AppendArg(GetMimeTypeFilterString()); 324 VLOG(1) << "KDialog command line: " << command_line->GetCommandLineString(); 325} 326 327void SelectFileDialogImplKDE::FileSelected(const base::FilePath& path, 328 void* params) { 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; 335 else 336 NOTREACHED(); 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); 340 } 341} 342 343void SelectFileDialogImplKDE::MultiFilesSelected( 344 const std::vector<base::FilePath>& files, void* params) { 345 *last_opened_path_ = files[0].DirName(); 346 if (listener_) 347 listener_->MultiFilesSelected(files, params); 348} 349 350void SelectFileDialogImplKDE::FileNotSelected(void* params) { 351 if (listener_) 352 listener_->FileSelectionCanceled(params); 353} 354 355void 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, 363 base::Bind( 364 &SelectFileDialogImplKDE::CallKDialogOutput, 365 this, 366 KDialogParams( 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))); 372} 373 374void 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, 379 base::Bind( 380 &SelectFileDialogImplKDE::CallKDialogOutput, 381 this, 382 KDialogParams( 383 "--getopenfilename", 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))); 388} 389 390void 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, 395 base::Bind( 396 &SelectFileDialogImplKDE::CallKDialogOutput, 397 this, 398 KDialogParams( 399 "--getopenfilename", 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))); 404} 405 406void 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, 411 base::Bind( 412 &SelectFileDialogImplKDE::CallKDialogOutput, 413 this, 414 KDialogParams( 415 "--getsavefilename", 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))); 420} 421 422void 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); 427 return; 428 } 429 430 base::FilePath path(output); 431 if (allow_folder) { 432 FileSelected(path, params); 433 return; 434 } 435 436 if (CallDirectoryExistsOnUIThread(path)) 437 FileNotSelected(params); 438 else 439 FileSelected(path, params); 440} 441 442void 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); 446} 447 448void 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); 452} 453 454void SelectFileDialogImplKDE::OnSelectMultiFileDialogResponse( 455 const std::string& output, int exit_code, void* params) { 456 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 457 VLOG(1) << "[kdialog] MultiFileResponse: " << output; 458 459 if (exit_code != 0 || output.empty()) { 460 FileNotSelected(params); 461 return; 462 } 463 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)) 471 continue; 472 filenames_fp.push_back(path); 473 } 474 475 if (filenames_fp.empty()) { 476 FileNotSelected(params); 477 return; 478 } 479 MultiFilesSelected(filenames_fp, params); 480} 481 482} // namespace libgtk2ui 483