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.

401 lines
12KB

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