DISTRHO Plugin Framework
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.

459 lines
11KB

  1. /*
  2. * DISTRHO Plugin Framework (DPF)
  3. * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
  4. *
  5. * Permission to use, copy, modify, and/or distribute this software for any purpose with
  6. * or without fee is hereby granted, provided that the above copyright notice and this
  7. * permission notice appear in all copies.
  8. *
  9. * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
  10. * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
  11. * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
  12. * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
  13. * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
  14. * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  15. */
  16. #ifndef DISTRHO_EXTERNAL_WINDOW_HPP_INCLUDED
  17. #define DISTRHO_EXTERNAL_WINDOW_HPP_INCLUDED
  18. #include "String.hpp"
  19. #ifdef DISTRHO_OS_WINDOWS
  20. # error Unsupported platform!
  21. #else
  22. # include <cerrno>
  23. # include <signal.h>
  24. # include <sys/wait.h>
  25. # include <unistd.h>
  26. #endif
  27. START_NAMESPACE_DISTRHO
  28. // -----------------------------------------------------------------------
  29. // ExternalWindow class
  30. /**
  31. External Window class.
  32. This is a standalone TopLevelWidget-compatible class, but without any real event handling.
  33. Being compatible with TopLevelWidget, it allows to be used as DPF UI target.
  34. It can be used to embed non-DPF things or to run a tool in a new process as the "UI".
  35. */
  36. class ExternalWindow
  37. {
  38. struct PrivateData;
  39. public:
  40. /**
  41. Constructor.
  42. */
  43. explicit ExternalWindow()
  44. : pData() {}
  45. /**
  46. Constructor.
  47. */
  48. explicit ExternalWindow(const PrivateData& data)
  49. : pData(data) {}
  50. /**
  51. Destructor.
  52. */
  53. virtual ~ExternalWindow()
  54. {
  55. DISTRHO_SAFE_ASSERT(!pData.visible);
  56. }
  57. /* --------------------------------------------------------------------------------------------------------
  58. * ExternalWindow specific calls */
  59. virtual bool isRunning() const
  60. {
  61. if (ext.inUse)
  62. return ext.isRunning();
  63. return isVisible();
  64. }
  65. virtual bool isQuiting() const
  66. {
  67. if (ext.inUse)
  68. return ext.isQuiting;
  69. return !isVisible();
  70. }
  71. /**
  72. Hide the UI and gracefully terminate.
  73. */
  74. virtual void close()
  75. {
  76. hide();
  77. if (ext.inUse)
  78. terminateAndWaitForExternalProcess();
  79. }
  80. /**
  81. Grab the keyboard input focus.
  82. */
  83. virtual void focus() {}
  84. /**
  85. Get the transient window that we should attach ourselves to.
  86. TODO what id? also NSView* on macOS, or NSWindow?
  87. */
  88. uintptr_t getTransientWindowId() const noexcept
  89. {
  90. return pData.transientWinId;
  91. }
  92. /**
  93. Called by the host to set the transient window that we should attach ourselves to.
  94. TODO what id? also NSView* on macOS, or NSWindow?
  95. */
  96. void setTransientWindowId(uintptr_t winId)
  97. {
  98. if (pData.transientWinId == winId)
  99. return;
  100. pData.transientWinId = winId;
  101. transientWindowChanged(winId);
  102. }
  103. #if DISTRHO_PLUGIN_HAS_EMBED_UI
  104. /**
  105. Whether this Window is embed into another (usually not DGL-controlled) Window.
  106. */
  107. bool isEmbed() const noexcept
  108. {
  109. return pData.parentWindowHandle != 0;
  110. }
  111. /**
  112. Get the "native" window handle.
  113. This can be reimplemented in order to pass the child window to hosts that can use such informaton.
  114. Returned value type depends on the platform:
  115. - HaikuOS: This is a pointer to a `BView`.
  116. - MacOS: This is a pointer to an `NSView*`.
  117. - Windows: This is a `HWND`.
  118. - Everything else: This is an [X11] `Window`.
  119. */
  120. virtual uintptr_t getNativeWindowHandle() const noexcept
  121. {
  122. return 0;
  123. }
  124. /**
  125. Get the "native" window handle that this window should embed itself into.
  126. Returned value type depends on the platform:
  127. - HaikuOS: This is a pointer to a `BView`.
  128. - MacOS: This is a pointer to an `NSView*`.
  129. - Windows: This is a `HWND`.
  130. - Everything else: This is an [X11] `Window`.
  131. */
  132. uintptr_t getParentWindowHandle() const noexcept
  133. {
  134. return pData.parentWindowHandle;
  135. }
  136. #endif
  137. /* --------------------------------------------------------------------------------------------------------
  138. * TopLevelWidget-like calls */
  139. /**
  140. Check if this window is visible.
  141. @see setVisible(bool)
  142. */
  143. bool isVisible() const noexcept
  144. {
  145. return pData.visible;
  146. }
  147. /**
  148. Set window visible (or not) according to @a visible.
  149. @see isVisible(), hide(), show()
  150. */
  151. void setVisible(bool visible)
  152. {
  153. if (pData.visible == visible)
  154. return;
  155. pData.visible = visible;
  156. visibilityChanged(visible);
  157. }
  158. /**
  159. Show window.
  160. This is the same as calling setVisible(true).
  161. @see isVisible(), setVisible(bool)
  162. */
  163. void show()
  164. {
  165. setVisible(true);
  166. }
  167. /**
  168. Hide window.
  169. This is the same as calling setVisible(false).
  170. @see isVisible(), setVisible(bool)
  171. */
  172. void hide()
  173. {
  174. setVisible(false);
  175. }
  176. /**
  177. Get width.
  178. */
  179. uint getWidth() const noexcept
  180. {
  181. return pData.width;
  182. }
  183. /**
  184. Get height.
  185. */
  186. uint getHeight() const noexcept
  187. {
  188. return pData.height;
  189. }
  190. /**
  191. Set width.
  192. */
  193. void setWidth(uint width)
  194. {
  195. setSize(width, getHeight());
  196. }
  197. /**
  198. Set height.
  199. */
  200. void setHeight(uint height)
  201. {
  202. setSize(getWidth(), height);
  203. }
  204. /**
  205. Set size using @a width and @a height values.
  206. */
  207. void setSize(uint width, uint height)
  208. {
  209. DISTRHO_SAFE_ASSERT_UINT2_RETURN(width > 1 && height > 1, width, height,);
  210. if (pData.width == width || pData.height == height)
  211. return;
  212. pData.width = width;
  213. pData.height = height;
  214. onResize(width, height);
  215. }
  216. /**
  217. Get the title of the window previously set with setTitle().
  218. */
  219. const char* getTitle() const noexcept
  220. {
  221. return pData.title;
  222. }
  223. /**
  224. Set the title of the window, typically displayed in the title bar or in window switchers.
  225. */
  226. void setTitle(const char* title)
  227. {
  228. if (pData.title == title)
  229. return;
  230. pData.title = title;
  231. titleChanged(title);
  232. }
  233. /**
  234. Get the scale factor requested for this window.
  235. This is purely informational, and up to developers to choose what to do with it.
  236. */
  237. double getScaleFactor() const noexcept
  238. {
  239. return pData.scaleFactor;
  240. }
  241. protected:
  242. /* --------------------------------------------------------------------------------------------------------
  243. * ExternalWindow special calls for running externals tools */
  244. bool startExternalProcess(const char* args[])
  245. {
  246. ext.inUse = true;
  247. return ext.start(args);
  248. }
  249. void terminateAndWaitForExternalProcess()
  250. {
  251. ext.isQuiting = true;
  252. ext.terminateAndWait();
  253. }
  254. /* --------------------------------------------------------------------------------------------------------
  255. * ExternalWindow specific callbacks */
  256. /**
  257. A function called when the window is resized.
  258. */
  259. virtual void onResize(uint width, uint height)
  260. {
  261. // unused, meant for custom implementations
  262. return;
  263. (void)width;
  264. (void)height;
  265. }
  266. virtual void titleChanged(const char* title)
  267. {
  268. // unused, meant for custom implementations
  269. return; (void)title;
  270. }
  271. virtual void visibilityChanged(bool visible)
  272. {
  273. // unused, meant for custom implementations
  274. return; (void)visible;
  275. }
  276. virtual void transientWindowChanged(uintptr_t winId)
  277. {
  278. // unused, meant for custom implementations
  279. return; (void)winId;
  280. }
  281. private:
  282. friend class PluginWindow;
  283. friend class UI;
  284. struct ExternalProcess {
  285. bool inUse;
  286. bool isQuiting;
  287. mutable pid_t pid;
  288. ExternalProcess()
  289. : inUse(false),
  290. isQuiting(false),
  291. pid(0) {}
  292. bool isRunning() const noexcept
  293. {
  294. if (pid <= 0)
  295. return false;
  296. const pid_t p = ::waitpid(pid, nullptr, WNOHANG);
  297. if (p == pid || (p == -1 && errno == ECHILD))
  298. {
  299. d_stdout("NOTICE: Child process exited while idle");
  300. pid = 0;
  301. return false;
  302. }
  303. return true;
  304. }
  305. bool start(const char* args[])
  306. {
  307. terminateAndWait();
  308. pid = vfork();
  309. switch (pid)
  310. {
  311. case 0:
  312. execvp(args[0], (char**)args);
  313. _exit(1);
  314. return false;
  315. case -1:
  316. d_stderr("Could not start external ui");
  317. return false;
  318. default:
  319. return true;
  320. }
  321. }
  322. void terminateAndWait()
  323. {
  324. if (pid <= 0)
  325. return;
  326. d_stdout("Waiting for external process to stop,,,");
  327. bool sendTerm = true;
  328. for (pid_t p;;)
  329. {
  330. p = ::waitpid(pid, nullptr, WNOHANG);
  331. switch (p)
  332. {
  333. case 0:
  334. if (sendTerm)
  335. {
  336. sendTerm = false;
  337. ::kill(pid, SIGTERM);
  338. }
  339. break;
  340. case -1:
  341. if (errno == ECHILD)
  342. {
  343. d_stdout("Done! (no such process)");
  344. pid = 0;
  345. return;
  346. }
  347. break;
  348. default:
  349. if (p == pid)
  350. {
  351. d_stdout("Done! (clean wait)");
  352. pid = 0;
  353. return;
  354. }
  355. break;
  356. }
  357. // 5 msec
  358. usleep(5*1000);
  359. }
  360. }
  361. } ext;
  362. struct PrivateData {
  363. uintptr_t parentWindowHandle;
  364. uintptr_t transientWinId;
  365. uint width;
  366. uint height;
  367. double scaleFactor;
  368. String title;
  369. bool visible;
  370. PrivateData()
  371. : parentWindowHandle(0),
  372. transientWinId(0),
  373. width(1),
  374. height(1),
  375. scaleFactor(1.0),
  376. title(),
  377. visible(false) {}
  378. } pData;
  379. DISTRHO_DECLARE_NON_COPYABLE(ExternalWindow)
  380. };
  381. // -----------------------------------------------------------------------
  382. END_NAMESPACE_DISTRHO
  383. #endif // DISTRHO_EXTERNAL_WINDOW_HPP_INCLUDED