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.

1030 lines
30KB

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