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.

271 lines
7.7KB

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