The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
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.

288 lines
8.5KB

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