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.

1019 lines
29KB

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