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.

397 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. @interface NSExternalWindow : NSWindow
  31. @end
  32. @implementation NSExternalWindow
  33. - (id)initWithContentRect:(NSRect)contentRect
  34. styleMask:(unsigned long)aStyle
  35. backing:(NSBackingStoreType)bufferingType
  36. defer:(BOOL)flag
  37. {
  38. NSWindow* result = [super initWithContentRect:contentRect
  39. styleMask:aStyle
  40. backing:bufferingType
  41. defer:flag];
  42. [result setAcceptsMouseMovedEvents:YES];
  43. return (NSExternalWindow*)result;
  44. }
  45. - (BOOL)canBecomeKeyWindow
  46. {
  47. return YES;
  48. }
  49. - (BOOL)canBecomeMainWindow
  50. {
  51. return NO;
  52. }
  53. @end
  54. #endif
  55. START_NAMESPACE_DISTRHO
  56. // -----------------------------------------------------------------------------------------------------------
  57. class EmbedExternalExampleUI : public UI
  58. {
  59. #if defined(DISTRHO_OS_MAC)
  60. NSView* fView;
  61. NSExternalWindow* fWindow;
  62. #elif defined(DISTRHO_OS_WINDOWS)
  63. #else
  64. ::Display* fDisplay;
  65. ::Window fWindow;
  66. #endif
  67. public:
  68. EmbedExternalExampleUI()
  69. : UI(512, 256),
  70. #if defined(DISTRHO_OS_MAC)
  71. fView(nullptr),
  72. fWindow(nullptr),
  73. #elif defined(DISTRHO_OS_WINDOWS)
  74. #else
  75. fDisplay(nullptr),
  76. fWindow(0),
  77. #endif
  78. fValue(0.0f)
  79. {
  80. #if defined(DISTRHO_OS_MAC)
  81. if (isStandalone())
  82. {
  83. [[NSApplication sharedApplication]new];
  84. [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
  85. [NSApp activateIgnoringOtherApps:YES];
  86. }
  87. NSAutoreleasePool* const pool = [[NSAutoreleasePool alloc] init];
  88. [NSApplication sharedApplication];
  89. fView = [NSView new];
  90. DISTRHO_SAFE_ASSERT_RETURN(fView != nullptr,);
  91. [fView initWithFrame:NSMakeRect(0, 0, getWidth(), getHeight())];
  92. [fView setAutoresizesSubviews:YES];
  93. [fView setFrame:NSMakeRect(0, 0, getWidth(), getHeight())];
  94. [fView setHidden:NO];
  95. [fView setNeedsDisplay:YES];
  96. if (isEmbed())
  97. {
  98. [fView retain];
  99. [(NSView*)getParentWindowHandle() addSubview:fView];
  100. }
  101. else
  102. {
  103. fWindow = [[[NSExternalWindow alloc]
  104. initWithContentRect:[fView frame]
  105. styleMask:(NSWindowStyleMaskClosable | NSWindowStyleMaskTitled | NSWindowStyleMaskResizable)
  106. backing:NSBackingStoreBuffered
  107. defer:NO]retain];
  108. DISTRHO_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  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. [fWindow makeKeyAndOrderFront:fWindow];
  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. if (index != 0)
  193. return;
  194. fValue = value;
  195. }
  196. /* --------------------------------------------------------------------------------------------------------
  197. * External Window overrides */
  198. void focus() override
  199. {
  200. d_stdout("focus");
  201. #if defined(DISTRHO_OS_MAC)
  202. DISTRHO_SAFE_ASSERT_RETURN(fWindow != nil,);
  203. [fWindow orderFrontRegardless];
  204. #elif defined(DISTRHO_OS_WINDOWS)
  205. #else
  206. DISTRHO_SAFE_ASSERT_RETURN(fWindow != 0,);
  207. XRaiseWindow(fDisplay, fWindow);
  208. #endif
  209. }
  210. uintptr_t getNativeWindowHandle() const noexcept override
  211. {
  212. #if defined(DISTRHO_OS_MAC)
  213. return (uintptr_t)fView;
  214. #elif defined(DISTRHO_OS_WINDOWS)
  215. #else
  216. return (uintptr_t)fWindow;
  217. #endif
  218. return 0;
  219. }
  220. void titleChanged(const char* const title) override
  221. {
  222. d_stdout("titleChanged %s", title);
  223. #if defined(DISTRHO_OS_MAC)
  224. if (fWindow != nil)
  225. {
  226. if (NSString* const nsTitle = [[NSString alloc]
  227. initWithBytes:title
  228. length:strlen(title)
  229. encoding:NSUTF8StringEncoding])
  230. {
  231. [fWindow setTitle:nsTitle];
  232. [nsTitle release];
  233. }
  234. }
  235. #elif defined(DISTRHO_OS_WINDOWS)
  236. #else
  237. DISTRHO_SAFE_ASSERT_RETURN(fWindow != 0,);
  238. XStoreName(fDisplay, fWindow, title);
  239. #endif
  240. }
  241. void transientParentWindowChanged(const uintptr_t winId) override
  242. {
  243. d_stdout("transientParentWindowChanged %lu", winId);
  244. #if defined(DISTRHO_OS_MAC)
  245. #elif defined(DISTRHO_OS_WINDOWS)
  246. #else
  247. DISTRHO_SAFE_ASSERT_RETURN(fWindow != 0,);
  248. XSetTransientForHint(fDisplay, fWindow, (::Window)winId);
  249. #endif
  250. }
  251. void visibilityChanged(const bool visible) override
  252. {
  253. d_stdout("visibilityChanged %d", visible);
  254. #if defined(DISTRHO_OS_MAC)
  255. DISTRHO_SAFE_ASSERT_RETURN(fView != nullptr,);
  256. if (fWindow != nil)
  257. [fWindow setIsVisible:(visible ? YES : NO)];
  258. else
  259. [fView setHidden:(visible ? NO : YES)];
  260. #elif defined(DISTRHO_OS_WINDOWS)
  261. #else
  262. DISTRHO_SAFE_ASSERT_RETURN(fWindow != 0,);
  263. if (visible)
  264. XMapRaised(fDisplay, fWindow);
  265. else
  266. XUnmapWindow(fDisplay, fWindow);
  267. #endif
  268. }
  269. void uiIdle() override
  270. {
  271. // d_stdout("uiIdle");
  272. #if defined(DISTRHO_OS_MAC)
  273. NSAutoreleasePool* const pool = [[NSAutoreleasePool alloc] init];
  274. NSDate* const date = [NSDate distantPast];
  275. for (NSEvent* event;;)
  276. {
  277. event = [NSApp
  278. nextEventMatchingMask:NSAnyEventMask
  279. untilDate:date
  280. inMode:NSDefaultRunLoopMode
  281. dequeue:YES];
  282. if (event == nil)
  283. break;
  284. d_stdout("uiIdle with event %p", event);
  285. [NSApp sendEvent:event];
  286. }
  287. [pool release];
  288. #elif defined(DISTRHO_OS_WINDOWS)
  289. MSG msg;
  290. if (! ::PeekMessage(&msg, nullptr, 0, 0, PM_NOREMOVE))
  291. return true;
  292. if (::GetMessage(&msg, nullptr, 0, 0) >= 0)
  293. {
  294. if (msg.message == WM_QUIT)
  295. return false;
  296. //TranslateMessage(&msg);
  297. DispatchMessage(&msg);
  298. }
  299. #else
  300. if (fDisplay == nullptr)
  301. return;
  302. for (XEvent event; XPending(fDisplay) > 0;)
  303. {
  304. XNextEvent(fDisplay, &event);
  305. if (! isVisible())
  306. continue;
  307. switch (event.type)
  308. {
  309. case ClientMessage:
  310. if (char* const type = XGetAtomName(fDisplay, event.xclient.message_type))
  311. {
  312. if (std::strcmp(type, "WM_PROTOCOLS") == 0)
  313. hide();
  314. }
  315. break;
  316. case KeyRelease:
  317. if (event.xkey.keycode == X11Key_Escape)
  318. hide();
  319. break;
  320. }
  321. }
  322. #endif
  323. }
  324. // -------------------------------------------------------------------------------------------------------
  325. private:
  326. // Current value, cached for when UI becomes visible
  327. float fValue;
  328. /**
  329. Set our UI class as non-copyable and add a leak detector just in case.
  330. */
  331. DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EmbedExternalExampleUI)
  332. };
  333. /* ------------------------------------------------------------------------------------------------------------
  334. * UI entry point, called by DPF to create a new UI instance. */
  335. UI* createUI()
  336. {
  337. return new EmbedExternalExampleUI();
  338. }
  339. // -----------------------------------------------------------------------------------------------------------
  340. END_NAMESPACE_DISTRHO