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.

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