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.

438 lines
13KB

  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. // needed for IDE
  17. #include "DistrhoPluginInfo.h"
  18. #include "DistrhoUI.hpp"
  19. #if defined(DISTRHO_OS_MAC)
  20. # import <Cocoa/Cocoa.h>
  21. #elif defined(DISTRHO_OS_WINDOWS)
  22. #else
  23. # include <sys/types.h>
  24. # include <X11/Xatom.h>
  25. # include <X11/Xlib.h>
  26. # include <X11/Xutil.h>
  27. # define X11Key_Escape 9
  28. #endif
  29. #if defined(DISTRHO_OS_MAC)
  30. # ifndef __MAC_10_12
  31. # define NSEventMaskAny NSAnyEventMask
  32. # define NSWindowStyleMaskClosable NSClosableWindowMask
  33. # define NSWindowStyleMaskMiniaturizable NSMiniaturizableWindowMask
  34. # define NSWindowStyleMaskResizable NSResizableWindowMask
  35. # define NSWindowStyleMaskTitled NSTitledWindowMask
  36. # endif
  37. @interface NSExternalWindow : NSWindow
  38. @end
  39. @implementation NSExternalWindow {
  40. @public
  41. bool closed;
  42. bool standalone;
  43. }
  44. - (BOOL)canBecomeKeyWindow { return YES; }
  45. - (BOOL)canBecomeMainWindow { return standalone ? YES : NO; }
  46. - (BOOL)windowShouldClose:(id)_ { closed = true; return YES; }
  47. @end
  48. #endif
  49. START_NAMESPACE_DISTRHO
  50. // -----------------------------------------------------------------------------------------------------------
  51. class EmbedExternalExampleUI : public UI
  52. {
  53. #if defined(DISTRHO_OS_MAC)
  54. NSView* fView;
  55. NSExternalWindow* fWindow;
  56. #elif defined(DISTRHO_OS_WINDOWS)
  57. ::HWND fWindow;
  58. #else
  59. ::Display* fDisplay;
  60. ::Window fWindow;
  61. #endif
  62. public:
  63. EmbedExternalExampleUI()
  64. : UI(512, 256),
  65. #if defined(DISTRHO_OS_MAC)
  66. fView(nullptr),
  67. fWindow(nullptr)
  68. #elif defined(DISTRHO_OS_WINDOWS)
  69. fWindow(nullptr)
  70. #else
  71. fDisplay(nullptr),
  72. fWindow(0)
  73. #endif
  74. {
  75. const bool standalone = isStandalone();
  76. d_stdout("isStandalone %d", (int)standalone);
  77. #if defined(DISTRHO_OS_MAC)
  78. NSAutoreleasePool* const pool = [[NSAutoreleasePool alloc]init];
  79. [NSApplication sharedApplication];
  80. if (standalone)
  81. {
  82. [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
  83. [NSApp activateIgnoringOtherApps:YES];
  84. }
  85. fView = [NSView new];
  86. DISTRHO_SAFE_ASSERT_RETURN(fView != nullptr,);
  87. [fView initWithFrame:NSMakeRect(0, 0, getWidth(), getHeight())];
  88. [fView setAutoresizesSubviews:YES];
  89. [fView setFrame:NSMakeRect(0, 0, getWidth(), getHeight())];
  90. [fView setHidden:NO];
  91. [fView setNeedsDisplay:YES];
  92. if (isEmbed())
  93. {
  94. [fView retain];
  95. [(NSView*)getParentWindowHandle() addSubview:fView];
  96. }
  97. else
  98. {
  99. const ulong styleMask = NSWindowStyleMaskClosable
  100. | NSWindowStyleMaskMiniaturizable
  101. | NSWindowStyleMaskResizable
  102. | NSWindowStyleMaskTitled;
  103. fWindow = [[[NSExternalWindow alloc]
  104. initWithContentRect:[fView frame]
  105. styleMask:styleMask
  106. backing:NSBackingStoreBuffered
  107. defer:NO]retain];
  108. DISTRHO_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  109. fWindow->closed = false; // is this needed?
  110. fWindow->standalone = standalone;
  111. [fWindow setIsVisible:NO];
  112. if (NSString* const nsTitle = [[NSString alloc]
  113. initWithBytes:getTitle()
  114. length:strlen(getTitle())
  115. encoding:NSUTF8StringEncoding])
  116. [fWindow setTitle:nsTitle];
  117. [fWindow setContentView:fView];
  118. [fWindow setContentSize:NSMakeSize(getWidth(), getHeight())];
  119. [fWindow makeFirstResponder:fView];
  120. }
  121. [pool release];
  122. #elif defined(DISTRHO_OS_WINDOWS)
  123. #else
  124. fDisplay = XOpenDisplay(nullptr);
  125. DISTRHO_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  126. const ::Window parent = isEmbed()
  127. ? (::Window)getParentWindowHandle()
  128. : RootWindow(fDisplay, DefaultScreen(fDisplay));
  129. fWindow = XCreateSimpleWindow(fDisplay, parent, 0, 0, getWidth(), getHeight(), 0, 0, 0);
  130. DISTRHO_SAFE_ASSERT_RETURN(fWindow != 0,);
  131. XSizeHints sizeHints = {};
  132. sizeHints.flags = PMinSize;
  133. sizeHints.min_width = getWidth();
  134. sizeHints.min_height = getHeight();
  135. XSetNormalHints(fDisplay, fWindow, &sizeHints);
  136. XStoreName(fDisplay, fWindow, getTitle());
  137. if (isEmbed())
  138. {
  139. // start with window mapped, so host can access it
  140. XMapWindow(fDisplay, fWindow);
  141. }
  142. else
  143. {
  144. // grab Esc key for auto-close
  145. XGrabKey(fDisplay, X11Key_Escape, AnyModifier, fWindow, 1, GrabModeAsync, GrabModeAsync);
  146. // destroy window on close
  147. Atom wmDelete = XInternAtom(fDisplay, "WM_DELETE_WINDOW", True);
  148. XSetWMProtocols(fDisplay, fWindow, &wmDelete, 1);
  149. // set pid WM hint
  150. const pid_t pid = getpid();
  151. const Atom _nwp = XInternAtom(fDisplay, "_NET_WM_PID", False);
  152. XChangeProperty(fDisplay, fWindow, _nwp, XA_CARDINAL, 32, PropModeReplace, (const uchar*)&pid, 1);
  153. // set the window to both dialog and normal to produce a decorated floating dialog
  154. // order is important: DIALOG needs to come before NORMAL
  155. const Atom _wt = XInternAtom(fDisplay, "_NET_WM_WINDOW_TYPE", False);
  156. const Atom _wts[2] = {
  157. XInternAtom(fDisplay, "_NET_WM_WINDOW_TYPE_DIALOG", False),
  158. XInternAtom(fDisplay, "_NET_WM_WINDOW_TYPE_NORMAL", False)
  159. };
  160. XChangeProperty(fDisplay, fWindow, _wt, XA_ATOM, 32, PropModeReplace, (const uchar*)&_wts, 2);
  161. }
  162. #endif
  163. d_stdout("created external window with size %u %u", getWidth(), getHeight());
  164. }
  165. ~EmbedExternalExampleUI()
  166. {
  167. #if defined(DISTRHO_OS_MAC)
  168. if (fView == nullptr)
  169. return;
  170. if (fWindow != nil)
  171. [fWindow close];
  172. [fView release];
  173. if (fWindow != nil)
  174. [fWindow release];
  175. #elif defined(DISTRHO_OS_WINDOWS)
  176. #else
  177. if (fDisplay == nullptr)
  178. return;
  179. if (fWindow != 0)
  180. XDestroyWindow(fDisplay, fWindow);
  181. XCloseDisplay(fDisplay);
  182. #endif
  183. }
  184. protected:
  185. /* --------------------------------------------------------------------------------------------------------
  186. * DSP/Plugin Callbacks */
  187. /**
  188. A parameter has changed on the plugin side.
  189. This is called by the host to inform the UI about parameter changes.
  190. */
  191. void parameterChanged(uint32_t index, float value) override
  192. {
  193. d_stdout("parameterChanged %u %f", index, value);
  194. switch (index)
  195. {
  196. case kParameterWidth:
  197. setWidth(static_cast<int>(value + 0.5f));
  198. break;
  199. case kParameterHeight:
  200. setHeight(static_cast<int>(value + 0.5f));
  201. break;
  202. }
  203. }
  204. /* --------------------------------------------------------------------------------------------------------
  205. * External Window overrides */
  206. void focus() override
  207. {
  208. d_stdout("focus");
  209. #if defined(DISTRHO_OS_MAC)
  210. DISTRHO_SAFE_ASSERT_RETURN(fWindow != nil,);
  211. [fWindow orderFrontRegardless];
  212. [fWindow makeKeyWindow];
  213. [fWindow makeFirstResponder:fView];
  214. #elif defined(DISTRHO_OS_WINDOWS)
  215. #else
  216. DISTRHO_SAFE_ASSERT_RETURN(fWindow != 0,);
  217. XRaiseWindow(fDisplay, fWindow);
  218. #endif
  219. }
  220. uintptr_t getNativeWindowHandle() const noexcept override
  221. {
  222. #if defined(DISTRHO_OS_MAC)
  223. return (uintptr_t)fView;
  224. #elif defined(DISTRHO_OS_WINDOWS)
  225. #else
  226. return (uintptr_t)fWindow;
  227. #endif
  228. return 0;
  229. }
  230. void sizeChanged(uint width, uint height) override
  231. {
  232. d_stdout("sizeChanged %u %u", width, height);
  233. UI::sizeChanged(width, height);
  234. #if defined(DISTRHO_OS_MAC)
  235. #elif defined(DISTRHO_OS_WINDOWS)
  236. #else
  237. if (fWindow != 0)
  238. XResizeWindow(fDisplay, fWindow, width, height);
  239. #endif
  240. }
  241. void titleChanged(const char* const title) override
  242. {
  243. d_stdout("titleChanged %s", title);
  244. #if defined(DISTRHO_OS_MAC)
  245. if (fWindow != nil)
  246. {
  247. if (NSString* const nsTitle = [[NSString alloc]
  248. initWithBytes:title
  249. length:strlen(title)
  250. encoding:NSUTF8StringEncoding])
  251. {
  252. [fWindow setTitle:nsTitle];
  253. [nsTitle release];
  254. }
  255. }
  256. #elif defined(DISTRHO_OS_WINDOWS)
  257. #else
  258. DISTRHO_SAFE_ASSERT_RETURN(fWindow != 0,);
  259. XStoreName(fDisplay, fWindow, title);
  260. #endif
  261. }
  262. void transientParentWindowChanged(const uintptr_t winId) override
  263. {
  264. d_stdout("transientParentWindowChanged %lu", winId);
  265. #if defined(DISTRHO_OS_MAC)
  266. #elif defined(DISTRHO_OS_WINDOWS)
  267. #else
  268. DISTRHO_SAFE_ASSERT_RETURN(fWindow != 0,);
  269. XSetTransientForHint(fDisplay, fWindow, (::Window)winId);
  270. #endif
  271. }
  272. void visibilityChanged(const bool visible) override
  273. {
  274. d_stdout("visibilityChanged %d", visible);
  275. #if defined(DISTRHO_OS_MAC)
  276. DISTRHO_SAFE_ASSERT_RETURN(fView != nullptr,);
  277. if (fWindow != nil)
  278. {
  279. [fWindow setIsVisible:(visible ? YES : NO)];
  280. if (isStandalone())
  281. [fWindow makeMainWindow];
  282. [fWindow makeKeyAndOrderFront:fWindow];
  283. }
  284. else
  285. {
  286. [fView setHidden:(visible ? NO : YES)];
  287. }
  288. #elif defined(DISTRHO_OS_WINDOWS)
  289. #else
  290. DISTRHO_SAFE_ASSERT_RETURN(fWindow != 0,);
  291. if (visible)
  292. XMapRaised(fDisplay, fWindow);
  293. else
  294. XUnmapWindow(fDisplay, fWindow);
  295. #endif
  296. }
  297. void uiIdle() override
  298. {
  299. // d_stdout("uiIdle");
  300. #if defined(DISTRHO_OS_MAC)
  301. NSAutoreleasePool* const pool = [[NSAutoreleasePool alloc] init];
  302. NSDate* const date = [NSDate distantPast];
  303. for (NSEvent* event;;)
  304. {
  305. event = [NSApp
  306. nextEventMatchingMask:NSEventMaskAny
  307. untilDate:date
  308. inMode:NSDefaultRunLoopMode
  309. dequeue:YES];
  310. if (event == nil)
  311. break;
  312. [NSApp sendEvent:event];
  313. }
  314. if (fWindow->closed)
  315. {
  316. fWindow->closed = false;
  317. close();
  318. }
  319. [pool release];
  320. #elif defined(DISTRHO_OS_WINDOWS)
  321. /*
  322. MSG msg;
  323. if (! ::PeekMessage(&msg, nullptr, 0, 0, PM_NOREMOVE))
  324. return true;
  325. if (::GetMessage(&msg, nullptr, 0, 0) >= 0)
  326. {
  327. if (msg.message == WM_QUIT)
  328. return false;
  329. //TranslateMessage(&msg);
  330. DispatchMessage(&msg);
  331. }
  332. */
  333. #else
  334. if (fDisplay == nullptr)
  335. return;
  336. for (XEvent event; XPending(fDisplay) > 0;)
  337. {
  338. XNextEvent(fDisplay, &event);
  339. if (! isVisible())
  340. continue;
  341. switch (event.type)
  342. {
  343. case ClientMessage:
  344. if (char* const type = XGetAtomName(fDisplay, event.xclient.message_type))
  345. {
  346. if (std::strcmp(type, "WM_PROTOCOLS") == 0)
  347. hide();
  348. }
  349. break;
  350. case KeyRelease:
  351. if (event.xkey.keycode == X11Key_Escape)
  352. hide();
  353. break;
  354. }
  355. }
  356. #endif
  357. }
  358. // -------------------------------------------------------------------------------------------------------
  359. private:
  360. // Current value, cached for when UI becomes visible
  361. float fValue;
  362. /**
  363. Set our UI class as non-copyable and add a leak detector just in case.
  364. */
  365. DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EmbedExternalExampleUI)
  366. };
  367. /* ------------------------------------------------------------------------------------------------------------
  368. * UI entry point, called by DPF to create a new UI instance. */
  369. UI* createUI()
  370. {
  371. return new EmbedExternalExampleUI();
  372. }
  373. // -----------------------------------------------------------------------------------------------------------
  374. END_NAMESPACE_DISTRHO