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.

392 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. d_stdout("created window with size %u %u", getWidth(), getHeight());
  120. }
  121. [pool release];
  122. #elif defined(DISTRHO_OS_WINDOWS)
  123. #else
  124. fDisplay = XOpenDisplay(nullptr);
  125. DISTRHO_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  126. const int screen = DefaultScreen(fDisplay);
  127. const ::Window root = RootWindow(fDisplay, screen);
  128. const ::Window parent = isEmbed() ? (::Window)getParentWindowHandle() : root;
  129. fWindow = XCreateSimpleWindow(fDisplay, parent, 0, 0, getWidth(), getHeight(), 0, 0, 0);
  130. DISTRHO_SAFE_ASSERT_RETURN(fWindow != 0,);
  131. XSizeHints sizeHints = {};
  132. sizeHints.flags = PMinSize;
  133. sizeHints.min_width = getWidth();
  134. sizeHints.min_height = getHeight();
  135. XSetNormalHints(fDisplay, fWindow, &sizeHints);
  136. XStoreName(fDisplay, fWindow, getTitle());
  137. if (parent == root)
  138. {
  139. // grab Esc key for auto-close
  140. XGrabKey(fDisplay, X11Key_Escape, AnyModifier, fWindow, 1, GrabModeAsync, GrabModeAsync);
  141. // destroy window on close
  142. Atom wmDelete = XInternAtom(fDisplay, "WM_DELETE_WINDOW", True);
  143. XSetWMProtocols(fDisplay, fWindow, &wmDelete, 1);
  144. // set pid WM hint
  145. const pid_t pid = getpid();
  146. const Atom _nwp = XInternAtom(fDisplay, "_NET_WM_PID", False);
  147. XChangeProperty(fDisplay, fWindow, _nwp, XA_CARDINAL, 32, PropModeReplace, (const uchar*)&pid, 1);
  148. // set the window to both dialog and normal to produce a decorated floating dialog
  149. // order is important: DIALOG needs to come before NORMAL
  150. const Atom _wt = XInternAtom(fDisplay, "_NET_WM_WINDOW_TYPE", False);
  151. const Atom _wts[2] = {
  152. XInternAtom(fDisplay, "_NET_WM_WINDOW_TYPE_DIALOG", False),
  153. XInternAtom(fDisplay, "_NET_WM_WINDOW_TYPE_NORMAL", False)
  154. };
  155. XChangeProperty(fDisplay, fWindow, _wt, XA_ATOM, 32, PropModeReplace, (const uchar*)&_wts, 2);
  156. }
  157. #endif
  158. }
  159. ~EmbedExternalExampleUI()
  160. {
  161. #if defined(DISTRHO_OS_MAC)
  162. if (fView == nullptr)
  163. return;
  164. if (fWindow != nil)
  165. [fWindow close];
  166. [fView release];
  167. if (fWindow != nil)
  168. [fWindow release];
  169. #elif defined(DISTRHO_OS_WINDOWS)
  170. #else
  171. if (fDisplay == nullptr)
  172. return;
  173. if (fWindow != 0)
  174. XDestroyWindow(fDisplay, fWindow);
  175. XCloseDisplay(fDisplay);
  176. #endif
  177. }
  178. protected:
  179. /* --------------------------------------------------------------------------------------------------------
  180. * DSP/Plugin Callbacks */
  181. /**
  182. A parameter has changed on the plugin side.
  183. This is called by the host to inform the UI about parameter changes.
  184. */
  185. void parameterChanged(uint32_t index, float value) override
  186. {
  187. if (index != 0)
  188. return;
  189. fValue = value;
  190. }
  191. /* --------------------------------------------------------------------------------------------------------
  192. * External Window overrides */
  193. void focus() override
  194. {
  195. d_stdout("focus");
  196. #if defined(DISTRHO_OS_MAC)
  197. DISTRHO_SAFE_ASSERT_RETURN(fWindow != nil,);
  198. [fWindow orderFrontRegardless];
  199. #elif defined(DISTRHO_OS_WINDOWS)
  200. #else
  201. DISTRHO_SAFE_ASSERT_RETURN(fWindow != 0,);
  202. XRaiseWindow(fDisplay, fWindow);
  203. #endif
  204. }
  205. uintptr_t getNativeWindowHandle() const noexcept override
  206. {
  207. #if defined(DISTRHO_OS_MAC)
  208. return (uintptr_t)fView;
  209. #elif defined(DISTRHO_OS_WINDOWS)
  210. #else
  211. return (uintptr_t)fWindow;
  212. #endif
  213. return 0;
  214. }
  215. void titleChanged(const char* const title) override
  216. {
  217. d_stdout("titleChanged %s", title);
  218. #if defined(DISTRHO_OS_MAC)
  219. if (fWindow != nil)
  220. {
  221. if (NSString* const nsTitle = [[NSString alloc]
  222. initWithBytes:title
  223. length:strlen(title)
  224. encoding:NSUTF8StringEncoding])
  225. {
  226. [fWindow setTitle:nsTitle];
  227. [nsTitle release];
  228. }
  229. }
  230. #elif defined(DISTRHO_OS_WINDOWS)
  231. #else
  232. DISTRHO_SAFE_ASSERT_RETURN(fWindow != 0,);
  233. XStoreName(fDisplay, fWindow, title);
  234. #endif
  235. }
  236. void transientParentWindowChanged(const uintptr_t winId) override
  237. {
  238. d_stdout("transientParentWindowChanged %lu", winId);
  239. #if defined(DISTRHO_OS_MAC)
  240. #elif defined(DISTRHO_OS_WINDOWS)
  241. #else
  242. DISTRHO_SAFE_ASSERT_RETURN(fWindow != 0,);
  243. XSetTransientForHint(fDisplay, fWindow, (::Window)winId);
  244. #endif
  245. }
  246. void visibilityChanged(const bool visible) override
  247. {
  248. d_stdout("visibilityChanged %d", visible);
  249. #if defined(DISTRHO_OS_MAC)
  250. DISTRHO_SAFE_ASSERT_RETURN(fView != nullptr,);
  251. if (fWindow != nil)
  252. [fWindow setIsVisible:(visible ? YES : NO)];
  253. else
  254. [fView setHidden:(visible ? NO : YES)];
  255. #elif defined(DISTRHO_OS_WINDOWS)
  256. #else
  257. DISTRHO_SAFE_ASSERT_RETURN(fWindow != 0,);
  258. if (visible)
  259. XMapRaised(fDisplay, fWindow);
  260. else
  261. XUnmapWindow(fDisplay, fWindow);
  262. #endif
  263. }
  264. void uiIdle() override
  265. {
  266. // d_stdout("uiIdle");
  267. #if defined(DISTRHO_OS_MAC)
  268. NSAutoreleasePool* const pool = [[NSAutoreleasePool alloc] init];
  269. NSDate* const date = [NSDate distantPast];
  270. for (NSEvent* event;;)
  271. {
  272. event = [NSApp
  273. nextEventMatchingMask:NSAnyEventMask
  274. untilDate:date
  275. inMode:NSDefaultRunLoopMode
  276. dequeue:YES];
  277. if (event == nil)
  278. break;
  279. d_stdout("uiIdle with event %p", event);
  280. [NSApp sendEvent:event];
  281. }
  282. [pool release];
  283. #elif defined(DISTRHO_OS_WINDOWS)
  284. MSG msg;
  285. if (! ::PeekMessage(&msg, nullptr, 0, 0, PM_NOREMOVE))
  286. return true;
  287. if (::GetMessage(&msg, nullptr, 0, 0) >= 0)
  288. {
  289. if (msg.message == WM_QUIT)
  290. return false;
  291. //TranslateMessage(&msg);
  292. DispatchMessage(&msg);
  293. }
  294. #else
  295. if (fDisplay == nullptr)
  296. return;
  297. for (XEvent event; XPending(fDisplay) > 0;)
  298. {
  299. XNextEvent(fDisplay, &event);
  300. if (! isVisible())
  301. continue;
  302. switch (event.type)
  303. {
  304. case ClientMessage:
  305. if (char* const type = XGetAtomName(fDisplay, event.xclient.message_type))
  306. {
  307. if (std::strcmp(type, "WM_PROTOCOLS") == 0)
  308. hide();
  309. }
  310. break;
  311. case KeyRelease:
  312. if (event.xkey.keycode == X11Key_Escape)
  313. hide();
  314. break;
  315. }
  316. }
  317. #endif
  318. }
  319. // -------------------------------------------------------------------------------------------------------
  320. private:
  321. // Current value, cached for when UI becomes visible
  322. float fValue;
  323. /**
  324. Set our UI class as non-copyable and add a leak detector just in case.
  325. */
  326. DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EmbedExternalExampleUI)
  327. };
  328. /* ------------------------------------------------------------------------------------------------------------
  329. * UI entry point, called by DPF to create a new UI instance. */
  330. UI* createUI()
  331. {
  332. return new EmbedExternalExampleUI();
  333. }
  334. // -----------------------------------------------------------------------------------------------------------
  335. END_NAMESPACE_DISTRHO