Audio plugin host https://kx.studio/carla
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.

1324 lines
39KB

  1. /*
  2. * Carla Plugin UI
  3. * Copyright (C) 2014-2018 Filipe Coelho <falktx@falktx.com>
  4. *
  5. * This program is free software; you can redistribute it and/or
  6. * modify it under the terms of the GNU General Public License as
  7. * published by the Free Software Foundation; either version 2 of
  8. * the License, or any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * For a full copy of the GNU General Public License see the doc/GPL.txt file.
  16. */
  17. #include "CarlaJuceUtils.hpp"
  18. #include "CarlaPluginUI.hpp"
  19. #ifdef HAVE_X11
  20. # include <sys/types.h>
  21. # include <X11/Xatom.h>
  22. # include <X11/Xlib.h>
  23. # include <X11/Xutil.h>
  24. # include "CarlaPluginUI_X11Icon.hpp"
  25. #endif
  26. #ifdef CARLA_OS_MAC
  27. # include "CarlaMacUtils.hpp"
  28. # import <Cocoa/Cocoa.h>
  29. #endif
  30. #ifdef CARLA_OS_WIN
  31. # include <ctime>
  32. # include "water/common.hpp"
  33. #endif
  34. #ifndef CARLA_PLUGIN_UI_CLASS_PREFIX
  35. # error CARLA_PLUGIN_UI_CLASS_PREFIX undefined
  36. #endif
  37. // ---------------------------------------------------------------------------------------------------------------------
  38. // X11
  39. #ifdef HAVE_X11
  40. typedef void (*EventProcPtr)(XEvent* ev);
  41. static const uint X11Key_Escape = 9;
  42. static bool gErrorTriggered = false;
  43. static int temporaryErrorHandler(Display*, XErrorEvent*)
  44. {
  45. gErrorTriggered = true;
  46. return 0;
  47. }
  48. class X11PluginUI : public CarlaPluginUI
  49. {
  50. public:
  51. X11PluginUI(Callback* const cb, const uintptr_t parentId, const bool isResizable) noexcept
  52. : CarlaPluginUI(cb, isResizable),
  53. fDisplay(nullptr),
  54. fHostWindow(0),
  55. fChildWindow(0),
  56. fChildWindowConfigured(false),
  57. fIsVisible(false),
  58. fFirstShow(true),
  59. fSetSizeCalledAtLeastOnce(false),
  60. fEventProc(nullptr)
  61. {
  62. fDisplay = XOpenDisplay(nullptr);
  63. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  64. const int screen = DefaultScreen(fDisplay);
  65. XSetWindowAttributes attr;
  66. carla_zeroStruct(attr);
  67. attr.border_pixel = 0;
  68. attr.event_mask = KeyPressMask|KeyReleaseMask;
  69. if (fIsResizable)
  70. attr.event_mask |= StructureNotifyMask;
  71. fHostWindow = XCreateWindow(fDisplay, RootWindow(fDisplay, screen),
  72. 0, 0, 300, 300, 0,
  73. DefaultDepth(fDisplay, screen),
  74. InputOutput,
  75. DefaultVisual(fDisplay, screen),
  76. CWBorderPixel|CWEventMask, &attr);
  77. CARLA_SAFE_ASSERT_RETURN(fHostWindow != 0,);
  78. XGrabKey(fDisplay, X11Key_Escape, AnyModifier, fHostWindow, 1, GrabModeAsync, GrabModeAsync);
  79. Atom wmDelete = XInternAtom(fDisplay, "WM_DELETE_WINDOW", True);
  80. XSetWMProtocols(fDisplay, fHostWindow, &wmDelete, 1);
  81. const pid_t pid = getpid();
  82. const Atom _nwp = XInternAtom(fDisplay, "_NET_WM_PID", False);
  83. XChangeProperty(fDisplay, fHostWindow, _nwp, XA_CARDINAL, 32, PropModeReplace, (const uchar*)&pid, 1);
  84. const Atom _nwi = XInternAtom(fDisplay, "_NET_WM_ICON", False);
  85. XChangeProperty(fDisplay, fHostWindow, _nwi, XA_CARDINAL, 32, PropModeReplace, (const uchar*)sCarlaX11Icon, sCarlaX11IconSize);
  86. const Atom _wt = XInternAtom(fDisplay, "_NET_WM_WINDOW_TYPE", False);
  87. // Setting the window to both dialog and normal will produce a decorated floating dialog
  88. // Order is important: DIALOG needs to come before NORMAL
  89. const Atom _wts[2] = {
  90. XInternAtom(fDisplay, "_NET_WM_WINDOW_TYPE_DIALOG", False),
  91. XInternAtom(fDisplay, "_NET_WM_WINDOW_TYPE_NORMAL", False)
  92. };
  93. XChangeProperty(fDisplay, fHostWindow, _wt, XA_ATOM, 32, PropModeReplace, (const uchar*)&_wts, 2);
  94. if (parentId != 0)
  95. setTransientWinId(parentId);
  96. }
  97. ~X11PluginUI() override
  98. {
  99. CARLA_SAFE_ASSERT(! fIsVisible);
  100. if (fIsVisible)
  101. {
  102. XUnmapWindow(fDisplay, fHostWindow);
  103. fIsVisible = false;
  104. }
  105. if (fHostWindow != 0)
  106. {
  107. XDestroyWindow(fDisplay, fHostWindow);
  108. fHostWindow = 0;
  109. }
  110. if (fDisplay != nullptr)
  111. {
  112. XCloseDisplay(fDisplay);
  113. fDisplay = nullptr;
  114. }
  115. }
  116. void show() override
  117. {
  118. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  119. CARLA_SAFE_ASSERT_RETURN(fHostWindow != 0,);
  120. if (fFirstShow)
  121. {
  122. if (const Window childWindow = getChildWindow())
  123. {
  124. if (! fSetSizeCalledAtLeastOnce)
  125. {
  126. XSizeHints hints;
  127. carla_zeroStruct(hints);
  128. if (XGetNormalHints(fDisplay, childWindow, &hints) && hints.width > 0 && hints.height > 0)
  129. {
  130. setSize(static_cast<uint>(hints.width),
  131. static_cast<uint>(hints.height), false);
  132. }
  133. }
  134. const Atom _xevp = XInternAtom(fDisplay, "_XEventProc", False);
  135. gErrorTriggered = false;
  136. const XErrorHandler oldErrorHandler(XSetErrorHandler(temporaryErrorHandler));
  137. Atom actualType;
  138. int actualFormat;
  139. ulong nitems, bytesAfter;
  140. uchar* data = nullptr;
  141. XGetWindowProperty(fDisplay, childWindow, _xevp, 0, 1, False, AnyPropertyType, &actualType, &actualFormat, &nitems, &bytesAfter, &data);
  142. XSetErrorHandler(oldErrorHandler);
  143. if (nitems == 1 && ! gErrorTriggered)
  144. {
  145. fEventProc = *reinterpret_cast<EventProcPtr*>(data);
  146. XMapRaised(fDisplay, childWindow);
  147. }
  148. }
  149. }
  150. fIsVisible = true;
  151. fFirstShow = false;
  152. XMapRaised(fDisplay, fHostWindow);
  153. XSync(fDisplay, False);
  154. }
  155. void hide() override
  156. {
  157. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  158. CARLA_SAFE_ASSERT_RETURN(fHostWindow != 0,);
  159. fIsVisible = false;
  160. XUnmapWindow(fDisplay, fHostWindow);
  161. XFlush(fDisplay);
  162. }
  163. void idle() override
  164. {
  165. // prevent recursion
  166. if (fIsIdling) return;
  167. fIsIdling = true;
  168. for (XEvent event; XPending(fDisplay) > 0;)
  169. {
  170. XNextEvent(fDisplay, &event);
  171. if (! fIsVisible)
  172. continue;
  173. char* type = nullptr;
  174. switch (event.type)
  175. {
  176. case ConfigureNotify:
  177. CARLA_SAFE_ASSERT_CONTINUE(fCallback != nullptr);
  178. CARLA_SAFE_ASSERT_CONTINUE(event.xconfigure.width > 0);
  179. CARLA_SAFE_ASSERT_CONTINUE(event.xconfigure.height > 0);
  180. {
  181. const uint width = static_cast<uint>(event.xconfigure.width);
  182. const uint height = static_cast<uint>(event.xconfigure.height);
  183. if (fChildWindow != 0)
  184. {
  185. if (! fChildWindowConfigured)
  186. {
  187. gErrorTriggered = false;
  188. const XErrorHandler oldErrorHandler = XSetErrorHandler(temporaryErrorHandler);
  189. XSizeHints sizeHints;
  190. carla_zeroStruct(sizeHints);
  191. if (XGetNormalHints(fDisplay, fChildWindow, &sizeHints) && !gErrorTriggered)
  192. XSetNormalHints(fDisplay, fHostWindow, &sizeHints);
  193. else
  194. fChildWindow = 0;
  195. fChildWindowConfigured = true;
  196. XSetErrorHandler(oldErrorHandler);
  197. }
  198. if (fChildWindow != 0)
  199. XResizeWindow(fDisplay, fChildWindow, width, height);
  200. }
  201. fCallback->handlePluginUIResized(width, height);
  202. }
  203. break;
  204. case ClientMessage:
  205. type = XGetAtomName(fDisplay, event.xclient.message_type);
  206. CARLA_SAFE_ASSERT_CONTINUE(type != nullptr);
  207. if (std::strcmp(type, "WM_PROTOCOLS") == 0)
  208. {
  209. fIsVisible = false;
  210. CARLA_SAFE_ASSERT_CONTINUE(fCallback != nullptr);
  211. fCallback->handlePluginUIClosed();
  212. }
  213. break;
  214. case KeyRelease:
  215. if (event.xkey.keycode == X11Key_Escape)
  216. {
  217. fIsVisible = false;
  218. CARLA_SAFE_ASSERT_CONTINUE(fCallback != nullptr);
  219. fCallback->handlePluginUIClosed();
  220. }
  221. break;
  222. }
  223. if (type != nullptr)
  224. XFree(type);
  225. else if (fEventProc != nullptr)
  226. fEventProc(&event);
  227. }
  228. fIsIdling = false;
  229. }
  230. void focus() override
  231. {
  232. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  233. CARLA_SAFE_ASSERT_RETURN(fHostWindow != 0,);
  234. XWindowAttributes wa;
  235. carla_zeroStruct(wa);
  236. CARLA_SAFE_ASSERT_RETURN(XGetWindowAttributes(fDisplay, fHostWindow, &wa),);
  237. if (wa.map_state == IsViewable)
  238. {
  239. XRaiseWindow(fDisplay, fHostWindow);
  240. XSetInputFocus(fDisplay, fHostWindow, RevertToPointerRoot, CurrentTime);
  241. XSync(fDisplay, False);
  242. }
  243. }
  244. void setSize(const uint width, const uint height, const bool forceUpdate) override
  245. {
  246. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  247. CARLA_SAFE_ASSERT_RETURN(fHostWindow != 0,);
  248. fSetSizeCalledAtLeastOnce = true;
  249. XResizeWindow(fDisplay, fHostWindow, width, height);
  250. if (fChildWindow != 0)
  251. XResizeWindow(fDisplay, fChildWindow, width, height);
  252. if (! fIsResizable)
  253. {
  254. XSizeHints sizeHints;
  255. carla_zeroStruct(sizeHints);
  256. sizeHints.flags = PSize|PMinSize|PMaxSize;
  257. sizeHints.width = static_cast<int>(width);
  258. sizeHints.height = static_cast<int>(height);
  259. sizeHints.min_width = static_cast<int>(width);
  260. sizeHints.min_height = static_cast<int>(height);
  261. sizeHints.max_width = static_cast<int>(width);
  262. sizeHints.max_height = static_cast<int>(height);
  263. XSetNormalHints(fDisplay, fHostWindow, &sizeHints);
  264. }
  265. if (forceUpdate)
  266. XSync(fDisplay, False);
  267. }
  268. void setTitle(const char* const title) override
  269. {
  270. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  271. CARLA_SAFE_ASSERT_RETURN(fHostWindow != 0,);
  272. XStoreName(fDisplay, fHostWindow, title);
  273. }
  274. void setTransientWinId(const uintptr_t winId) override
  275. {
  276. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  277. CARLA_SAFE_ASSERT_RETURN(fHostWindow != 0,);
  278. XSetTransientForHint(fDisplay, fHostWindow, static_cast<Window>(winId));
  279. }
  280. void setChildWindow(void* const winId) override
  281. {
  282. CARLA_SAFE_ASSERT_RETURN(winId != nullptr,);
  283. fChildWindow = (Window)winId;
  284. }
  285. void* getPtr() const noexcept override
  286. {
  287. return (void*)fHostWindow;
  288. }
  289. void* getDisplay() const noexcept override
  290. {
  291. return fDisplay;
  292. }
  293. protected:
  294. Window getChildWindow() const
  295. {
  296. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr, 0);
  297. CARLA_SAFE_ASSERT_RETURN(fHostWindow != 0, 0);
  298. Window rootWindow, parentWindow, ret = 0;
  299. Window* childWindows = nullptr;
  300. uint numChildren = 0;
  301. XQueryTree(fDisplay, fHostWindow, &rootWindow, &parentWindow, &childWindows, &numChildren);
  302. if (numChildren > 0 && childWindows != nullptr)
  303. {
  304. ret = childWindows[0];
  305. XFree(childWindows);
  306. }
  307. return ret;
  308. }
  309. private:
  310. Display* fDisplay;
  311. Window fHostWindow;
  312. Window fChildWindow;
  313. bool fChildWindowConfigured;
  314. bool fIsVisible;
  315. bool fFirstShow;
  316. bool fSetSizeCalledAtLeastOnce;
  317. EventProcPtr fEventProc;
  318. CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(X11PluginUI)
  319. };
  320. #endif // HAVE_X11
  321. // ---------------------------------------------------------------------------------------------------------------------
  322. // MacOS / Cocoa
  323. #ifdef CARLA_OS_MAC
  324. #if defined(BUILD_BRIDGE_ALTERNATIVE_ARCH)
  325. # define CarlaPluginWindow CARLA_JOIN_MACRO(CarlaPluginWindowBridgedArch, CARLA_PLUGIN_UI_CLASS_PREFIX)
  326. #elif defined(BUILD_BRIDGE)
  327. # define CarlaPluginWindow CARLA_JOIN_MACRO(CarlaPluginWindowBridged, CARLA_PLUGIN_UI_CLASS_PREFIX)
  328. #else
  329. # define CarlaPluginWindow CARLA_JOIN_MACRO(CarlaPluginWindow, CARLA_PLUGIN_UI_CLASS_PREFIX)
  330. #endif
  331. @interface CarlaPluginWindow : NSWindow
  332. {
  333. @public
  334. CarlaPluginUI::Callback* callback;
  335. }
  336. - (id) initWithContentRect:(NSRect)contentRect
  337. styleMask:(unsigned int)aStyle
  338. backing:(NSBackingStoreType)bufferingType
  339. defer:(BOOL)flag;
  340. - (void) setCallback:(CarlaPluginUI::Callback*)cb;
  341. - (BOOL) acceptsFirstResponder;
  342. - (BOOL) canBecomeKeyWindow;
  343. - (BOOL) canBecomeMainWindow;
  344. - (BOOL) windowShouldClose:(id)sender;
  345. - (NSSize) windowWillResize:(NSWindow*)sender toSize:(NSSize)frameSize;
  346. @end
  347. @implementation CarlaPluginWindow
  348. - (id)initWithContentRect:(NSRect)contentRect
  349. styleMask:(unsigned int)aStyle
  350. backing:(NSBackingStoreType)bufferingType
  351. defer:(BOOL)flag
  352. {
  353. callback = nil;
  354. NSWindow* result = [super initWithContentRect:contentRect
  355. styleMask:aStyle
  356. backing:bufferingType
  357. defer:flag];
  358. [result setAcceptsMouseMovedEvents:YES];
  359. return (CarlaPluginWindow*)result;
  360. // unused
  361. (void)flag;
  362. }
  363. - (void)setCallback:(CarlaPluginUI::Callback*)cb
  364. {
  365. callback = cb;
  366. }
  367. - (BOOL)acceptsFirstResponder
  368. {
  369. return YES;
  370. }
  371. - (BOOL)canBecomeKeyWindow
  372. {
  373. return YES;
  374. }
  375. - (BOOL)canBecomeMainWindow
  376. {
  377. return NO;
  378. }
  379. - (BOOL)windowShouldClose:(id)sender
  380. {
  381. if (callback != nil)
  382. callback->handlePluginUIClosed();
  383. return NO;
  384. // unused
  385. (void)sender;
  386. }
  387. - (NSSize) windowWillResize:(NSWindow*)sender toSize:(NSSize)frameSize
  388. {
  389. if (callback != nil)
  390. callback->handlePluginUIResized(frameSize.width, frameSize.height);
  391. return frameSize;
  392. // unused
  393. (void)sender;
  394. }
  395. @end
  396. class CocoaPluginUI : public CarlaPluginUI
  397. {
  398. public:
  399. CocoaPluginUI(Callback* const cb, const uintptr_t parentId, const bool isResizable) noexcept
  400. : CarlaPluginUI(cb, isResizable),
  401. fView(nullptr),
  402. fParentWindow(nullptr),
  403. fWindow(nullptr)
  404. {
  405. carla_debug("CocoaPluginUI::CocoaPluginUI(%p, " P_UINTPTR, "%s)", cb, parentId, bool2str(isResizable));
  406. const CarlaBackend::AutoNSAutoreleasePool arp;
  407. [NSApplication sharedApplication];
  408. fView = [[NSView new]retain];
  409. CARLA_SAFE_ASSERT_RETURN(fView != nullptr,)
  410. uint style = NSClosableWindowMask | NSTitledWindowMask;
  411. /*
  412. if (isResizable)
  413. style |= NSResizableWindowMask;
  414. */
  415. NSRect frame = NSMakeRect(0, 0, 100, 100);
  416. fWindow = [[[CarlaPluginWindow alloc]
  417. initWithContentRect:frame
  418. styleMask:style
  419. backing:NSBackingStoreBuffered
  420. defer:NO
  421. ] retain];
  422. if (fWindow == nullptr)
  423. {
  424. [fView release];
  425. fView = nullptr;
  426. return;
  427. }
  428. //if (! isResizable)
  429. {
  430. [fView setAutoresizingMask:NSViewNotSizable];
  431. [[fWindow standardWindowButton:NSWindowZoomButton] setHidden:YES];
  432. }
  433. [fWindow setCallback:cb];
  434. [fWindow setContentView:fView];
  435. [fWindow makeFirstResponder:fView];
  436. [fView setHidden:NO];
  437. if (parentId != 0)
  438. setTransientWinId(parentId);
  439. }
  440. ~CocoaPluginUI() override
  441. {
  442. carla_debug("CocoaPluginUI::~CocoaPluginUI()");
  443. if (fView == nullptr)
  444. return;
  445. [fView setHidden:YES];
  446. [fView removeFromSuperview];
  447. [fWindow close];
  448. [fView release];
  449. [fWindow release];
  450. }
  451. void show() override
  452. {
  453. carla_debug("CocoaPluginUI::show()");
  454. CARLA_SAFE_ASSERT_RETURN(fView != nullptr,);
  455. if (fParentWindow != nullptr)
  456. {
  457. [fParentWindow addChildWindow:fWindow
  458. ordered:NSWindowAbove];
  459. }
  460. else
  461. {
  462. [fWindow setIsVisible:YES];
  463. }
  464. }
  465. void hide() override
  466. {
  467. carla_debug("CocoaPluginUI::hide()");
  468. CARLA_SAFE_ASSERT_RETURN(fView != nullptr,);
  469. [fWindow setIsVisible:NO];
  470. if (fParentWindow != nullptr)
  471. [fParentWindow removeChildWindow:fWindow];
  472. }
  473. void idle() override
  474. {
  475. // carla_debug("CocoaPluginUI::idle()");
  476. }
  477. void focus() override
  478. {
  479. carla_debug("CocoaPluginUI::focus()");
  480. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  481. [fWindow makeKeyAndOrderFront:fWindow];
  482. [NSApp activateIgnoringOtherApps:YES];
  483. }
  484. void setSize(const uint width, const uint height, const bool forceUpdate) override
  485. {
  486. carla_debug("CocoaPluginUI::setSize(%u, %u, %s)", width, height, bool2str(forceUpdate));
  487. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  488. CARLA_SAFE_ASSERT_RETURN(fView != nullptr,);
  489. [fView setFrame:NSMakeRect(0, 0, width, height)];
  490. for (NSView* subview in [fView subviews])
  491. {
  492. [subview setFrame:NSMakeRect(0, 0, width, height)];
  493. break;
  494. }
  495. const NSSize size = NSMakeSize(width, height);
  496. [fWindow setContentSize:size];
  497. /*
  498. if (fIsResizable)
  499. {
  500. [fWindow setContentMinSize:NSMakeSize(1, 1)];
  501. [fWindow setContentMaxSize:NSMakeSize(99999, 99999)];
  502. }
  503. else
  504. {
  505. [fWindow setContentMinSize:size];
  506. [fWindow setContentMaxSize:size];
  507. }
  508. */
  509. if (forceUpdate)
  510. {
  511. // FIXME, not enough
  512. [fView setNeedsDisplay:YES];
  513. }
  514. }
  515. void setTitle(const char* const title) override
  516. {
  517. carla_debug("CocoaPluginUI::setTitle(\"%s\")", title);
  518. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  519. NSString* titleString = [[NSString alloc]
  520. initWithBytes:title
  521. length:strlen(title)
  522. encoding:NSUTF8StringEncoding];
  523. [fWindow setTitle:titleString];
  524. }
  525. void setTransientWinId(const uintptr_t winId) override
  526. {
  527. carla_debug("CocoaPluginUI::setTransientWinId(" P_UINTPTR ")", winId);
  528. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  529. NSWindow* const parentWindow = [NSApp windowWithWindowNumber:winId];
  530. CARLA_SAFE_ASSERT_RETURN(parentWindow != nullptr,);
  531. fParentWindow = parentWindow;
  532. if ([fWindow isVisible])
  533. [fParentWindow addChildWindow:fWindow
  534. ordered:NSWindowAbove];
  535. }
  536. void setChildWindow(void* const window) override
  537. {
  538. carla_debug("CocoaPluginUI::setChildWindow(%p)", window);
  539. CARLA_SAFE_ASSERT_RETURN(window != nullptr,);
  540. }
  541. void* getPtr() const noexcept override
  542. {
  543. carla_debug("CocoaPluginUI::getPtr()");
  544. return (void*)fView;
  545. }
  546. void* getDisplay() const noexcept
  547. {
  548. carla_debug("CocoaPluginUI::getDisplay()");
  549. return (void*)fWindow;
  550. }
  551. private:
  552. NSView* fView;
  553. NSWindow* fParentWindow;
  554. CarlaPluginWindow* fWindow;
  555. CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CocoaPluginUI)
  556. };
  557. #endif // CARLA_OS_MAC
  558. // ---------------------------------------------------------------------------------------------------------------------
  559. // Windows
  560. #ifdef CARLA_OS_WIN
  561. #define PUGL_LOCAL_CLOSE_MSG (WM_USER + 50)
  562. static LRESULT CALLBACK wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
  563. class WindowsPluginUI : public CarlaPluginUI
  564. {
  565. public:
  566. WindowsPluginUI(Callback* const cb, const uintptr_t parentId, const bool isResizable) noexcept
  567. : CarlaPluginUI(cb, isResizable),
  568. fWindow(nullptr),
  569. fChildWindow(nullptr),
  570. fParentWindow(nullptr),
  571. fIsVisible(false),
  572. fFirstShow(true)
  573. {
  574. // FIXME
  575. static int wc_count = 0;
  576. char classNameBuf[32];
  577. std::srand((std::time(nullptr)));
  578. std::snprintf(classNameBuf, 32, "CarlaWin-%d-%d", ++wc_count, std::rand());
  579. classNameBuf[31] = '\0';
  580. const HINSTANCE hInstance = water::getCurrentModuleInstanceHandle();
  581. carla_zeroStruct(fWindowClass);
  582. fWindowClass.style = CS_OWNDC;
  583. fWindowClass.lpfnWndProc = wndProc;
  584. fWindowClass.hInstance = hInstance;
  585. fWindowClass.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
  586. fWindowClass.hCursor = LoadCursor(hInstance, IDC_ARROW);
  587. fWindowClass.lpszClassName = strdup(classNameBuf);
  588. if (!RegisterClass(&fWindowClass)) {
  589. free((void*)fWindowClass.lpszClassName);
  590. return;
  591. }
  592. int winFlags = WS_POPUPWINDOW | WS_CAPTION;
  593. if (isResizable)
  594. {
  595. // not supported right now
  596. // winFlags |= WS_SIZEBOX;
  597. }
  598. #ifdef BUILDING_CARLA_FOR_WINDOWS
  599. const uint winType = WS_EX_TOOLWINDOW;
  600. const HWND parent = (HWND)parentId;
  601. #else
  602. const uint winType = WS_EX_DLGMODALFRAME;
  603. const HWND parent = nullptr;
  604. #endif
  605. fWindow = CreateWindowEx(winType,
  606. classNameBuf, "Carla Plugin UI", winFlags,
  607. CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
  608. parent, nullptr,
  609. hInstance, nullptr);
  610. if (fWindow == nullptr) {
  611. const DWORD errorCode = ::GetLastError();
  612. carla_stderr2("CreateWindowEx failed with error code 0x%x, class name was '%s'",
  613. errorCode, fWindowClass.lpszClassName);
  614. UnregisterClass(fWindowClass.lpszClassName, nullptr);
  615. free((void*)fWindowClass.lpszClassName);
  616. return;
  617. }
  618. SetWindowLongPtr(fWindow, GWLP_USERDATA, (LONG_PTR)this);
  619. #ifdef BUILDING_CARLA_FOR_WINDOWS
  620. if (parentId != 0)
  621. setTransientWinId(parentId);
  622. #else
  623. return;
  624. // unused
  625. (void)parentId;
  626. #endif
  627. }
  628. ~WindowsPluginUI() override
  629. {
  630. CARLA_SAFE_ASSERT(! fIsVisible);
  631. if (fWindow != 0)
  632. {
  633. if (fIsVisible)
  634. ShowWindow(fWindow, SW_HIDE);
  635. DestroyWindow(fWindow);
  636. fWindow = 0;
  637. }
  638. // FIXME
  639. UnregisterClass(fWindowClass.lpszClassName, nullptr);
  640. free((void*)fWindowClass.lpszClassName);
  641. }
  642. void show() override
  643. {
  644. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  645. if (fFirstShow)
  646. {
  647. fFirstShow = false;
  648. RECT rectChild, rectParent;
  649. if (fParentWindow != nullptr &&
  650. GetWindowRect(fWindow, &rectChild) &&
  651. GetWindowRect(fParentWindow, &rectParent))
  652. {
  653. SetWindowPos(fWindow, fParentWindow,
  654. rectParent.left + (rectChild.right-rectChild.left)/2,
  655. rectParent.top + (rectChild.bottom-rectChild.top)/2,
  656. 0, 0, SWP_SHOWWINDOW|SWP_NOSIZE);
  657. }
  658. else
  659. {
  660. ShowWindow(fWindow, SW_SHOWNORMAL);
  661. }
  662. }
  663. else
  664. {
  665. ShowWindow(fWindow, SW_RESTORE);
  666. }
  667. fIsVisible = true;
  668. UpdateWindow(fWindow);
  669. }
  670. void hide() override
  671. {
  672. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  673. ShowWindow(fWindow, SW_HIDE);
  674. fIsVisible = false;
  675. UpdateWindow(fWindow);
  676. }
  677. void idle() override
  678. {
  679. if (fIsIdling || fWindow == nullptr)
  680. return;
  681. MSG msg;
  682. fIsIdling = true;
  683. while (::PeekMessage(&msg, fWindow, 0, 0, PM_REMOVE))
  684. {
  685. switch (msg.message)
  686. {
  687. case WM_QUIT:
  688. case PUGL_LOCAL_CLOSE_MSG:
  689. fIsVisible = false;
  690. CARLA_SAFE_ASSERT_BREAK(fCallback != nullptr);
  691. fCallback->handlePluginUIClosed();
  692. break;
  693. }
  694. DispatchMessageA(&msg);
  695. }
  696. fIsIdling = false;
  697. }
  698. LRESULT checkAndHandleMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
  699. {
  700. if (fWindow == hwnd)
  701. {
  702. switch (message)
  703. {
  704. case WM_QUIT:
  705. case PUGL_LOCAL_CLOSE_MSG:
  706. fIsVisible = false;
  707. CARLA_SAFE_ASSERT_BREAK(fCallback != nullptr);
  708. fCallback->handlePluginUIClosed();
  709. break;
  710. }
  711. }
  712. return DefWindowProcA(hwnd, message, wParam, lParam);
  713. }
  714. void focus() override
  715. {
  716. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  717. SetForegroundWindow(fWindow);
  718. SetActiveWindow(fWindow);
  719. SetFocus(fWindow);
  720. }
  721. void setSize(const uint width, const uint height, const bool forceUpdate) override
  722. {
  723. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  724. const int winFlags = WS_POPUPWINDOW | WS_CAPTION | (fIsResizable ? WS_SIZEBOX : 0x0);
  725. RECT wr = { 0, 0, static_cast<long>(width), static_cast<long>(height) };
  726. AdjustWindowRectEx(&wr, winFlags, FALSE, WS_EX_TOPMOST);
  727. SetWindowPos(fWindow, 0, 0, 0, wr.right-wr.left, wr.bottom-wr.top,
  728. SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOOWNERZORDER|SWP_NOZORDER);
  729. if (forceUpdate)
  730. UpdateWindow(fWindow);
  731. }
  732. void setTitle(const char* const title) override
  733. {
  734. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  735. SetWindowTextA(fWindow, title);
  736. }
  737. void setTransientWinId(const uintptr_t winId) override
  738. {
  739. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  740. fParentWindow = (HWND)winId;
  741. SetWindowLongPtr(fWindow, GWLP_HWNDPARENT, (LONG_PTR)winId);
  742. }
  743. void setChildWindow(void* const winId) override
  744. {
  745. CARLA_SAFE_ASSERT_RETURN(winId != nullptr,);
  746. fChildWindow = (HWND)fChildWindow;
  747. }
  748. void* getPtr() const noexcept override
  749. {
  750. return (void*)fWindow;
  751. }
  752. void* getDisplay() const noexcept
  753. {
  754. return nullptr;
  755. }
  756. private:
  757. HWND fWindow;
  758. HWND fChildWindow;
  759. HWND fParentWindow;
  760. WNDCLASS fWindowClass;
  761. bool fIsVisible;
  762. bool fFirstShow;
  763. CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WindowsPluginUI)
  764. };
  765. LRESULT CALLBACK wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
  766. {
  767. switch (message)
  768. {
  769. case WM_CLOSE:
  770. PostMessage(hwnd, PUGL_LOCAL_CLOSE_MSG, wParam, lParam);
  771. return 0;
  772. #if 0
  773. case WM_CREATE:
  774. PostMessage(hwnd, WM_SHOWWINDOW, TRUE, 0);
  775. return 0;
  776. case WM_DESTROY:
  777. return 0;
  778. #endif
  779. default:
  780. if (WindowsPluginUI* const ui = (WindowsPluginUI*)GetWindowLongPtr(hwnd, GWLP_USERDATA))
  781. return ui->checkAndHandleMessage(hwnd, message, wParam, lParam);
  782. return DefWindowProcA(hwnd, message, wParam, lParam);
  783. }
  784. }
  785. #endif // CARLA_OS_WIN
  786. // -----------------------------------------------------
  787. #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
  788. bool CarlaPluginUI::tryTransientWinIdMatch(const uintptr_t pid, const char* const uiTitle, const uintptr_t winId, const bool centerUI)
  789. {
  790. CARLA_SAFE_ASSERT_RETURN(uiTitle != nullptr && uiTitle[0] != '\0', true);
  791. CARLA_SAFE_ASSERT_RETURN(winId != 0, true);
  792. #if defined(HAVE_X11)
  793. struct ScopedDisplay {
  794. Display* display;
  795. ScopedDisplay() : display(XOpenDisplay(nullptr)) {}
  796. ~ScopedDisplay() { if (display!=nullptr) XCloseDisplay(display); }
  797. // c++ compat stuff
  798. CARLA_PREVENT_HEAP_ALLOCATION
  799. CARLA_DECLARE_NON_COPY_CLASS(ScopedDisplay)
  800. };
  801. struct ScopedFreeData {
  802. union {
  803. char* data;
  804. uchar* udata;
  805. };
  806. ScopedFreeData(char* d) : data(d) {}
  807. ScopedFreeData(uchar* d) : udata(d) {}
  808. ~ScopedFreeData() { XFree(data); }
  809. // c++ compat stuff
  810. CARLA_PREVENT_HEAP_ALLOCATION
  811. CARLA_DECLARE_NON_COPY_CLASS(ScopedFreeData)
  812. };
  813. const ScopedDisplay sd;
  814. CARLA_SAFE_ASSERT_RETURN(sd.display != nullptr, true);
  815. const Window rootWindow(DefaultRootWindow(sd.display));
  816. const Atom _ncl = XInternAtom(sd.display, "_NET_CLIENT_LIST" , False);
  817. const Atom _nwn = XInternAtom(sd.display, "_NET_WM_NAME", False);
  818. const Atom _nwp = XInternAtom(sd.display, "_NET_WM_PID", False);
  819. const Atom utf8 = XInternAtom(sd.display, "UTF8_STRING", True);
  820. Atom actualType;
  821. int actualFormat;
  822. ulong numWindows, bytesAfter;
  823. uchar* data = nullptr;
  824. int status = XGetWindowProperty(sd.display, rootWindow, _ncl, 0L, (~0L), False, AnyPropertyType, &actualType, &actualFormat, &numWindows, &bytesAfter, &data);
  825. CARLA_SAFE_ASSERT_RETURN(data != nullptr, true);
  826. const ScopedFreeData sfd(data);
  827. CARLA_SAFE_ASSERT_RETURN(status == Success, true);
  828. CARLA_SAFE_ASSERT_RETURN(actualFormat == 32, true);
  829. CARLA_SAFE_ASSERT_RETURN(numWindows != 0, true);
  830. Window* windows = (Window*)data;
  831. Window lastGoodWindowPID = 0, lastGoodWindowNameSimple = 0, lastGoodWindowNameUTF8 = 0;
  832. for (ulong i = 0; i < numWindows; i++)
  833. {
  834. const Window window(windows[i]);
  835. CARLA_SAFE_ASSERT_CONTINUE(window != 0);
  836. // ------------------------------------------------
  837. // try using pid
  838. if (pid != 0)
  839. {
  840. ulong pidSize;
  841. uchar* pidData = nullptr;
  842. status = XGetWindowProperty(sd.display, window, _nwp, 0L, (~0L), False, XA_CARDINAL, &actualType, &actualFormat, &pidSize, &bytesAfter, &pidData);
  843. if (pidData != nullptr)
  844. {
  845. const ScopedFreeData sfd2(pidData);
  846. CARLA_SAFE_ASSERT_CONTINUE(status == Success);
  847. CARLA_SAFE_ASSERT_CONTINUE(pidSize != 0);
  848. if (*(ulong*)pidData == static_cast<ulong>(pid))
  849. lastGoodWindowPID = window;
  850. }
  851. }
  852. // ------------------------------------------------
  853. // try using name (UTF-8)
  854. ulong nameSize;
  855. uchar* nameData = nullptr;
  856. status = XGetWindowProperty(sd.display, window, _nwn, 0L, (~0L), False, utf8, &actualType, &actualFormat, &nameSize, &bytesAfter, &nameData);
  857. if (nameData != nullptr)
  858. {
  859. const ScopedFreeData sfd2(nameData);
  860. CARLA_SAFE_ASSERT_CONTINUE(status == Success);
  861. if (nameSize != 0 && std::strstr((const char*)nameData, uiTitle) != nullptr)
  862. lastGoodWindowNameUTF8 = window;
  863. }
  864. // ------------------------------------------------
  865. // try using name (simple)
  866. char* wmName = nullptr;
  867. status = XFetchName(sd.display, window, &wmName);
  868. if (wmName != nullptr)
  869. {
  870. const ScopedFreeData sfd2(wmName);
  871. CARLA_SAFE_ASSERT_CONTINUE(status != 0);
  872. if (std::strstr(wmName, uiTitle) != nullptr)
  873. lastGoodWindowNameSimple = window;
  874. }
  875. }
  876. if (lastGoodWindowPID == 0 && lastGoodWindowNameSimple == 0 && lastGoodWindowNameUTF8 == 0)
  877. return false;
  878. Window windowToMap;
  879. if (lastGoodWindowPID != 0)
  880. {
  881. if (lastGoodWindowPID == lastGoodWindowNameSimple && lastGoodWindowPID == lastGoodWindowNameUTF8)
  882. {
  883. carla_stdout("Match found using pid, simple and UTF-8 name all at once, nice!");
  884. windowToMap = lastGoodWindowPID;
  885. }
  886. else if (lastGoodWindowPID == lastGoodWindowNameUTF8)
  887. {
  888. carla_stdout("Match found using pid and UTF-8 name");
  889. windowToMap = lastGoodWindowPID;
  890. }
  891. else if (lastGoodWindowPID == lastGoodWindowNameSimple)
  892. {
  893. carla_stdout("Match found using pid and simple name");
  894. windowToMap = lastGoodWindowPID;
  895. }
  896. else if (lastGoodWindowNameUTF8 != 0)
  897. {
  898. if (lastGoodWindowNameUTF8 == lastGoodWindowNameSimple)
  899. {
  900. carla_stdout("Match found using simple and UTF-8 name (ignoring pid)");
  901. windowToMap = lastGoodWindowNameUTF8;
  902. }
  903. else
  904. {
  905. carla_stdout("Match found using UTF-8 name (ignoring pid)");
  906. windowToMap = lastGoodWindowNameUTF8;
  907. }
  908. }
  909. else
  910. {
  911. carla_stdout("Match found using pid");
  912. windowToMap = lastGoodWindowPID;
  913. }
  914. }
  915. else if (lastGoodWindowNameUTF8 != 0)
  916. {
  917. if (lastGoodWindowNameUTF8 == lastGoodWindowNameSimple)
  918. {
  919. carla_stdout("Match found using simple and UTF-8 name");
  920. windowToMap = lastGoodWindowNameUTF8;
  921. }
  922. else
  923. {
  924. carla_stdout("Match found using UTF-8 name");
  925. windowToMap = lastGoodWindowNameUTF8;
  926. }
  927. }
  928. else
  929. {
  930. carla_stdout("Match found using simple name");
  931. windowToMap = lastGoodWindowNameSimple;
  932. }
  933. const Atom _nwt = XInternAtom(sd.display ,"_NET_WM_STATE", False);
  934. const Atom _nws[2] = {
  935. XInternAtom(sd.display, "_NET_WM_STATE_SKIP_TASKBAR", False),
  936. XInternAtom(sd.display, "_NET_WM_STATE_SKIP_PAGER", False)
  937. };
  938. XChangeProperty(sd.display, windowToMap, _nwt, XA_ATOM, 32, PropModeAppend, (const uchar*)_nws, 2);
  939. const Atom _nwi = XInternAtom(sd.display, "_NET_WM_ICON", False);
  940. XChangeProperty(sd.display, windowToMap, _nwi, XA_CARDINAL, 32, PropModeReplace, (const uchar*)sCarlaX11Icon, sCarlaX11IconSize);
  941. const Window hostWinId((Window)winId);
  942. XSetTransientForHint(sd.display, windowToMap, hostWinId);
  943. if (centerUI && false /* moving the window after being shown isn't pretty... */)
  944. {
  945. int hostX, hostY, pluginX, pluginY;
  946. uint hostWidth, hostHeight, pluginWidth, pluginHeight, border, depth;
  947. Window retWindow;
  948. if (XGetGeometry(sd.display, hostWinId, &retWindow, &hostX, &hostY, &hostWidth, &hostHeight, &border, &depth) != 0 &&
  949. XGetGeometry(sd.display, windowToMap, &retWindow, &pluginX, &pluginY, &pluginWidth, &pluginHeight, &border, &depth) != 0)
  950. {
  951. if (XTranslateCoordinates(sd.display, hostWinId, rootWindow, hostX, hostY, &hostX, &hostY, &retWindow) == True &&
  952. XTranslateCoordinates(sd.display, windowToMap, rootWindow, pluginX, pluginY, &pluginX, &pluginY, &retWindow) == True)
  953. {
  954. const int newX = hostX + int(hostWidth/2 - pluginWidth/2);
  955. const int newY = hostY + int(hostHeight/2 - pluginHeight/2);
  956. XMoveWindow(sd.display, windowToMap, newX, newY);
  957. }
  958. }
  959. }
  960. // focusing the host UI and then the plugin UI forces the WM to repaint the plugin window icon
  961. XRaiseWindow(sd.display, hostWinId);
  962. XSetInputFocus(sd.display, hostWinId, RevertToPointerRoot, CurrentTime);
  963. XRaiseWindow(sd.display, windowToMap);
  964. XSetInputFocus(sd.display, windowToMap, RevertToPointerRoot, CurrentTime);
  965. XFlush(sd.display);
  966. return true;
  967. #endif
  968. #ifdef CARLA_OS_MAC
  969. uint const hints = kCGWindowListOptionOnScreenOnly|kCGWindowListExcludeDesktopElements;
  970. CFArrayRef const windowListRef = CGWindowListCopyWindowInfo(hints, kCGNullWindowID);
  971. const NSArray* const windowList = (const NSArray*)windowListRef;
  972. int windowToMap, windowWithPID = 0, windowWithNameAndPID = 0;
  973. const NSDictionary* entry;
  974. for (entry in windowList)
  975. {
  976. // FIXME: is this needed? is old version safe?
  977. #if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
  978. if ([entry[(id)kCGWindowSharingState] intValue] == kCGWindowSharingNone)
  979. continue;
  980. NSString* const windowName = entry[(id)kCGWindowName];
  981. int const windowNumber = [entry[(id)kCGWindowNumber] intValue];
  982. uintptr_t const windowPID = [entry[(id)kCGWindowOwnerPID] intValue];
  983. #else
  984. if ([[entry objectForKey:(id)kCGWindowSharingState] intValue] == kCGWindowSharingNone)
  985. continue;
  986. NSString* const windowName = [entry objectForKey:(id)kCGWindowName];
  987. int const windowNumber = [[entry objectForKey:(id)kCGWindowNumber] intValue];
  988. uintptr_t const windowPID = [[entry objectForKey:(id)kCGWindowOwnerPID] intValue];
  989. #endif
  990. if (windowPID != pid)
  991. continue;
  992. windowWithPID = windowNumber;
  993. if (windowName != nullptr && std::strcmp([windowName UTF8String], uiTitle) == 0)
  994. windowWithNameAndPID = windowNumber;
  995. }
  996. CFRelease(windowListRef);
  997. if (windowWithNameAndPID != 0)
  998. {
  999. carla_stdout("Match found using pid and name");
  1000. windowToMap = windowWithNameAndPID;
  1001. }
  1002. else if (windowWithPID != 0)
  1003. {
  1004. carla_stdout("Match found using pid");
  1005. windowToMap = windowWithPID;
  1006. }
  1007. else
  1008. {
  1009. return false;
  1010. }
  1011. NSWindow* const parentWindow = [NSApp windowWithWindowNumber:winId];
  1012. CARLA_SAFE_ASSERT_RETURN(parentWindow != nullptr, false);
  1013. [parentWindow orderWindow:NSWindowBelow
  1014. relativeTo:windowToMap];
  1015. return true;
  1016. #endif
  1017. #ifdef CARLA_OS_WIN
  1018. if (HWND const childWindow = FindWindowA(nullptr, uiTitle))
  1019. {
  1020. HWND const parentWindow = (HWND)winId;
  1021. SetWindowLongPtr(childWindow, GWLP_HWNDPARENT, (LONG_PTR)parentWindow);
  1022. if (centerUI)
  1023. {
  1024. RECT rectChild, rectParent;
  1025. if (GetWindowRect(childWindow, &rectChild) && GetWindowRect(parentWindow, &rectParent))
  1026. {
  1027. SetWindowPos(childWindow, parentWindow,
  1028. rectParent.left + (rectChild.right-rectChild.left)/2,
  1029. rectParent.top + (rectChild.bottom-rectChild.top)/2,
  1030. 0, 0, SWP_NOSIZE);
  1031. }
  1032. }
  1033. carla_stdout("Match found using window name");
  1034. return true;
  1035. }
  1036. return false;
  1037. #endif
  1038. // fallback, may be unused
  1039. return true;
  1040. (void)pid; (void)centerUI;
  1041. }
  1042. #endif // BUILD_BRIDGE_ALTERNATIVE_ARCH
  1043. // -----------------------------------------------------
  1044. #ifdef HAVE_X11
  1045. CarlaPluginUI* CarlaPluginUI::newX11(Callback* cb, uintptr_t parentId, bool isResizable)
  1046. {
  1047. return new X11PluginUI(cb, parentId, isResizable);
  1048. }
  1049. #endif
  1050. #ifdef CARLA_OS_MAC
  1051. CarlaPluginUI* CarlaPluginUI::newCocoa(Callback* cb, uintptr_t parentId, bool isResizable)
  1052. {
  1053. return new CocoaPluginUI(cb, parentId, isResizable);
  1054. }
  1055. #endif
  1056. #ifdef CARLA_OS_WIN
  1057. CarlaPluginUI* CarlaPluginUI::newWindows(Callback* cb, uintptr_t parentId, bool isResizable)
  1058. {
  1059. return new WindowsPluginUI(cb, parentId, isResizable);
  1060. }
  1061. #endif
  1062. // -----------------------------------------------------