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.

1554 lines
48KB

  1. /*
  2. * Carla Plugin UI
  3. * Copyright (C) 2014-2022 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 <pthread.h>
  21. # include <sys/types.h>
  22. # include <X11/Xatom.h>
  23. # include <X11/Xlib.h>
  24. # include <X11/Xutil.h>
  25. # include "CarlaPluginUI_X11Icon.hpp"
  26. #endif
  27. #ifdef CARLA_OS_MAC
  28. # include "CarlaMacUtils.hpp"
  29. # import <Cocoa/Cocoa.h>
  30. #endif
  31. #ifdef CARLA_OS_WIN
  32. # include <ctime>
  33. # include "water/common.hpp"
  34. #endif
  35. #ifndef CARLA_PLUGIN_UI_CLASS_PREFIX
  36. # error CARLA_PLUGIN_UI_CLASS_PREFIX undefined
  37. #endif
  38. // ---------------------------------------------------------------------------------------------------------------------
  39. // X11
  40. #ifdef HAVE_X11
  41. static constexpr const uint X11Key_Escape = 9;
  42. typedef void (*EventProcPtr)(XEvent* ev);
  43. // FIXME put all this inside a scoped class
  44. static bool gErrorTriggered = false;
  45. # if defined(__GNUC__) && (__GNUC__ >= 5) && ! defined(__clang__)
  46. # pragma GCC diagnostic push
  47. # pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
  48. # endif
  49. static pthread_mutex_t gErrorMutex = PTHREAD_MUTEX_INITIALIZER;
  50. # if defined(__GNUC__) && (__GNUC__ >= 5) && ! defined(__clang__)
  51. # pragma GCC diagnostic pop
  52. # endif
  53. static int temporaryErrorHandler(Display*, XErrorEvent*)
  54. {
  55. gErrorTriggered = true;
  56. return 0;
  57. }
  58. class X11PluginUI : public CarlaPluginUI
  59. {
  60. public:
  61. X11PluginUI(Callback* const cb, const uintptr_t parentId,
  62. const bool isStandalone, const bool isResizable, const bool canMonitorChildren) noexcept
  63. : CarlaPluginUI(cb, isStandalone, isResizable),
  64. fDisplay(nullptr),
  65. fHostWindow(0),
  66. fChildWindow(0),
  67. fChildWindowConfigured(false),
  68. fChildWindowMonitoring(isResizable || canMonitorChildren),
  69. fIsVisible(false),
  70. fFirstShow(true),
  71. fSetSizeCalledAtLeastOnce(false),
  72. fMinimumWidth(0),
  73. fMinimumHeight(0),
  74. fEventProc(nullptr)
  75. {
  76. fDisplay = XOpenDisplay(nullptr);
  77. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  78. const int screen = DefaultScreen(fDisplay);
  79. XSetWindowAttributes attr;
  80. carla_zeroStruct(attr);
  81. attr.event_mask = KeyPressMask|KeyReleaseMask|FocusChangeMask;
  82. if (fChildWindowMonitoring)
  83. attr.event_mask |= StructureNotifyMask|SubstructureNotifyMask;
  84. fHostWindow = XCreateWindow(fDisplay, RootWindow(fDisplay, screen),
  85. 0, 0, 300, 300, 0,
  86. DefaultDepth(fDisplay, screen),
  87. InputOutput,
  88. DefaultVisual(fDisplay, screen),
  89. CWBorderPixel|CWEventMask, &attr);
  90. CARLA_SAFE_ASSERT_RETURN(fHostWindow != 0,);
  91. XGrabKey(fDisplay, X11Key_Escape, AnyModifier, fHostWindow, 1, GrabModeAsync, GrabModeAsync);
  92. Atom wmDelete = XInternAtom(fDisplay, "WM_DELETE_WINDOW", True);
  93. XSetWMProtocols(fDisplay, fHostWindow, &wmDelete, 1);
  94. const pid_t pid = getpid();
  95. const Atom _nwp = XInternAtom(fDisplay, "_NET_WM_PID", False);
  96. XChangeProperty(fDisplay, fHostWindow, _nwp, XA_CARDINAL, 32, PropModeReplace, (const uchar*)&pid, 1);
  97. const Atom _nwi = XInternAtom(fDisplay, "_NET_WM_ICON", False);
  98. XChangeProperty(fDisplay, fHostWindow, _nwi, XA_CARDINAL, 32, PropModeReplace, (const uchar*)sCarlaX11Icon, sCarlaX11IconSize);
  99. const Atom _wt = XInternAtom(fDisplay, "_NET_WM_WINDOW_TYPE", False);
  100. // Setting the window to both dialog and normal will produce a decorated floating dialog
  101. // Order is important: DIALOG needs to come before NORMAL
  102. const Atom _wts[2] = {
  103. XInternAtom(fDisplay, "_NET_WM_WINDOW_TYPE_DIALOG", False),
  104. XInternAtom(fDisplay, "_NET_WM_WINDOW_TYPE_NORMAL", False)
  105. };
  106. XChangeProperty(fDisplay, fHostWindow, _wt, XA_ATOM, 32, PropModeReplace, (const uchar*)&_wts, 2);
  107. if (parentId != 0)
  108. setTransientWinId(parentId);
  109. }
  110. ~X11PluginUI() override
  111. {
  112. CARLA_SAFE_ASSERT(! fIsVisible);
  113. if (fDisplay == nullptr)
  114. return;
  115. if (fIsVisible)
  116. {
  117. XUnmapWindow(fDisplay, fHostWindow);
  118. fIsVisible = false;
  119. }
  120. if (fHostWindow != 0)
  121. {
  122. XDestroyWindow(fDisplay, fHostWindow);
  123. fHostWindow = 0;
  124. }
  125. XCloseDisplay(fDisplay);
  126. fDisplay = nullptr;
  127. }
  128. void show() override
  129. {
  130. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  131. CARLA_SAFE_ASSERT_RETURN(fHostWindow != 0,);
  132. if (fFirstShow)
  133. {
  134. if (const Window childWindow = getChildWindow())
  135. {
  136. if (! fSetSizeCalledAtLeastOnce)
  137. {
  138. int width = 0;
  139. int height = 0;
  140. XWindowAttributes attrs = {};
  141. pthread_mutex_lock(&gErrorMutex);
  142. const XErrorHandler oldErrorHandler = XSetErrorHandler(temporaryErrorHandler);
  143. gErrorTriggered = false;
  144. if (XGetWindowAttributes(fDisplay, childWindow, &attrs))
  145. {
  146. width = attrs.width;
  147. height = attrs.height;
  148. }
  149. XSetErrorHandler(oldErrorHandler);
  150. pthread_mutex_unlock(&gErrorMutex);
  151. if (width == 0 && height == 0)
  152. {
  153. XSizeHints sizeHints = {};
  154. if (XGetNormalHints(fDisplay, childWindow, &sizeHints))
  155. {
  156. if (sizeHints.flags & PSize)
  157. {
  158. width = sizeHints.width;
  159. height = sizeHints.height;
  160. }
  161. else if (sizeHints.flags & PBaseSize)
  162. {
  163. width = sizeHints.base_width;
  164. height = sizeHints.base_height;
  165. }
  166. }
  167. }
  168. if (width > 1 && height > 1)
  169. setSize(static_cast<uint>(width), static_cast<uint>(height), false, false);
  170. }
  171. const Atom _xevp = XInternAtom(fDisplay, "_XEventProc", False);
  172. pthread_mutex_lock(&gErrorMutex);
  173. const XErrorHandler oldErrorHandler(XSetErrorHandler(temporaryErrorHandler));
  174. gErrorTriggered = false;
  175. Atom actualType;
  176. int actualFormat;
  177. ulong nitems, bytesAfter;
  178. uchar* data = nullptr;
  179. XGetWindowProperty(fDisplay, childWindow, _xevp, 0, 1, False, AnyPropertyType,
  180. &actualType, &actualFormat, &nitems, &bytesAfter, &data);
  181. XSetErrorHandler(oldErrorHandler);
  182. pthread_mutex_unlock(&gErrorMutex);
  183. if (nitems == 1 && ! gErrorTriggered)
  184. {
  185. fEventProc = *reinterpret_cast<EventProcPtr*>(data);
  186. XMapRaised(fDisplay, childWindow);
  187. }
  188. }
  189. }
  190. fIsVisible = true;
  191. fFirstShow = false;
  192. XMapRaised(fDisplay, fHostWindow);
  193. XSync(fDisplay, False);
  194. }
  195. void hide() override
  196. {
  197. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  198. CARLA_SAFE_ASSERT_RETURN(fHostWindow != 0,);
  199. fIsVisible = false;
  200. XUnmapWindow(fDisplay, fHostWindow);
  201. XFlush(fDisplay);
  202. }
  203. void idle() override
  204. {
  205. // prevent recursion
  206. if (fIsIdling) return;
  207. uint nextChildWidth = 0;
  208. uint nextChildHeight = 0;
  209. uint nextHostWidth = 0;
  210. uint nextHostHeight = 0;
  211. fIsIdling = true;
  212. for (XEvent event; XPending(fDisplay) > 0;)
  213. {
  214. XNextEvent(fDisplay, &event);
  215. if (! fIsVisible)
  216. continue;
  217. char* type = nullptr;
  218. switch (event.type)
  219. {
  220. case ConfigureNotify:
  221. CARLA_SAFE_ASSERT_CONTINUE(fCallback != nullptr);
  222. CARLA_SAFE_ASSERT_CONTINUE(event.xconfigure.width > 0);
  223. CARLA_SAFE_ASSERT_CONTINUE(event.xconfigure.height > 0);
  224. if (event.xconfigure.window == fHostWindow && fHostWindow != 0)
  225. {
  226. nextHostWidth = static_cast<uint>(event.xconfigure.width);
  227. nextHostHeight = static_cast<uint>(event.xconfigure.height);
  228. }
  229. else if (event.xconfigure.window == fChildWindow && fChildWindow != 0)
  230. {
  231. nextChildWidth = static_cast<uint>(event.xconfigure.width);
  232. nextChildHeight = static_cast<uint>(event.xconfigure.height);
  233. }
  234. break;
  235. case ClientMessage:
  236. type = XGetAtomName(fDisplay, event.xclient.message_type);
  237. CARLA_SAFE_ASSERT_CONTINUE(type != nullptr);
  238. if (std::strcmp(type, "WM_PROTOCOLS") == 0)
  239. {
  240. fIsVisible = false;
  241. CARLA_SAFE_ASSERT_CONTINUE(fCallback != nullptr);
  242. fCallback->handlePluginUIClosed();
  243. }
  244. break;
  245. case KeyRelease:
  246. if (event.xkey.keycode == X11Key_Escape)
  247. {
  248. fIsVisible = false;
  249. CARLA_SAFE_ASSERT_CONTINUE(fCallback != nullptr);
  250. fCallback->handlePluginUIClosed();
  251. }
  252. break;
  253. case FocusIn:
  254. if (fChildWindow == 0)
  255. fChildWindow = getChildWindow();
  256. if (fChildWindow != 0)
  257. {
  258. XWindowAttributes wa;
  259. carla_zeroStruct(wa);
  260. if (XGetWindowAttributes(fDisplay, fChildWindow, &wa) && wa.map_state == IsViewable)
  261. XSetInputFocus(fDisplay, fChildWindow, RevertToPointerRoot, CurrentTime);
  262. }
  263. break;
  264. }
  265. if (type != nullptr)
  266. XFree(type);
  267. else if (fEventProc != nullptr && event.type != FocusIn && event.type != FocusOut)
  268. fEventProc(&event);
  269. }
  270. if (nextChildWidth != 0 && nextChildHeight != 0 && fChildWindow != 0)
  271. {
  272. applyHintsFromChildWindow();
  273. XResizeWindow(fDisplay, fHostWindow, nextChildWidth, nextChildHeight);
  274. // XFlush(fDisplay);
  275. }
  276. else if (nextHostWidth != 0 && nextHostHeight != 0)
  277. {
  278. if (fChildWindow != 0 && ! fChildWindowConfigured)
  279. {
  280. applyHintsFromChildWindow();
  281. fChildWindowConfigured = true;
  282. }
  283. if (fChildWindow != 0)
  284. XResizeWindow(fDisplay, fChildWindow, nextHostWidth, nextHostHeight);
  285. fCallback->handlePluginUIResized(nextHostWidth, nextHostHeight);
  286. }
  287. fIsIdling = false;
  288. }
  289. void focus() override
  290. {
  291. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  292. CARLA_SAFE_ASSERT_RETURN(fHostWindow != 0,);
  293. XWindowAttributes wa;
  294. carla_zeroStruct(wa);
  295. CARLA_SAFE_ASSERT_RETURN(XGetWindowAttributes(fDisplay, fHostWindow, &wa),);
  296. if (wa.map_state == IsViewable)
  297. {
  298. XRaiseWindow(fDisplay, fHostWindow);
  299. XSetInputFocus(fDisplay, fHostWindow, RevertToPointerRoot, CurrentTime);
  300. XSync(fDisplay, False);
  301. }
  302. }
  303. void setMinimumSize(const uint width, const uint height) override
  304. {
  305. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  306. CARLA_SAFE_ASSERT_RETURN(fHostWindow != 0,);
  307. fMinimumWidth = width;
  308. fMinimumHeight = height;
  309. XSizeHints sizeHints = {};
  310. if (XGetNormalHints(fDisplay, fHostWindow, &sizeHints))
  311. {
  312. sizeHints.flags |= PMinSize;
  313. sizeHints.min_width = static_cast<int>(width);
  314. sizeHints.min_height = static_cast<int>(height);
  315. XSetNormalHints(fDisplay, fHostWindow, &sizeHints);
  316. }
  317. }
  318. void setSize(const uint width, const uint height, const bool forceUpdate, const bool resizeChild) override
  319. {
  320. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  321. CARLA_SAFE_ASSERT_RETURN(fHostWindow != 0,);
  322. fSetSizeCalledAtLeastOnce = true;
  323. XResizeWindow(fDisplay, fHostWindow, width, height);
  324. if (fChildWindow != 0 && resizeChild)
  325. XResizeWindow(fDisplay, fChildWindow, width, height);
  326. if (! fIsResizable)
  327. {
  328. XSizeHints sizeHints = {};
  329. sizeHints.flags = PSize|PMinSize|PMaxSize;
  330. sizeHints.width = static_cast<int>(width);
  331. sizeHints.height = static_cast<int>(height);
  332. sizeHints.min_width = static_cast<int>(width);
  333. sizeHints.min_height = static_cast<int>(height);
  334. sizeHints.max_width = static_cast<int>(width);
  335. sizeHints.max_height = static_cast<int>(height);
  336. XSetNormalHints(fDisplay, fHostWindow, &sizeHints);
  337. }
  338. if (forceUpdate)
  339. XSync(fDisplay, False);
  340. }
  341. void setTitle(const char* const title) override
  342. {
  343. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  344. CARLA_SAFE_ASSERT_RETURN(fHostWindow != 0,);
  345. XStoreName(fDisplay, fHostWindow, title);
  346. const Atom _nwn = XInternAtom(fDisplay, "_NET_WM_NAME", False);
  347. const Atom utf8 = XInternAtom(fDisplay, "UTF8_STRING", True);
  348. XChangeProperty(fDisplay, fHostWindow, _nwn, utf8, 8,
  349. PropModeReplace,
  350. (const uchar*)(title),
  351. (int)strlen(title));
  352. }
  353. void setTransientWinId(const uintptr_t winId) override
  354. {
  355. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  356. CARLA_SAFE_ASSERT_RETURN(fHostWindow != 0,);
  357. XSetTransientForHint(fDisplay, fHostWindow, static_cast<Window>(winId));
  358. }
  359. void setChildWindow(void* const winId) override
  360. {
  361. CARLA_SAFE_ASSERT_RETURN(winId != nullptr,);
  362. fChildWindow = (Window)winId;
  363. }
  364. void* getPtr() const noexcept override
  365. {
  366. return (void*)fHostWindow;
  367. }
  368. void* getDisplay() const noexcept override
  369. {
  370. return fDisplay;
  371. }
  372. protected:
  373. void applyHintsFromChildWindow()
  374. {
  375. pthread_mutex_lock(&gErrorMutex);
  376. const XErrorHandler oldErrorHandler = XSetErrorHandler(temporaryErrorHandler);
  377. gErrorTriggered = false;
  378. XSizeHints sizeHints = {};
  379. if (XGetNormalHints(fDisplay, fChildWindow, &sizeHints) && !gErrorTriggered)
  380. {
  381. if (fMinimumWidth != 0 && fMinimumHeight != 0)
  382. {
  383. sizeHints.flags |= PMinSize;
  384. sizeHints.min_width = fMinimumWidth;
  385. sizeHints.min_height = fMinimumHeight;
  386. }
  387. XSetNormalHints(fDisplay, fHostWindow, &sizeHints);
  388. }
  389. if (gErrorTriggered)
  390. {
  391. carla_stdout("Caught errors while accessing child window");
  392. fChildWindow = 0;
  393. }
  394. XSetErrorHandler(oldErrorHandler);
  395. pthread_mutex_unlock(&gErrorMutex);
  396. }
  397. Window getChildWindow() const
  398. {
  399. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr, 0);
  400. CARLA_SAFE_ASSERT_RETURN(fHostWindow != 0, 0);
  401. Window rootWindow, parentWindow, ret = 0;
  402. Window* childWindows = nullptr;
  403. uint numChildren = 0;
  404. XQueryTree(fDisplay, fHostWindow, &rootWindow, &parentWindow, &childWindows, &numChildren);
  405. if (numChildren > 0 && childWindows != nullptr)
  406. {
  407. ret = childWindows[0];
  408. XFree(childWindows);
  409. }
  410. return ret;
  411. }
  412. private:
  413. Display* fDisplay;
  414. Window fHostWindow;
  415. Window fChildWindow;
  416. bool fChildWindowConfigured;
  417. bool fChildWindowMonitoring;
  418. bool fIsVisible;
  419. bool fFirstShow;
  420. bool fSetSizeCalledAtLeastOnce;
  421. uint fMinimumWidth;
  422. uint fMinimumHeight;
  423. EventProcPtr fEventProc;
  424. CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(X11PluginUI)
  425. };
  426. #endif // HAVE_X11
  427. // ---------------------------------------------------------------------------------------------------------------------
  428. // MacOS / Cocoa
  429. #ifdef CARLA_OS_MAC
  430. #if defined(BUILD_BRIDGE_ALTERNATIVE_ARCH)
  431. # define CarlaPluginWindow CARLA_JOIN_MACRO3(CarlaPluginWindowBridgedArch, CARLA_BACKEND_NAMESPACE, CARLA_PLUGIN_UI_CLASS_PREFIX)
  432. # define CarlaPluginWindowDelegate CARLA_JOIN_MACRO3(CarlaPluginWindowDelegateBridgedArch, CARLA_BACKEND_NAMESPACE, CARLA_PLUGIN_UI_CLASS_PREFIX)
  433. #elif defined(BUILD_BRIDGE)
  434. # define CarlaPluginWindow CARLA_JOIN_MACRO3(CarlaPluginWindowBridged, CARLA_BACKEND_NAMESPACE, CARLA_PLUGIN_UI_CLASS_PREFIX)
  435. # define CarlaPluginWindowDelegate CARLA_JOIN_MACRO3(CarlaPluginWindowDelegateBridged, CARLA_BACKEND_NAMESPACE, CARLA_PLUGIN_UI_CLASS_PREFIX)
  436. #else
  437. # define CarlaPluginWindow CARLA_JOIN_MACRO3(CarlaPluginWindow, CARLA_BACKEND_NAMESPACE, CARLA_PLUGIN_UI_CLASS_PREFIX)
  438. # define CarlaPluginWindowDelegate CARLA_JOIN_MACRO3(CarlaPluginWindowDelegate, CARLA_BACKEND_NAMESPACE, CARLA_PLUGIN_UI_CLASS_PREFIX)
  439. #endif
  440. @interface CarlaPluginWindow : NSWindow
  441. - (id) initWithContentRect:(NSRect)contentRect
  442. styleMask:(unsigned int)aStyle
  443. backing:(NSBackingStoreType)bufferingType
  444. defer:(BOOL)flag;
  445. - (BOOL) canBecomeKeyWindow;
  446. - (BOOL) canBecomeMainWindow;
  447. @end
  448. @implementation CarlaPluginWindow
  449. - (id)initWithContentRect:(NSRect)contentRect
  450. styleMask:(unsigned int)aStyle
  451. backing:(NSBackingStoreType)bufferingType
  452. defer:(BOOL)flag
  453. {
  454. NSWindow* result = [super initWithContentRect:contentRect
  455. styleMask:aStyle
  456. backing:bufferingType
  457. defer:flag];
  458. [result setAcceptsMouseMovedEvents:YES];
  459. return (CarlaPluginWindow*)result;
  460. // unused
  461. (void)flag;
  462. }
  463. - (BOOL)canBecomeKeyWindow
  464. {
  465. return YES;
  466. }
  467. - (BOOL)canBecomeMainWindow
  468. {
  469. return NO;
  470. }
  471. @end
  472. @interface CarlaPluginWindowDelegate : NSObject<NSWindowDelegate>
  473. {
  474. CarlaPluginUI::Callback* callback;
  475. CarlaPluginWindow* window;
  476. }
  477. - (instancetype)initWithWindowAndCallback:(CarlaPluginWindow*)window
  478. callback:(CarlaPluginUI::Callback*)callback2;
  479. - (BOOL)windowShouldClose:(id)sender;
  480. - (NSSize)windowWillResize:(NSWindow*)sender toSize:(NSSize)frameSize;
  481. @end
  482. @implementation CarlaPluginWindowDelegate
  483. - (instancetype)initWithWindowAndCallback:(CarlaPluginWindow*)window2
  484. callback:(CarlaPluginUI::Callback*)callback2
  485. {
  486. if ((self = [super init]))
  487. {
  488. callback = callback2;
  489. window = window2;
  490. }
  491. return self;
  492. }
  493. - (BOOL)windowShouldClose:(id)sender
  494. {
  495. if (callback != nil)
  496. callback->handlePluginUIClosed();
  497. return NO;
  498. // unused
  499. (void)sender;
  500. }
  501. - (NSSize)windowWillResize:(NSWindow*)sender toSize:(NSSize)frameSize
  502. {
  503. for (NSView* subview in [[window contentView] subviews])
  504. {
  505. const NSSize minSize = [subview fittingSize];
  506. if (frameSize.width < minSize.width)
  507. frameSize.width = minSize.width;
  508. if (frameSize.height < minSize.height)
  509. frameSize.height = minSize.height;
  510. break;
  511. }
  512. return frameSize;
  513. }
  514. /*
  515. - (void)windowDidResize:(NSWindow*)sender
  516. {
  517. carla_stdout("window did resize %p %f %f", sender, [window frame].size.width, [window frame].size.height);
  518. const NSSize size = [window frame].size;
  519. NSView* const view = [window contentView];
  520. for (NSView* subview in [view subviews])
  521. {
  522. [subview setFrameSize:size];
  523. break;
  524. }
  525. }
  526. */
  527. @end
  528. class CocoaPluginUI : public CarlaPluginUI
  529. {
  530. public:
  531. CocoaPluginUI(Callback* const callback, const uintptr_t parentId, const bool isStandalone, const bool isResizable) noexcept
  532. : CarlaPluginUI(callback, isStandalone, isResizable),
  533. fView(nullptr),
  534. fParentWindow(nullptr),
  535. fWindow(nullptr)
  536. {
  537. carla_debug("CocoaPluginUI::CocoaPluginUI(%p, " P_UINTPTR, "%s)", callback, parentId, bool2str(isResizable));
  538. const CARLA_BACKEND_NAMESPACE::AutoNSAutoreleasePool arp;
  539. [NSApplication sharedApplication];
  540. fView = [[NSView new]retain];
  541. CARLA_SAFE_ASSERT_RETURN(fView != nullptr,)
  542. #ifdef __MAC_10_12
  543. uint style = NSWindowStyleMaskClosable | NSWindowStyleMaskTitled;
  544. #else
  545. uint style = NSClosableWindowMask | NSTitledWindowMask;
  546. #endif
  547. /*
  548. if (isResizable)
  549. style |= NSResizableWindowMask;
  550. */
  551. const NSRect frame = NSMakeRect(0, 0, 100, 100);
  552. fWindow = [[[CarlaPluginWindow alloc]
  553. initWithContentRect:frame
  554. styleMask:style
  555. backing:NSBackingStoreBuffered
  556. defer:NO
  557. ] retain];
  558. if (fWindow == nullptr)
  559. {
  560. [fView release];
  561. fView = nullptr;
  562. return;
  563. }
  564. ((NSWindow*)fWindow).delegate = [[[CarlaPluginWindowDelegate alloc]
  565. initWithWindowAndCallback:fWindow
  566. callback:callback] retain];
  567. /*
  568. if (isResizable)
  569. {
  570. [fView setAutoresizingMask:(NSViewWidthSizable |
  571. NSViewHeightSizable |
  572. NSViewMinXMargin |
  573. NSViewMaxXMargin |
  574. NSViewMinYMargin |
  575. NSViewMaxYMargin)];
  576. [fView setAutoresizesSubviews:YES];
  577. }
  578. else
  579. */
  580. {
  581. [fView setAutoresizingMask:NSViewNotSizable];
  582. [fView setAutoresizesSubviews:NO];
  583. [[fWindow standardWindowButton:NSWindowZoomButton] setHidden:YES];
  584. }
  585. [fWindow setContentView:fView];
  586. [fWindow makeFirstResponder:fView];
  587. [fView setHidden:NO];
  588. if (parentId != 0)
  589. setTransientWinId(parentId);
  590. }
  591. ~CocoaPluginUI() override
  592. {
  593. carla_debug("CocoaPluginUI::~CocoaPluginUI()");
  594. if (fView == nullptr)
  595. return;
  596. [fView setHidden:YES];
  597. [fView removeFromSuperview];
  598. [fWindow close];
  599. [fView release];
  600. [fWindow release];
  601. }
  602. void show() override
  603. {
  604. carla_debug("CocoaPluginUI::show()");
  605. CARLA_SAFE_ASSERT_RETURN(fView != nullptr,);
  606. if (fParentWindow != nullptr)
  607. {
  608. [fParentWindow addChildWindow:fWindow
  609. ordered:NSWindowAbove];
  610. }
  611. else
  612. {
  613. [fWindow setIsVisible:YES];
  614. }
  615. }
  616. void hide() override
  617. {
  618. carla_debug("CocoaPluginUI::hide()");
  619. CARLA_SAFE_ASSERT_RETURN(fView != nullptr,);
  620. [fWindow setIsVisible:NO];
  621. if (fParentWindow != nullptr)
  622. [fParentWindow removeChildWindow:fWindow];
  623. }
  624. void idle() override
  625. {
  626. // carla_debug("CocoaPluginUI::idle()");
  627. for (NSView* subview in [fView subviews])
  628. {
  629. const NSSize viewSize = [fView frame].size;
  630. const NSSize subviewSize = [subview frame].size;
  631. if (viewSize.width != subviewSize.width || viewSize.height != subviewSize.height)
  632. {
  633. [fView setFrameSize:subviewSize];
  634. [fWindow setContentSize:subviewSize];
  635. }
  636. break;
  637. }
  638. }
  639. void focus() override
  640. {
  641. carla_debug("CocoaPluginUI::focus()");
  642. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  643. [fWindow makeKeyAndOrderFront:fWindow];
  644. [fWindow orderFrontRegardless];
  645. [NSApp activateIgnoringOtherApps:YES];
  646. }
  647. void setMinimumSize(uint, uint) override
  648. {
  649. // TODO
  650. }
  651. void setSize(const uint width, const uint height, const bool forceUpdate, const bool resizeChild) override
  652. {
  653. carla_debug("CocoaPluginUI::setSize(%u, %u, %s)", width, height, bool2str(forceUpdate));
  654. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  655. CARLA_SAFE_ASSERT_RETURN(fView != nullptr,);
  656. const NSSize size = NSMakeSize(width, height);
  657. [fView setFrameSize:size];
  658. [fWindow setContentSize:size];
  659. // this is needed for a few plugins
  660. if (forceUpdate && resizeChild)
  661. {
  662. for (NSView* subview in [fView subviews])
  663. {
  664. [subview setFrame:[fView frame]];
  665. break;
  666. }
  667. }
  668. /*
  669. if (fIsResizable)
  670. {
  671. [fWindow setContentMinSize:NSMakeSize(1, 1)];
  672. [fWindow setContentMaxSize:NSMakeSize(99999, 99999)];
  673. }
  674. else
  675. {
  676. [fWindow setContentMinSize:size];
  677. [fWindow setContentMaxSize:size];
  678. }
  679. */
  680. if (forceUpdate)
  681. {
  682. // FIXME, not enough
  683. [fView setNeedsDisplay:YES];
  684. }
  685. }
  686. void setTitle(const char* const title) override
  687. {
  688. carla_debug("CocoaPluginUI::setTitle(\"%s\")", title);
  689. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  690. NSString* titleString = [[NSString alloc]
  691. initWithBytes:title
  692. length:strlen(title)
  693. encoding:NSUTF8StringEncoding];
  694. [fWindow setTitle:titleString];
  695. }
  696. void setTransientWinId(const uintptr_t winId) override
  697. {
  698. carla_debug("CocoaPluginUI::setTransientWinId(" P_UINTPTR ")", winId);
  699. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  700. NSWindow* const parentWindow = [NSApp windowWithWindowNumber:winId];
  701. CARLA_SAFE_ASSERT_RETURN(parentWindow != nullptr,);
  702. fParentWindow = parentWindow;
  703. if ([fWindow isVisible])
  704. [fParentWindow addChildWindow:fWindow
  705. ordered:NSWindowAbove];
  706. }
  707. void setChildWindow(void* const childWindow) override
  708. {
  709. carla_debug("CocoaPluginUI::setChildWindow(%p)", childWindow);
  710. CARLA_SAFE_ASSERT_RETURN(childWindow != nullptr,);
  711. NSView* const view = (NSView*)childWindow;
  712. const NSRect frame = [view frame];
  713. [fWindow setContentSize:frame.size];
  714. [fView setFrame:frame];
  715. [fView setNeedsDisplay:YES];
  716. }
  717. void* getPtr() const noexcept override
  718. {
  719. carla_debug("CocoaPluginUI::getPtr()");
  720. return (void*)fView;
  721. }
  722. void* getDisplay() const noexcept
  723. {
  724. carla_debug("CocoaPluginUI::getDisplay()");
  725. return (void*)fWindow;
  726. }
  727. private:
  728. NSView* fView;
  729. NSWindow* fParentWindow;
  730. CarlaPluginWindow* fWindow;
  731. CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CocoaPluginUI)
  732. };
  733. #endif // CARLA_OS_MAC
  734. // ---------------------------------------------------------------------------------------------------------------------
  735. // Windows
  736. #ifdef CARLA_OS_WIN
  737. #define CARLA_LOCAL_CLOSE_MSG (WM_USER + 50)
  738. static LRESULT CALLBACK wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
  739. class WindowsPluginUI : public CarlaPluginUI
  740. {
  741. public:
  742. WindowsPluginUI(Callback* const cb, const uintptr_t parentId, const bool isStandalone, const bool isResizable) noexcept
  743. : CarlaPluginUI(cb, isStandalone, isResizable),
  744. fWindow(nullptr),
  745. fChildWindow(nullptr),
  746. fParentWindow(nullptr),
  747. fIsVisible(false),
  748. fFirstShow(true)
  749. {
  750. // FIXME
  751. static int wc_count = 0;
  752. char classNameBuf[32];
  753. std::srand((std::time(nullptr)));
  754. std::snprintf(classNameBuf, 32, "CarlaWin-%d-%d", ++wc_count, std::rand());
  755. classNameBuf[31] = '\0';
  756. const HINSTANCE hInstance = water::getCurrentModuleInstanceHandle();
  757. carla_zeroStruct(fWindowClass);
  758. fWindowClass.style = CS_OWNDC;
  759. fWindowClass.lpfnWndProc = wndProc;
  760. fWindowClass.hInstance = hInstance;
  761. fWindowClass.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
  762. fWindowClass.hCursor = LoadCursor(hInstance, IDC_ARROW);
  763. fWindowClass.lpszClassName = strdup(classNameBuf);
  764. if (!RegisterClassA(&fWindowClass)) {
  765. free((void*)fWindowClass.lpszClassName);
  766. return;
  767. }
  768. int winFlags = WS_POPUPWINDOW | WS_CAPTION;
  769. if (isResizable)
  770. winFlags |= WS_SIZEBOX;
  771. #ifdef BUILDING_CARLA_FOR_WINE
  772. const uint winType = WS_EX_DLGMODALFRAME;
  773. const HWND parent = nullptr;
  774. #else
  775. const uint winType = WS_EX_TOOLWINDOW;
  776. const HWND parent = (HWND)parentId;
  777. #endif
  778. fWindow = CreateWindowExA(winType,
  779. classNameBuf, "Carla Plugin UI", winFlags,
  780. CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
  781. parent, nullptr,
  782. hInstance, nullptr);
  783. if (fWindow == nullptr)
  784. {
  785. const DWORD errorCode = ::GetLastError();
  786. carla_stderr2("CreateWindowEx failed with error code 0x%x, class name was '%s'",
  787. errorCode, fWindowClass.lpszClassName);
  788. UnregisterClassA(fWindowClass.lpszClassName, nullptr);
  789. free((void*)fWindowClass.lpszClassName);
  790. return;
  791. }
  792. SetWindowLongPtr(fWindow, GWLP_USERDATA, (LONG_PTR)this);
  793. #ifndef BUILDING_CARLA_FOR_WINE
  794. if (parentId != 0)
  795. setTransientWinId(parentId);
  796. #endif
  797. return;
  798. // maybe unused
  799. (void)parentId;
  800. }
  801. ~WindowsPluginUI() override
  802. {
  803. CARLA_SAFE_ASSERT(! fIsVisible);
  804. if (fWindow != 0)
  805. {
  806. if (fIsVisible)
  807. ShowWindow(fWindow, SW_HIDE);
  808. DestroyWindow(fWindow);
  809. fWindow = 0;
  810. }
  811. // FIXME
  812. UnregisterClassA(fWindowClass.lpszClassName, nullptr);
  813. free((void*)fWindowClass.lpszClassName);
  814. }
  815. void show() override
  816. {
  817. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  818. if (fFirstShow)
  819. {
  820. fFirstShow = false;
  821. RECT rectChild, rectParent;
  822. if (fChildWindow != nullptr && GetWindowRect(fChildWindow, &rectChild))
  823. setSize(rectChild.right - rectChild.left, rectChild.bottom - rectChild.top, false, false);
  824. if (fParentWindow != nullptr &&
  825. GetWindowRect(fWindow, &rectChild) &&
  826. GetWindowRect(fParentWindow, &rectParent))
  827. {
  828. SetWindowPos(fWindow, fParentWindow,
  829. rectParent.left + (rectChild.right-rectChild.left)/2,
  830. rectParent.top + (rectChild.bottom-rectChild.top)/2,
  831. 0, 0, SWP_SHOWWINDOW|SWP_NOSIZE);
  832. }
  833. else
  834. {
  835. ShowWindow(fWindow, SW_SHOWNORMAL);
  836. }
  837. }
  838. else
  839. {
  840. ShowWindow(fWindow, SW_RESTORE);
  841. }
  842. fIsVisible = true;
  843. UpdateWindow(fWindow);
  844. }
  845. void hide() override
  846. {
  847. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  848. ShowWindow(fWindow, SW_HIDE);
  849. fIsVisible = false;
  850. UpdateWindow(fWindow);
  851. }
  852. void idle() override
  853. {
  854. if (fIsIdling || fWindow == nullptr)
  855. return;
  856. MSG msg;
  857. fIsIdling = true;
  858. while (::PeekMessage(&msg, fWindow, 0, 0, PM_REMOVE))
  859. {
  860. switch (msg.message)
  861. {
  862. case WM_QUIT:
  863. case CARLA_LOCAL_CLOSE_MSG:
  864. fIsVisible = false;
  865. CARLA_SAFE_ASSERT_BREAK(fCallback != nullptr);
  866. fCallback->handlePluginUIClosed();
  867. break;
  868. }
  869. DispatchMessageA(&msg);
  870. }
  871. fIsIdling = false;
  872. }
  873. LRESULT checkAndHandleMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
  874. {
  875. if (fWindow == hwnd)
  876. {
  877. switch (message)
  878. {
  879. case WM_SIZE:
  880. if (fChildWindow != nullptr)
  881. {
  882. RECT rect;
  883. GetClientRect(fWindow, &rect);
  884. SetWindowPos(fChildWindow, 0, 0, 0, rect.right, rect.bottom,
  885. SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOOWNERZORDER|SWP_NOZORDER);
  886. }
  887. break;
  888. case WM_QUIT:
  889. case CARLA_LOCAL_CLOSE_MSG:
  890. fIsVisible = false;
  891. CARLA_SAFE_ASSERT_BREAK(fCallback != nullptr);
  892. fCallback->handlePluginUIClosed();
  893. break;
  894. }
  895. }
  896. return DefWindowProcA(hwnd, message, wParam, lParam);
  897. }
  898. void focus() override
  899. {
  900. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  901. SetForegroundWindow(fWindow);
  902. SetActiveWindow(fWindow);
  903. SetFocus(fWindow);
  904. }
  905. void setMinimumSize(uint, uint) override
  906. {
  907. // TODO
  908. }
  909. void setSize(const uint width, const uint height, const bool forceUpdate, bool) override
  910. {
  911. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  912. const int winFlags = WS_POPUPWINDOW | WS_CAPTION | (fIsResizable ? WS_SIZEBOX : 0x0);
  913. RECT wr = { 0, 0, static_cast<long>(width), static_cast<long>(height) };
  914. AdjustWindowRectEx(&wr, winFlags, FALSE, WS_EX_TOPMOST);
  915. SetWindowPos(fWindow, 0, 0, 0, wr.right-wr.left, wr.bottom-wr.top,
  916. SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOOWNERZORDER|SWP_NOZORDER);
  917. if (forceUpdate)
  918. UpdateWindow(fWindow);
  919. }
  920. void setTitle(const char* const title) override
  921. {
  922. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  923. SetWindowTextA(fWindow, title);
  924. }
  925. void setTransientWinId(const uintptr_t winId) override
  926. {
  927. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  928. fParentWindow = (HWND)winId;
  929. SetWindowLongPtr(fWindow, GWLP_HWNDPARENT, (LONG_PTR)winId);
  930. }
  931. void setChildWindow(void* const winId) override
  932. {
  933. CARLA_SAFE_ASSERT_RETURN(winId != nullptr,);
  934. fChildWindow = (HWND)winId;
  935. }
  936. void* getPtr() const noexcept override
  937. {
  938. return (void*)fWindow;
  939. }
  940. void* getDisplay() const noexcept
  941. {
  942. return nullptr;
  943. }
  944. private:
  945. HWND fWindow;
  946. HWND fChildWindow;
  947. HWND fParentWindow;
  948. WNDCLASSA fWindowClass;
  949. bool fIsVisible;
  950. bool fFirstShow;
  951. CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WindowsPluginUI)
  952. };
  953. LRESULT CALLBACK wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
  954. {
  955. switch (message)
  956. {
  957. case WM_CLOSE:
  958. PostMessage(hwnd, CARLA_LOCAL_CLOSE_MSG, wParam, lParam);
  959. return 0;
  960. #if 0
  961. case WM_CREATE:
  962. PostMessage(hwnd, WM_SHOWWINDOW, TRUE, 0);
  963. return 0;
  964. case WM_DESTROY:
  965. return 0;
  966. #endif
  967. default:
  968. if (WindowsPluginUI* const ui = (WindowsPluginUI*)GetWindowLongPtr(hwnd, GWLP_USERDATA))
  969. return ui->checkAndHandleMessage(hwnd, message, wParam, lParam);
  970. return DefWindowProcA(hwnd, message, wParam, lParam);
  971. }
  972. }
  973. #endif // CARLA_OS_WIN
  974. // -----------------------------------------------------
  975. #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
  976. bool CarlaPluginUI::tryTransientWinIdMatch(const uintptr_t pid, const char* const uiTitle, const uintptr_t winId, const bool centerUI)
  977. {
  978. CARLA_SAFE_ASSERT_RETURN(uiTitle != nullptr && uiTitle[0] != '\0', true);
  979. CARLA_SAFE_ASSERT_RETURN(winId != 0, true);
  980. #if defined(HAVE_X11)
  981. struct ScopedDisplay {
  982. Display* display;
  983. ScopedDisplay() : display(XOpenDisplay(nullptr)) {}
  984. ~ScopedDisplay() { if (display!=nullptr) XCloseDisplay(display); }
  985. // c++ compat stuff
  986. CARLA_PREVENT_HEAP_ALLOCATION
  987. CARLA_DECLARE_NON_COPYABLE(ScopedDisplay)
  988. };
  989. struct ScopedFreeData {
  990. union {
  991. char* data;
  992. uchar* udata;
  993. };
  994. ScopedFreeData(char* d) : data(d) {}
  995. ScopedFreeData(uchar* d) : udata(d) {}
  996. ~ScopedFreeData() { XFree(data); }
  997. // c++ compat stuff
  998. CARLA_PREVENT_HEAP_ALLOCATION
  999. CARLA_DECLARE_NON_COPYABLE(ScopedFreeData)
  1000. };
  1001. const ScopedDisplay sd;
  1002. CARLA_SAFE_ASSERT_RETURN(sd.display != nullptr, true);
  1003. const Window rootWindow(DefaultRootWindow(sd.display));
  1004. const Atom _ncl = XInternAtom(sd.display, "_NET_CLIENT_LIST" , False);
  1005. const Atom _nwn = XInternAtom(sd.display, "_NET_WM_NAME", False);
  1006. const Atom _nwp = XInternAtom(sd.display, "_NET_WM_PID", False);
  1007. const Atom utf8 = XInternAtom(sd.display, "UTF8_STRING", True);
  1008. Atom actualType;
  1009. int actualFormat;
  1010. ulong numWindows, bytesAfter;
  1011. uchar* data = nullptr;
  1012. int status = XGetWindowProperty(sd.display, rootWindow, _ncl, 0L, (~0L), False, AnyPropertyType, &actualType, &actualFormat, &numWindows, &bytesAfter, &data);
  1013. CARLA_SAFE_ASSERT_RETURN(data != nullptr, true);
  1014. const ScopedFreeData sfd(data);
  1015. CARLA_SAFE_ASSERT_RETURN(status == Success, true);
  1016. CARLA_SAFE_ASSERT_RETURN(actualFormat == 32, true);
  1017. CARLA_SAFE_ASSERT_RETURN(numWindows != 0, true);
  1018. Window* windows = (Window*)data;
  1019. Window lastGoodWindowPID = 0, lastGoodWindowNameSimple = 0, lastGoodWindowNameUTF8 = 0;
  1020. for (ulong i = 0; i < numWindows; i++)
  1021. {
  1022. const Window window(windows[i]);
  1023. CARLA_SAFE_ASSERT_CONTINUE(window != 0);
  1024. // ------------------------------------------------
  1025. // try using pid
  1026. if (pid != 0)
  1027. {
  1028. ulong pidSize;
  1029. uchar* pidData = nullptr;
  1030. status = XGetWindowProperty(sd.display, window, _nwp, 0L, (~0L), False, XA_CARDINAL, &actualType, &actualFormat, &pidSize, &bytesAfter, &pidData);
  1031. if (pidData != nullptr)
  1032. {
  1033. const ScopedFreeData sfd2(pidData);
  1034. CARLA_SAFE_ASSERT_CONTINUE(status == Success);
  1035. CARLA_SAFE_ASSERT_CONTINUE(pidSize != 0);
  1036. if (*(ulong*)pidData == static_cast<ulong>(pid))
  1037. lastGoodWindowPID = window;
  1038. }
  1039. }
  1040. // ------------------------------------------------
  1041. // try using name (UTF-8)
  1042. ulong nameSize;
  1043. uchar* nameData = nullptr;
  1044. status = XGetWindowProperty(sd.display, window, _nwn, 0L, (~0L), False, utf8, &actualType, &actualFormat, &nameSize, &bytesAfter, &nameData);
  1045. if (nameData != nullptr)
  1046. {
  1047. const ScopedFreeData sfd2(nameData);
  1048. CARLA_SAFE_ASSERT_CONTINUE(status == Success);
  1049. if (nameSize != 0 && std::strstr((const char*)nameData, uiTitle) != nullptr)
  1050. lastGoodWindowNameUTF8 = window;
  1051. }
  1052. // ------------------------------------------------
  1053. // try using name (simple)
  1054. char* wmName = nullptr;
  1055. status = XFetchName(sd.display, window, &wmName);
  1056. if (wmName != nullptr)
  1057. {
  1058. const ScopedFreeData sfd2(wmName);
  1059. CARLA_SAFE_ASSERT_CONTINUE(status != 0);
  1060. if (std::strstr(wmName, uiTitle) != nullptr)
  1061. lastGoodWindowNameSimple = window;
  1062. }
  1063. }
  1064. if (lastGoodWindowPID == 0 && lastGoodWindowNameSimple == 0 && lastGoodWindowNameUTF8 == 0)
  1065. return false;
  1066. Window windowToMap;
  1067. if (lastGoodWindowPID != 0)
  1068. {
  1069. if (lastGoodWindowPID == lastGoodWindowNameSimple && lastGoodWindowPID == lastGoodWindowNameUTF8)
  1070. {
  1071. carla_stdout("Match found using pid, simple and UTF-8 name all at once, nice!");
  1072. windowToMap = lastGoodWindowPID;
  1073. }
  1074. else if (lastGoodWindowPID == lastGoodWindowNameUTF8)
  1075. {
  1076. carla_stdout("Match found using pid and UTF-8 name");
  1077. windowToMap = lastGoodWindowPID;
  1078. }
  1079. else if (lastGoodWindowPID == lastGoodWindowNameSimple)
  1080. {
  1081. carla_stdout("Match found using pid and simple name");
  1082. windowToMap = lastGoodWindowPID;
  1083. }
  1084. else if (lastGoodWindowNameUTF8 != 0)
  1085. {
  1086. if (lastGoodWindowNameUTF8 == lastGoodWindowNameSimple)
  1087. {
  1088. carla_stdout("Match found using simple and UTF-8 name (ignoring pid)");
  1089. windowToMap = lastGoodWindowNameUTF8;
  1090. }
  1091. else
  1092. {
  1093. carla_stdout("Match found using UTF-8 name (ignoring pid)");
  1094. windowToMap = lastGoodWindowNameUTF8;
  1095. }
  1096. }
  1097. else
  1098. {
  1099. carla_stdout("Match found using pid");
  1100. windowToMap = lastGoodWindowPID;
  1101. }
  1102. }
  1103. else if (lastGoodWindowNameUTF8 != 0)
  1104. {
  1105. if (lastGoodWindowNameUTF8 == lastGoodWindowNameSimple)
  1106. {
  1107. carla_stdout("Match found using simple and UTF-8 name");
  1108. windowToMap = lastGoodWindowNameUTF8;
  1109. }
  1110. else
  1111. {
  1112. carla_stdout("Match found using UTF-8 name");
  1113. windowToMap = lastGoodWindowNameUTF8;
  1114. }
  1115. }
  1116. else
  1117. {
  1118. carla_stdout("Match found using simple name");
  1119. windowToMap = lastGoodWindowNameSimple;
  1120. }
  1121. const Atom _nwt = XInternAtom(sd.display ,"_NET_WM_STATE", False);
  1122. const Atom _nws[2] = {
  1123. XInternAtom(sd.display, "_NET_WM_STATE_SKIP_TASKBAR", False),
  1124. XInternAtom(sd.display, "_NET_WM_STATE_SKIP_PAGER", False)
  1125. };
  1126. XChangeProperty(sd.display, windowToMap, _nwt, XA_ATOM, 32, PropModeAppend, (const uchar*)_nws, 2);
  1127. const Atom _nwi = XInternAtom(sd.display, "_NET_WM_ICON", False);
  1128. XChangeProperty(sd.display, windowToMap, _nwi, XA_CARDINAL, 32, PropModeReplace, (const uchar*)sCarlaX11Icon, sCarlaX11IconSize);
  1129. const Window hostWinId((Window)winId);
  1130. XSetTransientForHint(sd.display, windowToMap, hostWinId);
  1131. if (centerUI && false /* moving the window after being shown isn't pretty... */)
  1132. {
  1133. int hostX, hostY, pluginX, pluginY;
  1134. uint hostWidth, hostHeight, pluginWidth, pluginHeight, border, depth;
  1135. Window retWindow;
  1136. if (XGetGeometry(sd.display, hostWinId, &retWindow, &hostX, &hostY, &hostWidth, &hostHeight, &border, &depth) != 0 &&
  1137. XGetGeometry(sd.display, windowToMap, &retWindow, &pluginX, &pluginY, &pluginWidth, &pluginHeight, &border, &depth) != 0)
  1138. {
  1139. if (XTranslateCoordinates(sd.display, hostWinId, rootWindow, hostX, hostY, &hostX, &hostY, &retWindow) == True &&
  1140. XTranslateCoordinates(sd.display, windowToMap, rootWindow, pluginX, pluginY, &pluginX, &pluginY, &retWindow) == True)
  1141. {
  1142. const int newX = hostX + int(hostWidth/2 - pluginWidth/2);
  1143. const int newY = hostY + int(hostHeight/2 - pluginHeight/2);
  1144. XMoveWindow(sd.display, windowToMap, newX, newY);
  1145. }
  1146. }
  1147. }
  1148. // focusing the host UI and then the plugin UI forces the WM to repaint the plugin window icon
  1149. XRaiseWindow(sd.display, hostWinId);
  1150. XSetInputFocus(sd.display, hostWinId, RevertToPointerRoot, CurrentTime);
  1151. XRaiseWindow(sd.display, windowToMap);
  1152. XSetInputFocus(sd.display, windowToMap, RevertToPointerRoot, CurrentTime);
  1153. XFlush(sd.display);
  1154. return true;
  1155. #endif
  1156. #ifdef CARLA_OS_MAC
  1157. uint const hints = kCGWindowListOptionOnScreenOnly|kCGWindowListExcludeDesktopElements;
  1158. CFArrayRef const windowListRef = CGWindowListCopyWindowInfo(hints, kCGNullWindowID);
  1159. const NSArray* const windowList = (const NSArray*)windowListRef;
  1160. int windowToMap, windowWithPID = 0, windowWithNameAndPID = 0;
  1161. const NSDictionary* entry;
  1162. for (entry in windowList)
  1163. {
  1164. // FIXME: is this needed? is old version safe?
  1165. #if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
  1166. if ([entry[(id)kCGWindowSharingState] intValue] == kCGWindowSharingNone)
  1167. continue;
  1168. NSString* const windowName = entry[(id)kCGWindowName];
  1169. int const windowNumber = [entry[(id)kCGWindowNumber] intValue];
  1170. uintptr_t const windowPID = [entry[(id)kCGWindowOwnerPID] intValue];
  1171. #else
  1172. if ([[entry objectForKey:(id)kCGWindowSharingState] intValue] == kCGWindowSharingNone)
  1173. continue;
  1174. NSString* const windowName = [entry objectForKey:(id)kCGWindowName];
  1175. int const windowNumber = [[entry objectForKey:(id)kCGWindowNumber] intValue];
  1176. uintptr_t const windowPID = [[entry objectForKey:(id)kCGWindowOwnerPID] intValue];
  1177. #endif
  1178. if (windowPID != pid)
  1179. continue;
  1180. windowWithPID = windowNumber;
  1181. if (windowName != nullptr && std::strcmp([windowName UTF8String], uiTitle) == 0)
  1182. windowWithNameAndPID = windowNumber;
  1183. }
  1184. CFRelease(windowListRef);
  1185. if (windowWithNameAndPID != 0)
  1186. {
  1187. carla_stdout("Match found using pid and name");
  1188. windowToMap = windowWithNameAndPID;
  1189. }
  1190. else if (windowWithPID != 0)
  1191. {
  1192. carla_stdout("Match found using pid");
  1193. windowToMap = windowWithPID;
  1194. }
  1195. else
  1196. {
  1197. return false;
  1198. }
  1199. NSWindow* const parentWindow = [NSApp windowWithWindowNumber:winId];
  1200. CARLA_SAFE_ASSERT_RETURN(parentWindow != nullptr, false);
  1201. [parentWindow orderWindow:NSWindowBelow
  1202. relativeTo:windowToMap];
  1203. return true;
  1204. #endif
  1205. #ifdef CARLA_OS_WIN
  1206. if (HWND const childWindow = FindWindowA(nullptr, uiTitle))
  1207. {
  1208. HWND const parentWindow = (HWND)winId;
  1209. SetWindowLongPtr(childWindow, GWLP_HWNDPARENT, (LONG_PTR)parentWindow);
  1210. if (centerUI)
  1211. {
  1212. RECT rectChild, rectParent;
  1213. if (GetWindowRect(childWindow, &rectChild) && GetWindowRect(parentWindow, &rectParent))
  1214. {
  1215. SetWindowPos(childWindow, parentWindow,
  1216. rectParent.left + (rectChild.right-rectChild.left)/2,
  1217. rectParent.top + (rectChild.bottom-rectChild.top)/2,
  1218. 0, 0, SWP_NOSIZE);
  1219. }
  1220. }
  1221. carla_stdout("Match found using window name");
  1222. return true;
  1223. }
  1224. return false;
  1225. #endif
  1226. // fallback, may be unused
  1227. return true;
  1228. (void)pid; (void)centerUI;
  1229. }
  1230. #endif // BUILD_BRIDGE_ALTERNATIVE_ARCH
  1231. // -----------------------------------------------------
  1232. #ifdef HAVE_X11
  1233. CarlaPluginUI* CarlaPluginUI::newX11(Callback* const cb,
  1234. const uintptr_t parentId,
  1235. const bool isStandalone,
  1236. const bool isResizable,
  1237. const bool isLV2)
  1238. {
  1239. return new X11PluginUI(cb, parentId, isStandalone, isResizable, isLV2);
  1240. }
  1241. #endif
  1242. #ifdef CARLA_OS_MAC
  1243. CarlaPluginUI* CarlaPluginUI::newCocoa(Callback* const cb,
  1244. const uintptr_t parentId,
  1245. const bool isStandalone,
  1246. const bool isResizable)
  1247. {
  1248. return new CocoaPluginUI(cb, parentId, isStandalone, isResizable);
  1249. }
  1250. #endif
  1251. #ifdef CARLA_OS_WIN
  1252. CarlaPluginUI* CarlaPluginUI::newWindows(Callback* const cb,
  1253. const uintptr_t parentId,
  1254. const bool isStandalone,
  1255. const bool isResizable)
  1256. {
  1257. return new WindowsPluginUI(cb, parentId, isStandalone, isResizable);
  1258. }
  1259. #endif
  1260. // -----------------------------------------------------