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.

260 lines
7.5KB

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