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.

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