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.

1336 lines
40KB

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