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.

265 lines
7.6KB

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