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.

1263 lines
37KB

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