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.

CarlaPluginUI.cpp 36KB

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