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.

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