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.

1243 lines
36KB

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