select_file_dialog_linux_kde.cc 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612
  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. #include <cstddef>
  5. #include <memory>
  6. #include <set>
  7. #include "base/bind.h"
  8. #include "base/callback_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_split.h"
  16. #include "base/strings/string_util.h"
  17. #include "base/strings/utf_string_conversions.h"
  18. #include "base/task/task_traits.h"
  19. #include "base/task/thread_pool.h"
  20. #include "base/threading/thread_restrictions.h"
  21. #include "base/version.h"
  22. #include "ui/aura/window_tree_host.h"
  23. #include "ui/base/l10n/l10n_util.h"
  24. #include "ui/shell_dialogs/select_file_dialog_linux.h"
  25. #include "ui/strings/grit/ui_strings.h"
  26. namespace {
  27. std::string GetTitle(const std::string& title, int message_id) {
  28. return title.empty() ? l10n_util::GetStringUTF8(message_id) : title;
  29. }
  30. const char kKdialogBinary[] = "kdialog";
  31. } // namespace
  32. namespace ui {
  33. // Implementation of SelectFileDialog that shows a KDE common dialog for
  34. // choosing a file or folder. This acts as a modal dialog.
  35. class SelectFileDialogLinuxKde : public SelectFileDialogLinux {
  36. public:
  37. SelectFileDialogLinuxKde(Listener* listener,
  38. std::unique_ptr<ui::SelectFilePolicy> policy,
  39. base::nix::DesktopEnvironment desktop,
  40. const std::string& kdialog_version);
  41. SelectFileDialogLinuxKde(const SelectFileDialogLinuxKde&) = delete;
  42. SelectFileDialogLinuxKde& operator=(const SelectFileDialogLinuxKde&) = delete;
  43. protected:
  44. ~SelectFileDialogLinuxKde() override;
  45. // BaseShellDialog implementation:
  46. bool IsRunning(gfx::NativeWindow parent_window) const override;
  47. // SelectFileDialog implementation.
  48. // |params| is user data we pass back via the Listener interface.
  49. void SelectFileImpl(Type type,
  50. const std::u16string& title,
  51. const base::FilePath& default_path,
  52. const FileTypeInfo* file_types,
  53. int file_type_index,
  54. const base::FilePath::StringType& default_extension,
  55. gfx::NativeWindow owning_window,
  56. void* params) override;
  57. private:
  58. bool HasMultipleFileTypeChoicesImpl() override;
  59. struct KDialogParams {
  60. KDialogParams(const std::string& type,
  61. const std::string& title,
  62. const base::FilePath& default_path,
  63. gfx::AcceleratedWidget parent,
  64. bool file_operation,
  65. bool multiple_selection)
  66. : type(type),
  67. title(title),
  68. default_path(default_path),
  69. parent(parent),
  70. file_operation(file_operation),
  71. multiple_selection(multiple_selection) {}
  72. std::string type;
  73. std::string title;
  74. base::FilePath default_path;
  75. gfx::AcceleratedWidget parent;
  76. bool file_operation;
  77. bool multiple_selection;
  78. };
  79. struct KDialogOutputParams {
  80. std::string output;
  81. int exit_code;
  82. };
  83. // Get the filters from |file_types_| and concatenate them into
  84. // |filter_string|.
  85. std::string GetMimeTypeFilterString();
  86. // Get KDialog command line representing the Argv array for KDialog.
  87. void GetKDialogCommandLine(const std::string& type,
  88. const std::string& title,
  89. const base::FilePath& default_path,
  90. gfx::AcceleratedWidget parent,
  91. bool file_operation,
  92. bool multiple_selection,
  93. base::CommandLine* command_line);
  94. // Call KDialog on the FILE thread and return the results.
  95. std::unique_ptr<KDialogOutputParams> CallKDialogOutput(
  96. const KDialogParams& params);
  97. // Notifies the listener that a single file was chosen.
  98. void FileSelected(const base::FilePath& path, void* params);
  99. // Notifies the listener that multiple files were chosen.
  100. void MultiFilesSelected(const std::vector<base::FilePath>& files,
  101. void* params);
  102. // Notifies the listener that no file was chosen (the action was canceled).
  103. // Dialog is passed so we can find that |params| pointer that was passed to
  104. // us when we were told to show the dialog.
  105. void FileNotSelected(void* params);
  106. void CreateSelectFolderDialog(Type type,
  107. const std::string& title,
  108. const base::FilePath& default_path,
  109. gfx::AcceleratedWidget parent,
  110. void* params);
  111. void CreateFileOpenDialog(const std::string& title,
  112. const base::FilePath& default_path,
  113. gfx::AcceleratedWidget parent,
  114. void* params);
  115. void CreateMultiFileOpenDialog(const std::string& title,
  116. const base::FilePath& default_path,
  117. gfx::AcceleratedWidget parent,
  118. void* params);
  119. void CreateSaveAsDialog(const std::string& title,
  120. const base::FilePath& default_path,
  121. gfx::AcceleratedWidget parent,
  122. void* params);
  123. // Common function for OnSelectSingleFileDialogResponse and
  124. // OnSelectSingleFolderDialogResponse.
  125. void SelectSingleFileHelper(void* params,
  126. bool allow_folder,
  127. std::unique_ptr<KDialogOutputParams> results);
  128. void OnSelectSingleFileDialogResponse(
  129. gfx::AcceleratedWidget parent,
  130. void* params,
  131. std::unique_ptr<KDialogOutputParams> results);
  132. void OnSelectMultiFileDialogResponse(
  133. gfx::AcceleratedWidget parent,
  134. void* params,
  135. std::unique_ptr<KDialogOutputParams> results);
  136. void OnSelectSingleFolderDialogResponse(
  137. gfx::AcceleratedWidget parent,
  138. void* params,
  139. std::unique_ptr<KDialogOutputParams> results);
  140. // Should be either DESKTOP_ENVIRONMENT_KDE3, KDE4, or KDE5.
  141. base::nix::DesktopEnvironment desktop_;
  142. // The set of all parent windows for which we are currently running
  143. // dialogs. This should only be accessed on the UI thread.
  144. std::set<gfx::AcceleratedWidget> parents_;
  145. // Set to true if the kdialog version is new enough to support passing
  146. // multiple extensions with descriptions, eliminating the need for the lossy
  147. // conversion of extensions to mime-types.
  148. bool kdialog_supports_multiple_extensions_ = false;
  149. // A task runner for blocking pipe reads.
  150. scoped_refptr<base::SequencedTaskRunner> pipe_task_runner_;
  151. SEQUENCE_CHECKER(sequence_checker_);
  152. };
  153. // static
  154. bool SelectFileDialogLinux::CheckKDEDialogWorksOnUIThread(
  155. std::string& kdialog_version) {
  156. // No choice. UI thread can't continue without an answer here. Fortunately we
  157. // only do this once, the first time a file dialog is displayed.
  158. base::ThreadRestrictions::ScopedAllowIO allow_io;
  159. base::CommandLine::StringVector cmd_vector;
  160. cmd_vector.push_back(kKdialogBinary);
  161. cmd_vector.push_back("--version");
  162. base::CommandLine command_line(cmd_vector);
  163. return base::GetAppOutput(command_line, &kdialog_version);
  164. }
  165. SelectFileDialogLinuxKde* NewSelectFileDialogLinuxKde(
  166. SelectFileDialog::Listener* listener,
  167. std::unique_ptr<ui::SelectFilePolicy> policy,
  168. base::nix::DesktopEnvironment desktop,
  169. const std::string& kdialog_version) {
  170. return new SelectFileDialogLinuxKde(listener, std::move(policy), desktop,
  171. kdialog_version);
  172. }
  173. SelectFileDialogLinuxKde::SelectFileDialogLinuxKde(
  174. Listener* listener,
  175. std::unique_ptr<ui::SelectFilePolicy> policy,
  176. base::nix::DesktopEnvironment desktop,
  177. const std::string& kdialog_version)
  178. : SelectFileDialogLinux(listener, std::move(policy)),
  179. desktop_(desktop),
  180. pipe_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
  181. {base::MayBlock(), base::TaskPriority::USER_BLOCKING,
  182. base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})) {
  183. DCHECK(desktop_ == base::nix::DESKTOP_ENVIRONMENT_KDE3 ||
  184. desktop_ == base::nix::DESKTOP_ENVIRONMENT_KDE4 ||
  185. desktop_ == base::nix::DESKTOP_ENVIRONMENT_KDE5);
  186. // |kdialog_version| should be of the form "kdialog 1.2.3", so split on
  187. // whitespace and then try to parse a version from the second piece. If
  188. // parsing fails for whatever reason, we fall back to the behavior that works
  189. // with all currently known versions of kdialog.
  190. std::vector<base::StringPiece> version_pieces = base::SplitStringPiece(
  191. kdialog_version, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
  192. if (version_pieces.size() >= 2) {
  193. base::Version parsed_version(version_pieces[1]);
  194. if (parsed_version.IsValid()) {
  195. kdialog_supports_multiple_extensions_ =
  196. parsed_version >= base::Version("19.12");
  197. }
  198. }
  199. }
  200. SelectFileDialogLinuxKde::~SelectFileDialogLinuxKde() = default;
  201. bool SelectFileDialogLinuxKde::IsRunning(
  202. gfx::NativeWindow parent_window) const {
  203. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  204. if (parent_window && parent_window->GetHost()) {
  205. auto window = parent_window->GetHost()->GetAcceleratedWidget();
  206. return parents_.find(window) != parents_.end();
  207. }
  208. return false;
  209. }
  210. // We ignore |default_extension|.
  211. void SelectFileDialogLinuxKde::SelectFileImpl(
  212. Type type,
  213. const std::u16string& title,
  214. const base::FilePath& default_path,
  215. const FileTypeInfo* file_types,
  216. int file_type_index,
  217. const base::FilePath::StringType& default_extension,
  218. gfx::NativeWindow owning_window,
  219. void* params) {
  220. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  221. set_type(type);
  222. gfx::AcceleratedWidget window = gfx::kNullAcceleratedWidget;
  223. if (owning_window && owning_window->GetHost()) {
  224. // |owning_window| can be null when user right-clicks on a downloadable item
  225. // and chooses 'Open Link in New Tab' when 'Ask where to save each file
  226. // before downloading.' preference is turned on. (http://crbug.com/29213)
  227. window = owning_window->GetHost()->GetAcceleratedWidget();
  228. parents_.insert(window);
  229. }
  230. std::string title_string = base::UTF16ToUTF8(title);
  231. set_file_type_index(file_type_index);
  232. if (file_types) {
  233. set_file_types(*file_types);
  234. } else {
  235. auto file_types_copy = SelectFileDialogLinux::file_types();
  236. file_types_copy.include_all_files = true;
  237. set_file_types(file_types_copy);
  238. }
  239. switch (type) {
  240. case SELECT_FOLDER:
  241. case SELECT_UPLOAD_FOLDER:
  242. case SELECT_EXISTING_FOLDER:
  243. CreateSelectFolderDialog(type, title_string, default_path, window,
  244. params);
  245. return;
  246. case SELECT_OPEN_FILE:
  247. CreateFileOpenDialog(title_string, default_path, window, params);
  248. return;
  249. case SELECT_OPEN_MULTI_FILE:
  250. CreateMultiFileOpenDialog(title_string, default_path, window, params);
  251. return;
  252. case SELECT_SAVEAS_FILE:
  253. CreateSaveAsDialog(title_string, default_path, window, params);
  254. return;
  255. case SELECT_NONE:
  256. NOTREACHED();
  257. return;
  258. }
  259. }
  260. bool SelectFileDialogLinuxKde::HasMultipleFileTypeChoicesImpl() {
  261. return file_types().extensions.size() > 1;
  262. }
  263. std::string SelectFileDialogLinuxKde::GetMimeTypeFilterString() {
  264. DCHECK(pipe_task_runner_->RunsTasksInCurrentSequence());
  265. if (!kdialog_supports_multiple_extensions_) {
  266. // We need a filter set because the same mime type can appear multiple
  267. // times.
  268. std::set<std::string> filter_set;
  269. for (auto& extensions : file_types().extensions) {
  270. for (auto& extension : extensions) {
  271. if (!extension.empty()) {
  272. std::string mime_type = base::nix::GetFileMimeType(
  273. base::FilePath("name").ReplaceExtension(extension));
  274. filter_set.insert(mime_type);
  275. }
  276. }
  277. }
  278. std::vector<std::string> filter_vector(filter_set.cbegin(),
  279. filter_set.cend());
  280. // Add the *.* filter, but only if we have added other filters (otherwise it
  281. // is implied). It needs to be added last to avoid being picked as the
  282. // default filter.
  283. if (file_types().include_all_files && !file_types().extensions.empty()) {
  284. DCHECK(filter_set.find("application/octet-stream") == filter_set.end());
  285. filter_vector.push_back("application/octet-stream");
  286. }
  287. return base::JoinString(filter_vector, " ");
  288. }
  289. std::vector<std::string> filters;
  290. for (size_t i = 0; i < file_types().extensions.size(); ++i) {
  291. std::set<std::string> extension_filters;
  292. for (const auto& extension : file_types().extensions[i]) {
  293. if (extension.empty())
  294. continue;
  295. extension_filters.insert(std::string("*.") + extension);
  296. }
  297. // We didn't find any non-empty extensions to filter on.
  298. if (extension_filters.empty())
  299. continue;
  300. std::vector<std::string> extension_filters_vector(extension_filters.begin(),
  301. extension_filters.end());
  302. std::string description;
  303. // The description vector may be blank, in which case we are supposed to
  304. // use some sort of default description based on the filter.
  305. if (i < file_types().extension_description_overrides.size()) {
  306. description =
  307. base::UTF16ToUTF8(file_types().extension_description_overrides[i]);
  308. // Filter out any characters that would mess up kdialog's parsing.
  309. base::ReplaceChars(description, "|()", "", &description);
  310. } else {
  311. // There is no system default filter description so we use
  312. // the extensions themselves if the description is blank.
  313. description = base::JoinString(extension_filters_vector, ",");
  314. }
  315. filters.push_back(description + " (" +
  316. base::JoinString(extension_filters_vector, " ") + ")");
  317. }
  318. if (file_types().include_all_files && !file_types().extensions.empty())
  319. filters.push_back(l10n_util::GetStringUTF8(IDS_SAVEAS_ALL_FILES) + " (*)");
  320. return base::JoinString(filters, "|");
  321. }
  322. std::unique_ptr<SelectFileDialogLinuxKde::KDialogOutputParams>
  323. SelectFileDialogLinuxKde::CallKDialogOutput(const KDialogParams& params) {
  324. DCHECK(pipe_task_runner_->RunsTasksInCurrentSequence());
  325. base::CommandLine::StringVector cmd_vector;
  326. cmd_vector.push_back(kKdialogBinary);
  327. base::CommandLine command_line(cmd_vector);
  328. GetKDialogCommandLine(params.type, params.title, params.default_path,
  329. params.parent, params.file_operation,
  330. params.multiple_selection, &command_line);
  331. auto results = std::make_unique<KDialogOutputParams>();
  332. // Get output from KDialog
  333. base::GetAppOutputWithExitCode(command_line, &results->output,
  334. &results->exit_code);
  335. if (!results->output.empty())
  336. results->output.erase(results->output.size() - 1);
  337. return results;
  338. }
  339. void SelectFileDialogLinuxKde::GetKDialogCommandLine(
  340. const std::string& type,
  341. const std::string& title,
  342. const base::FilePath& path,
  343. gfx::AcceleratedWidget parent,
  344. bool file_operation,
  345. bool multiple_selection,
  346. base::CommandLine* command_line) {
  347. CHECK(command_line);
  348. // Attach to the current Chrome window.
  349. if (parent != gfx::kNullAcceleratedWidget) {
  350. command_line->AppendSwitchNative(
  351. desktop_ == base::nix::DESKTOP_ENVIRONMENT_KDE3 ? "--embed"
  352. : "--attach",
  353. base::NumberToString(static_cast<uint32_t>(parent)));
  354. }
  355. // Set the correct title for the dialog.
  356. if (!title.empty())
  357. command_line->AppendSwitchNative("--title", title);
  358. // Enable multiple file selection if we need to.
  359. if (multiple_selection) {
  360. command_line->AppendSwitch("--multiple");
  361. command_line->AppendSwitch("--separate-output");
  362. }
  363. command_line->AppendSwitch(type);
  364. // The path should never be empty. If it is, set it to PWD.
  365. if (path.empty())
  366. command_line->AppendArgPath(base::FilePath("."));
  367. else
  368. command_line->AppendArgPath(path);
  369. // Depending on the type of the operation we need, get the path to the
  370. // file/folder and set up mime type filters.
  371. if (file_operation)
  372. command_line->AppendArg(GetMimeTypeFilterString());
  373. VLOG(1) << "KDialog command line: " << command_line->GetCommandLineString();
  374. }
  375. void SelectFileDialogLinuxKde::FileSelected(const base::FilePath& path,
  376. void* params) {
  377. if (type() == SELECT_SAVEAS_FILE)
  378. set_last_saved_path(path.DirName());
  379. else if (type() == SELECT_OPEN_FILE)
  380. set_last_opened_path(path.DirName());
  381. else if (type() == SELECT_FOLDER || type() == SELECT_UPLOAD_FOLDER ||
  382. type() == SELECT_EXISTING_FOLDER)
  383. set_last_opened_path(path);
  384. else
  385. NOTREACHED();
  386. if (listener_) { // What does the filter index actually do?
  387. // TODO(dfilimon): Get a reasonable index value from somewhere.
  388. listener_->FileSelected(path, 1, params);
  389. }
  390. }
  391. void SelectFileDialogLinuxKde::MultiFilesSelected(
  392. const std::vector<base::FilePath>& files,
  393. void* params) {
  394. set_last_opened_path(files[0].DirName());
  395. if (listener_)
  396. listener_->MultiFilesSelected(files, params);
  397. }
  398. void SelectFileDialogLinuxKde::FileNotSelected(void* params) {
  399. if (listener_)
  400. listener_->FileSelectionCanceled(params);
  401. }
  402. void SelectFileDialogLinuxKde::CreateSelectFolderDialog(
  403. Type type,
  404. const std::string& title,
  405. const base::FilePath& default_path,
  406. gfx::AcceleratedWidget parent,
  407. void* params) {
  408. int title_message_id = (type == SELECT_UPLOAD_FOLDER)
  409. ? IDS_SELECT_UPLOAD_FOLDER_DIALOG_TITLE
  410. : IDS_SELECT_FOLDER_DIALOG_TITLE;
  411. pipe_task_runner_->PostTaskAndReplyWithResult(
  412. FROM_HERE,
  413. base::BindOnce(
  414. &SelectFileDialogLinuxKde::CallKDialogOutput, this,
  415. KDialogParams(
  416. "--getexistingdirectory", GetTitle(title, title_message_id),
  417. default_path.empty() ? *last_opened_path() : default_path, parent,
  418. false, false)),
  419. base::BindOnce(
  420. &SelectFileDialogLinuxKde::OnSelectSingleFolderDialogResponse, this,
  421. parent, params));
  422. }
  423. void SelectFileDialogLinuxKde::CreateFileOpenDialog(
  424. const std::string& title,
  425. const base::FilePath& default_path,
  426. gfx::AcceleratedWidget parent,
  427. void* params) {
  428. pipe_task_runner_->PostTaskAndReplyWithResult(
  429. FROM_HERE,
  430. base::BindOnce(
  431. &SelectFileDialogLinuxKde::CallKDialogOutput, this,
  432. KDialogParams(
  433. "--getopenfilename", GetTitle(title, IDS_OPEN_FILE_DIALOG_TITLE),
  434. default_path.empty() ? *last_opened_path() : default_path, parent,
  435. true, false)),
  436. base::BindOnce(
  437. &SelectFileDialogLinuxKde::OnSelectSingleFileDialogResponse, this,
  438. parent, params));
  439. }
  440. void SelectFileDialogLinuxKde::CreateMultiFileOpenDialog(
  441. const std::string& title,
  442. const base::FilePath& default_path,
  443. gfx::AcceleratedWidget parent,
  444. void* params) {
  445. pipe_task_runner_->PostTaskAndReplyWithResult(
  446. FROM_HERE,
  447. base::BindOnce(
  448. &SelectFileDialogLinuxKde::CallKDialogOutput, this,
  449. KDialogParams(
  450. "--getopenfilename", GetTitle(title, IDS_OPEN_FILES_DIALOG_TITLE),
  451. default_path.empty() ? *last_opened_path() : default_path, parent,
  452. true, true)),
  453. base::BindOnce(&SelectFileDialogLinuxKde::OnSelectMultiFileDialogResponse,
  454. this, parent, params));
  455. }
  456. void SelectFileDialogLinuxKde::CreateSaveAsDialog(
  457. const std::string& title,
  458. const base::FilePath& default_path,
  459. gfx::AcceleratedWidget parent,
  460. void* params) {
  461. pipe_task_runner_->PostTaskAndReplyWithResult(
  462. FROM_HERE,
  463. base::BindOnce(
  464. &SelectFileDialogLinuxKde::CallKDialogOutput, this,
  465. KDialogParams(
  466. "--getsavefilename", GetTitle(title, IDS_SAVE_AS_DIALOG_TITLE),
  467. default_path.empty() ? *last_saved_path() : default_path, parent,
  468. true, false)),
  469. base::BindOnce(
  470. &SelectFileDialogLinuxKde::OnSelectSingleFileDialogResponse, this,
  471. parent, params));
  472. }
  473. void SelectFileDialogLinuxKde::SelectSingleFileHelper(
  474. void* params,
  475. bool allow_folder,
  476. std::unique_ptr<KDialogOutputParams> results) {
  477. VLOG(1) << "[kdialog] SingleFileResponse: " << results->output;
  478. if (results->exit_code || results->output.empty()) {
  479. FileNotSelected(params);
  480. return;
  481. }
  482. base::FilePath path(results->output);
  483. if (allow_folder) {
  484. FileSelected(path, params);
  485. return;
  486. }
  487. if (CallDirectoryExistsOnUIThread(path))
  488. FileNotSelected(params);
  489. else
  490. FileSelected(path, params);
  491. }
  492. void SelectFileDialogLinuxKde::OnSelectSingleFileDialogResponse(
  493. gfx::AcceleratedWidget parent,
  494. void* params,
  495. std::unique_ptr<KDialogOutputParams> results) {
  496. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  497. parents_.erase(parent);
  498. SelectSingleFileHelper(params, false, std::move(results));
  499. }
  500. void SelectFileDialogLinuxKde::OnSelectSingleFolderDialogResponse(
  501. gfx::AcceleratedWidget parent,
  502. void* params,
  503. std::unique_ptr<KDialogOutputParams> results) {
  504. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  505. parents_.erase(parent);
  506. SelectSingleFileHelper(params, true, std::move(results));
  507. }
  508. void SelectFileDialogLinuxKde::OnSelectMultiFileDialogResponse(
  509. gfx::AcceleratedWidget parent,
  510. void* params,
  511. std::unique_ptr<KDialogOutputParams> results) {
  512. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  513. VLOG(1) << "[kdialog] MultiFileResponse: " << results->output;
  514. parents_.erase(parent);
  515. if (results->exit_code || results->output.empty()) {
  516. FileNotSelected(params);
  517. return;
  518. }
  519. std::vector<base::FilePath> filenames_fp;
  520. for (const base::StringPiece& line :
  521. base::SplitStringPiece(results->output, "\n", base::KEEP_WHITESPACE,
  522. base::SPLIT_WANT_NONEMPTY)) {
  523. base::FilePath path(line);
  524. if (CallDirectoryExistsOnUIThread(path))
  525. continue;
  526. filenames_fp.push_back(path);
  527. }
  528. if (filenames_fp.empty()) {
  529. FileNotSelected(params);
  530. return;
  531. }
  532. MultiFilesSelected(filenames_fp, params);
  533. }
  534. } // namespace ui