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.

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