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.

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