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.

497 lines
15KB

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