juce_linux_FileChooser.cpp 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. namespace juce
  20. {
  21. static bool exeIsAvailable (const char* const executable)
  22. {
  23. ChildProcess child;
  24. const bool ok = child.start ("which " + String (executable))
  25. && child.readAllProcessOutput().trim().isNotEmpty();
  26. child.waitForProcessToFinish (60 * 1000);
  27. return ok;
  28. }
  29. class FileChooser::Native : public FileChooser::Pimpl,
  30. private Timer
  31. {
  32. public:
  33. Native (FileChooser& fileChooser, int flags)
  34. : owner (fileChooser),
  35. isDirectory ((flags & FileBrowserComponent::canSelectDirectories) != 0),
  36. isSave ((flags & FileBrowserComponent::saveMode) != 0),
  37. selectMultipleFiles ((flags & FileBrowserComponent::canSelectMultipleItems) != 0),
  38. warnAboutOverwrite ((flags & FileBrowserComponent::warnAboutOverwriting) != 0)
  39. {
  40. const File previousWorkingDirectory (File::getCurrentWorkingDirectory());
  41. // use kdialog for KDE sessions or if zenity is missing
  42. if (exeIsAvailable ("kdialog") && (isKdeFullSession() || ! exeIsAvailable ("zenity")))
  43. addKDialogArgs();
  44. else
  45. addZenityArgs();
  46. }
  47. ~Native() override
  48. {
  49. finish (true);
  50. }
  51. void runModally() override
  52. {
  53. child.start (args, ChildProcess::wantStdOut);
  54. while (child.isRunning())
  55. if (! MessageManager::getInstance()->runDispatchLoopUntil(20))
  56. break;
  57. finish (false);
  58. }
  59. void launch() override
  60. {
  61. child.start (args, ChildProcess::wantStdOut);
  62. startTimer (100);
  63. }
  64. private:
  65. FileChooser& owner;
  66. bool isDirectory, isSave, selectMultipleFiles, warnAboutOverwrite;
  67. ChildProcess child;
  68. StringArray args;
  69. String separator;
  70. void timerCallback() override
  71. {
  72. if (! child.isRunning())
  73. {
  74. stopTimer();
  75. finish (false);
  76. }
  77. }
  78. void finish (bool shouldKill)
  79. {
  80. String result;
  81. Array<URL> selection;
  82. if (shouldKill)
  83. child.kill();
  84. else
  85. result = child.readAllProcessOutput().trim();
  86. if (result.isNotEmpty())
  87. {
  88. StringArray tokens;
  89. if (selectMultipleFiles)
  90. tokens.addTokens (result, separator, "\"");
  91. else
  92. tokens.add (result);
  93. for (auto& token : tokens)
  94. selection.add (URL (File::getCurrentWorkingDirectory().getChildFile (token)));
  95. }
  96. if (! shouldKill)
  97. {
  98. child.waitForProcessToFinish (60 * 1000);
  99. owner.finished (selection);
  100. }
  101. }
  102. static uint64 getTopWindowID() noexcept
  103. {
  104. if (TopLevelWindow* top = TopLevelWindow::getActiveTopLevelWindow())
  105. return (uint64) (pointer_sized_uint) top->getWindowHandle();
  106. return 0;
  107. }
  108. static bool isKdeFullSession()
  109. {
  110. return SystemStats::getEnvironmentVariable ("KDE_FULL_SESSION", String())
  111. .equalsIgnoreCase ("true");
  112. }
  113. void addKDialogArgs()
  114. {
  115. args.add ("kdialog");
  116. if (owner.title.isNotEmpty())
  117. args.add ("--title=" + owner.title);
  118. if (uint64 topWindowID = getTopWindowID())
  119. {
  120. args.add ("--attach");
  121. args.add (String (topWindowID));
  122. }
  123. if (selectMultipleFiles)
  124. {
  125. separator = "\n";
  126. args.add ("--multiple");
  127. args.add ("--separate-output");
  128. args.add ("--getopenfilename");
  129. }
  130. else
  131. {
  132. if (isSave) args.add ("--getsavefilename");
  133. else if (isDirectory) args.add ("--getexistingdirectory");
  134. else args.add ("--getopenfilename");
  135. }
  136. File startPath;
  137. if (owner.startingFile.exists())
  138. {
  139. startPath = owner.startingFile;
  140. }
  141. else if (owner.startingFile.getParentDirectory().exists())
  142. {
  143. startPath = owner.startingFile.getParentDirectory();
  144. }
  145. else
  146. {
  147. startPath = File::getSpecialLocation (File::userHomeDirectory);
  148. if (isSave)
  149. startPath = startPath.getChildFile (owner.startingFile.getFileName());
  150. }
  151. args.add (startPath.getFullPathName());
  152. args.add (owner.filters.replaceCharacter (';', ' '));
  153. }
  154. void addZenityArgs()
  155. {
  156. args.add ("zenity");
  157. args.add ("--file-selection");
  158. if (warnAboutOverwrite)
  159. args.add("--confirm-overwrite");
  160. if (owner.title.isNotEmpty())
  161. args.add ("--title=" + owner.title);
  162. if (selectMultipleFiles)
  163. {
  164. separator = ":";
  165. args.add ("--multiple");
  166. args.add ("--separator=" + separator);
  167. }
  168. else
  169. {
  170. if (isDirectory) args.add ("--directory");
  171. if (isSave) args.add ("--save");
  172. }
  173. if (owner.filters.isNotEmpty() && owner.filters != "*" && owner.filters != "*.*")
  174. {
  175. StringArray tokens;
  176. tokens.addTokens (owner.filters, ";,|", "\"");
  177. for (int i = 0; i < tokens.size(); ++i)
  178. args.add ("--file-filter=" + tokens[i]);
  179. }
  180. if (owner.startingFile.isDirectory())
  181. owner.startingFile.setAsCurrentWorkingDirectory();
  182. else if (owner.startingFile.getParentDirectory().exists())
  183. owner.startingFile.getParentDirectory().setAsCurrentWorkingDirectory();
  184. else
  185. File::getSpecialLocation (File::userHomeDirectory).setAsCurrentWorkingDirectory();
  186. auto filename = owner.startingFile.getFileName();
  187. if (! filename.isEmpty())
  188. args.add ("--filename=" + filename);
  189. // supplying the window ID of the topmost window makes sure that Zenity pops up..
  190. if (uint64 topWindowID = getTopWindowID())
  191. setenv ("WINDOWID", String (topWindowID).toRawUTF8(), true);
  192. }
  193. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Native)
  194. };
  195. bool FileChooser::isPlatformDialogAvailable()
  196. {
  197. #if JUCE_DISABLE_NATIVE_FILECHOOSERS
  198. return false;
  199. #else
  200. static bool canUseNativeBox = exeIsAvailable ("zenity") || exeIsAvailable ("kdialog");
  201. return canUseNativeBox;
  202. #endif
  203. }
  204. FileChooser::Pimpl* FileChooser::showPlatformDialog (FileChooser& owner, int flags, FilePreviewComponent*)
  205. {
  206. return new Native (owner, flags);
  207. }
  208. } // namespace juce