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 34KB

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