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.

1261 lines
37KB

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