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.

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