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.

1239 lines
36KB

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