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.

1515 lines
47KB

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