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.

507 lines
16KB

  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_HAIKU)
  20. #elif defined(DISTRHO_OS_MAC)
  21. # import <Cocoa/Cocoa.h>
  22. #elif defined(DISTRHO_OS_WINDOWS)
  23. # define WIN32_CLASS_NAME "DPF-EmbedExternalExampleUI"
  24. # define WIN32_LEAN_AND_MEAN
  25. # include <windows.h>
  26. #else
  27. # include <sys/types.h>
  28. # include <X11/Xatom.h>
  29. # include <X11/Xlib.h>
  30. # include <X11/Xutil.h>
  31. # define X11Key_Escape 9
  32. #endif
  33. #if defined(DISTRHO_OS_MAC)
  34. # ifndef __MAC_10_12
  35. # define NSEventMaskAny NSAnyEventMask
  36. # define NSWindowStyleMaskClosable NSClosableWindowMask
  37. # define NSWindowStyleMaskMiniaturizable NSMiniaturizableWindowMask
  38. # define NSWindowStyleMaskResizable NSResizableWindowMask
  39. # define NSWindowStyleMaskTitled NSTitledWindowMask
  40. # endif
  41. @interface NSExternalWindow : NSWindow
  42. @end
  43. @implementation NSExternalWindow {
  44. @public
  45. bool closed;
  46. bool standalone;
  47. }
  48. - (BOOL)canBecomeKeyWindow { return YES; }
  49. - (BOOL)canBecomeMainWindow { return standalone ? YES : NO; }
  50. - (BOOL)windowShouldClose:(id)_ { closed = true; return YES; }
  51. @end
  52. #endif
  53. START_NAMESPACE_DISTRHO
  54. // -----------------------------------------------------------------------------------------------------------
  55. class EmbedExternalExampleUI : public UI
  56. {
  57. #if defined(DISTRHO_OS_HAIKU)
  58. char d;
  59. #elif defined(DISTRHO_OS_MAC)
  60. NSView* fView;
  61. NSExternalWindow* fWindow;
  62. #elif defined(DISTRHO_OS_WINDOWS)
  63. ::HWND fWindow;
  64. #else
  65. ::Display* fDisplay;
  66. ::Window fWindow;
  67. #endif
  68. public:
  69. EmbedExternalExampleUI()
  70. : UI(512, 256),
  71. #if defined(DISTRHO_OS_HAIKU)
  72. d(0)
  73. #elif defined(DISTRHO_OS_MAC)
  74. fView(nullptr),
  75. fWindow(nullptr)
  76. #elif defined(DISTRHO_OS_WINDOWS)
  77. fWindow(nullptr)
  78. #else
  79. fDisplay(nullptr),
  80. fWindow(0)
  81. #endif
  82. {
  83. const bool standalone = isStandalone();
  84. d_stdout("isStandalone %d", (int)standalone);
  85. #if defined(DISTRHO_OS_HAIKU)
  86. #elif defined(DISTRHO_OS_MAC)
  87. NSAutoreleasePool* const pool = [[NSAutoreleasePool alloc]init];
  88. [NSApplication sharedApplication];
  89. if (standalone)
  90. {
  91. [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
  92. [NSApp activateIgnoringOtherApps:YES];
  93. }
  94. fView = [NSView new];
  95. DISTRHO_SAFE_ASSERT_RETURN(fView != nullptr,);
  96. [fView setFrame:NSMakeRect(0, 0, getWidth(), getHeight())];
  97. [fView setAutoresizesSubviews:YES];
  98. [fView setWantsLayer:YES];
  99. [[fView layer] setBackgroundColor:[[NSColor blueColor] CGColor]];
  100. if (isEmbed())
  101. {
  102. [fView retain];
  103. [(NSView*)getParentWindowHandle() addSubview:fView];
  104. }
  105. else
  106. {
  107. const ulong styleMask = NSWindowStyleMaskClosable
  108. | NSWindowStyleMaskMiniaturizable
  109. | NSWindowStyleMaskResizable
  110. | NSWindowStyleMaskTitled;
  111. fWindow = [[[NSExternalWindow alloc]
  112. initWithContentRect:[fView frame]
  113. styleMask:styleMask
  114. backing:NSBackingStoreBuffered
  115. defer:NO]retain];
  116. DISTRHO_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  117. fWindow->closed = false; // is this needed?
  118. fWindow->standalone = standalone;
  119. [fWindow setIsVisible:NO];
  120. if (NSString* const nsTitle = [[NSString alloc]
  121. initWithBytes:getTitle()
  122. length:strlen(getTitle())
  123. encoding:NSUTF8StringEncoding])
  124. [fWindow setTitle:nsTitle];
  125. [fWindow setContentView:fView];
  126. [fWindow setContentSize:NSMakeSize(getWidth(), getHeight())];
  127. [fWindow makeFirstResponder:fView];
  128. }
  129. [pool release];
  130. #elif defined(DISTRHO_OS_WINDOWS)
  131. WNDCLASS windowClass = {};
  132. windowClass.style = CS_OWNDC;
  133. windowClass.lpfnWndProc = DefWindowProc;
  134. windowClass.hInstance = nullptr;
  135. windowClass.hIcon = LoadIcon(nullptr, IDI_APPLICATION);
  136. windowClass.hCursor = LoadCursor(nullptr, IDC_ARROW);
  137. windowClass.lpszClassName = WIN32_CLASS_NAME;
  138. DISTRHO_SAFE_ASSERT_RETURN(RegisterClass(&windowClass),);
  139. const int winFlags = isEmbed() ? (WS_CHILD | WS_VISIBLE) : (WS_POPUPWINDOW | WS_CAPTION | WS_SIZEBOX);
  140. RECT rect = { 0, 0, static_cast<LONG>(getWidth()), static_cast<LONG>(getHeight()) };
  141. AdjustWindowRectEx(&rect, winFlags, FALSE, WS_EX_TOPMOST);
  142. fWindow = CreateWindowEx(WS_EX_TOPMOST,
  143. WIN32_CLASS_NAME,
  144. getTitle(),
  145. winFlags,
  146. CW_USEDEFAULT, CW_USEDEFAULT,
  147. rect.right - rect.left,
  148. rect.bottom - rect.top,
  149. (HWND)getParentWindowHandle(),
  150. nullptr, nullptr, nullptr);
  151. DISTRHO_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  152. SetWindowLongPtr(fWindow, GWLP_USERDATA, (LONG_PTR)this);
  153. #else
  154. fDisplay = XOpenDisplay(nullptr);
  155. DISTRHO_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  156. const ::Window parent = isEmbed()
  157. ? (::Window)getParentWindowHandle()
  158. : RootWindow(fDisplay, DefaultScreen(fDisplay));
  159. fWindow = XCreateSimpleWindow(fDisplay, parent, 0, 0, getWidth(), getHeight(), 0, 0, 0);
  160. DISTRHO_SAFE_ASSERT_RETURN(fWindow != 0,);
  161. XSizeHints sizeHints = {};
  162. sizeHints.flags = PMinSize;
  163. sizeHints.min_width = getWidth();
  164. sizeHints.min_height = getHeight();
  165. XSetNormalHints(fDisplay, fWindow, &sizeHints);
  166. XStoreName(fDisplay, fWindow, getTitle());
  167. if (isEmbed())
  168. {
  169. // start with window mapped, so host can access it
  170. XMapWindow(fDisplay, fWindow);
  171. }
  172. else
  173. {
  174. // grab Esc key for auto-close
  175. XGrabKey(fDisplay, X11Key_Escape, AnyModifier, fWindow, 1, GrabModeAsync, GrabModeAsync);
  176. // destroy window on close
  177. Atom wmDelete = XInternAtom(fDisplay, "WM_DELETE_WINDOW", True);
  178. XSetWMProtocols(fDisplay, fWindow, &wmDelete, 1);
  179. // set pid WM hint
  180. const pid_t pid = getpid();
  181. const Atom _nwp = XInternAtom(fDisplay, "_NET_WM_PID", False);
  182. XChangeProperty(fDisplay, fWindow, _nwp, XA_CARDINAL, 32, PropModeReplace, (const uchar*)&pid, 1);
  183. // set the window to both dialog and normal to produce a decorated floating dialog
  184. // order is important: DIALOG needs to come before NORMAL
  185. const Atom _wt = XInternAtom(fDisplay, "_NET_WM_WINDOW_TYPE", False);
  186. const Atom _wts[2] = {
  187. XInternAtom(fDisplay, "_NET_WM_WINDOW_TYPE_DIALOG", False),
  188. XInternAtom(fDisplay, "_NET_WM_WINDOW_TYPE_NORMAL", False)
  189. };
  190. XChangeProperty(fDisplay, fWindow, _wt, XA_ATOM, 32, PropModeReplace, (const uchar*)&_wts, 2);
  191. }
  192. #endif
  193. d_stdout("created external window with size %u %u", getWidth(), getHeight());
  194. }
  195. ~EmbedExternalExampleUI()
  196. {
  197. #if defined(DISTRHO_OS_HAIKU)
  198. #elif defined(DISTRHO_OS_MAC)
  199. if (fView == nullptr)
  200. return;
  201. if (fWindow != nil)
  202. [fWindow close];
  203. [fView release];
  204. if (fWindow != nil)
  205. [fWindow release];
  206. #elif defined(DISTRHO_OS_WINDOWS)
  207. if (fWindow != nullptr)
  208. DestroyWindow(fWindow);
  209. UnregisterClass(WIN32_CLASS_NAME, nullptr);
  210. #else
  211. if (fDisplay == nullptr)
  212. return;
  213. if (fWindow != 0)
  214. XDestroyWindow(fDisplay, fWindow);
  215. XCloseDisplay(fDisplay);
  216. #endif
  217. }
  218. protected:
  219. /* --------------------------------------------------------------------------------------------------------
  220. * DSP/Plugin Callbacks */
  221. /**
  222. A parameter has changed on the plugin side.
  223. This is called by the host to inform the UI about parameter changes.
  224. */
  225. void parameterChanged(uint32_t index, float value) override
  226. {
  227. d_stdout("parameterChanged %u %f", index, value);
  228. switch (index)
  229. {
  230. case kParameterWidth:
  231. setWidth(static_cast<int>(value + 0.5f));
  232. break;
  233. case kParameterHeight:
  234. setHeight(static_cast<int>(value + 0.5f));
  235. break;
  236. }
  237. }
  238. /* --------------------------------------------------------------------------------------------------------
  239. * External Window overrides */
  240. void focus() override
  241. {
  242. d_stdout("focus");
  243. #if defined(DISTRHO_OS_HAIKU)
  244. #elif defined(DISTRHO_OS_MAC)
  245. DISTRHO_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  246. [fWindow orderFrontRegardless];
  247. [fWindow makeKeyWindow];
  248. [fWindow makeFirstResponder:fView];
  249. #elif defined(DISTRHO_OS_WINDOWS)
  250. DISTRHO_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  251. SetForegroundWindow(fWindow);
  252. SetActiveWindow(fWindow);
  253. SetFocus(fWindow);
  254. #else
  255. DISTRHO_SAFE_ASSERT_RETURN(fWindow != 0,);
  256. XRaiseWindow(fDisplay, fWindow);
  257. #endif
  258. }
  259. uintptr_t getNativeWindowHandle() const noexcept override
  260. {
  261. #if defined(DISTRHO_OS_HAIKU)
  262. #elif defined(DISTRHO_OS_MAC)
  263. return (uintptr_t)fView;
  264. #elif defined(DISTRHO_OS_WINDOWS)
  265. return (uintptr_t)fWindow;
  266. #else
  267. return (uintptr_t)fWindow;
  268. #endif
  269. }
  270. void sizeChanged(uint width, uint height) override
  271. {
  272. d_stdout("sizeChanged %u %u", width, height);
  273. UI::sizeChanged(width, height);
  274. #if defined(DISTRHO_OS_HAIKU)
  275. #elif defined(DISTRHO_OS_MAC)
  276. NSRect rect = [fView frame];
  277. rect.size = CGSizeMake((CGFloat)width, (CGFloat)height);
  278. [fView setFrame:rect];
  279. #elif defined(DISTRHO_OS_WINDOWS)
  280. if (fWindow != nullptr)
  281. SetWindowPos(fWindow,
  282. HWND_TOP,
  283. 0, 0,
  284. width, height,
  285. SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER);
  286. #else
  287. if (fWindow != 0)
  288. XResizeWindow(fDisplay, fWindow, width, height);
  289. #endif
  290. }
  291. void titleChanged(const char* const title) override
  292. {
  293. d_stdout("titleChanged %s", title);
  294. #if defined(DISTRHO_OS_HAIKU)
  295. #elif defined(DISTRHO_OS_MAC)
  296. if (fWindow != nil)
  297. {
  298. if (NSString* const nsTitle = [[NSString alloc]
  299. initWithBytes:title
  300. length:strlen(title)
  301. encoding:NSUTF8StringEncoding])
  302. {
  303. [fWindow setTitle:nsTitle];
  304. [nsTitle release];
  305. }
  306. }
  307. #elif defined(DISTRHO_OS_WINDOWS)
  308. #else
  309. DISTRHO_SAFE_ASSERT_RETURN(fWindow != 0,);
  310. XStoreName(fDisplay, fWindow, title);
  311. #endif
  312. }
  313. void transientParentWindowChanged(const uintptr_t winId) override
  314. {
  315. d_stdout("transientParentWindowChanged %lu", winId);
  316. #if defined(DISTRHO_OS_HAIKU)
  317. #elif defined(DISTRHO_OS_MAC)
  318. #elif defined(DISTRHO_OS_WINDOWS)
  319. #else
  320. DISTRHO_SAFE_ASSERT_RETURN(fWindow != 0,);
  321. XSetTransientForHint(fDisplay, fWindow, (::Window)winId);
  322. #endif
  323. }
  324. void visibilityChanged(const bool visible) override
  325. {
  326. d_stdout("visibilityChanged %d", visible);
  327. #if defined(DISTRHO_OS_HAIKU)
  328. #elif defined(DISTRHO_OS_MAC)
  329. DISTRHO_SAFE_ASSERT_RETURN(fView != nullptr,);
  330. if (fWindow != nil)
  331. {
  332. [fWindow setIsVisible:(visible ? YES : NO)];
  333. if (isStandalone())
  334. [fWindow makeMainWindow];
  335. [fWindow makeKeyAndOrderFront:fWindow];
  336. }
  337. else
  338. {
  339. [fView setHidden:(visible ? NO : YES)];
  340. }
  341. #elif defined(DISTRHO_OS_WINDOWS)
  342. DISTRHO_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  343. ShowWindow(fWindow, visible ? SW_SHOWNORMAL : SW_HIDE);
  344. #else
  345. DISTRHO_SAFE_ASSERT_RETURN(fWindow != 0,);
  346. if (visible)
  347. XMapRaised(fDisplay, fWindow);
  348. else
  349. XUnmapWindow(fDisplay, fWindow);
  350. #endif
  351. }
  352. void uiIdle() override
  353. {
  354. // d_stdout("uiIdle");
  355. #if defined(DISTRHO_OS_HAIKU)
  356. #elif defined(DISTRHO_OS_MAC)
  357. if (isEmbed()) {
  358. return;
  359. }
  360. NSAutoreleasePool* const pool = [[NSAutoreleasePool alloc] init];
  361. NSDate* const date = [NSDate distantPast];
  362. for (NSEvent* event;;)
  363. {
  364. event = [NSApp
  365. nextEventMatchingMask:NSEventMaskAny
  366. untilDate:date
  367. inMode:NSDefaultRunLoopMode
  368. dequeue:YES];
  369. if (event == nil)
  370. break;
  371. [NSApp sendEvent:event];
  372. }
  373. if (fWindow->closed)
  374. {
  375. fWindow->closed = false;
  376. close();
  377. }
  378. [pool release];
  379. #elif defined(DISTRHO_OS_WINDOWS)
  380. if (fWindow == nullptr)
  381. return;
  382. /*
  383. MSG msg;
  384. if (! ::PeekMessage(&msg, fWindow, 0, 0, PM_NOREMOVE))
  385. return true;
  386. if (::GetMessage(&msg, nullptr, 0, 0) >= 0)
  387. {
  388. if (msg.message == WM_QUIT)
  389. return false;
  390. //TranslateMessage(&msg);
  391. DispatchMessage(&msg);
  392. }
  393. */
  394. #else
  395. if (fDisplay == nullptr)
  396. return;
  397. for (XEvent event; XPending(fDisplay) > 0;)
  398. {
  399. XNextEvent(fDisplay, &event);
  400. if (! isVisible())
  401. continue;
  402. switch (event.type)
  403. {
  404. case ClientMessage:
  405. if (char* const type = XGetAtomName(fDisplay, event.xclient.message_type))
  406. {
  407. if (std::strcmp(type, "WM_PROTOCOLS") == 0)
  408. hide();
  409. }
  410. break;
  411. case KeyRelease:
  412. if (event.xkey.keycode == X11Key_Escape)
  413. hide();
  414. break;
  415. }
  416. }
  417. #endif
  418. }
  419. // -------------------------------------------------------------------------------------------------------
  420. /**
  421. Set our UI class as non-copyable and add a leak detector just in case.
  422. */
  423. DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EmbedExternalExampleUI)
  424. };
  425. /* ------------------------------------------------------------------------------------------------------------
  426. * UI entry point, called by DPF to create a new UI instance. */
  427. UI* createUI()
  428. {
  429. return new EmbedExternalExampleUI();
  430. }
  431. // -----------------------------------------------------------------------------------------------------------
  432. END_NAMESPACE_DISTRHO