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.

1300 lines
38KB

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