Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

juce_linux_FileChooser.cpp 8.0KB

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