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.

1158 lines
33KB

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