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.

1188 lines
35KB

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