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

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510
  1. /*
  2. * Carla Plugin UI
  3. * Copyright (C) 2014-2022 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 <pthread.h>
  21. # include <sys/types.h>
  22. # include <X11/Xatom.h>
  23. # include <X11/Xlib.h>
  24. # include <X11/Xutil.h>
  25. # include "CarlaPluginUI_X11Icon.hpp"
  26. #endif
  27. #ifdef CARLA_OS_MAC
  28. # include "CarlaMacUtils.hpp"
  29. # import <Cocoa/Cocoa.h>
  30. #endif
  31. #ifdef CARLA_OS_WIN
  32. # include <ctime>
  33. # include "water/common.hpp"
  34. #endif
  35. #ifndef CARLA_PLUGIN_UI_CLASS_PREFIX
  36. # error CARLA_PLUGIN_UI_CLASS_PREFIX undefined
  37. #endif
  38. // ---------------------------------------------------------------------------------------------------------------------
  39. // X11
  40. #ifdef HAVE_X11
  41. typedef void (*EventProcPtr)(XEvent* ev);
  42. static const uint X11Key_Escape = 9;
  43. static bool gErrorTriggered = false;
  44. # if defined(__GNUC__) && (__GNUC__ >= 5) && ! defined(__clang__)
  45. # pragma GCC diagnostic push
  46. # pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
  47. # endif
  48. static pthread_mutex_t gErrorMutex = PTHREAD_MUTEX_INITIALIZER;
  49. # if defined(__GNUC__) && (__GNUC__ >= 5) && ! defined(__clang__)
  50. # pragma GCC diagnostic pop
  51. # endif
  52. static int temporaryErrorHandler(Display*, XErrorEvent*)
  53. {
  54. gErrorTriggered = true;
  55. return 0;
  56. }
  57. class X11PluginUI : public CarlaPluginUI
  58. {
  59. public:
  60. X11PluginUI(Callback* const cb, const uintptr_t parentId,
  61. const bool isStandalone, const bool isResizable, const bool canMonitorChildren) noexcept
  62. : CarlaPluginUI(cb, isStandalone, isResizable),
  63. fDisplay(nullptr),
  64. fHostWindow(0),
  65. fChildWindow(0),
  66. fChildWindowConfigured(false),
  67. fChildWindowMonitoring(isResizable || canMonitorChildren),
  68. fIsVisible(false),
  69. fFirstShow(true),
  70. fSetSizeCalledAtLeastOnce(false),
  71. fEventProc(nullptr)
  72. {
  73. fDisplay = XOpenDisplay(nullptr);
  74. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  75. const int screen = DefaultScreen(fDisplay);
  76. XSetWindowAttributes attr;
  77. carla_zeroStruct(attr);
  78. attr.event_mask = KeyPressMask|KeyReleaseMask|FocusChangeMask;
  79. if (fChildWindowMonitoring)
  80. attr.event_mask |= StructureNotifyMask|SubstructureNotifyMask;
  81. fHostWindow = XCreateWindow(fDisplay, RootWindow(fDisplay, screen),
  82. 0, 0, 300, 300, 0,
  83. DefaultDepth(fDisplay, screen),
  84. InputOutput,
  85. DefaultVisual(fDisplay, screen),
  86. CWBorderPixel|CWEventMask, &attr);
  87. CARLA_SAFE_ASSERT_RETURN(fHostWindow != 0,);
  88. XGrabKey(fDisplay, X11Key_Escape, AnyModifier, fHostWindow, 1, GrabModeAsync, GrabModeAsync);
  89. Atom wmDelete = XInternAtom(fDisplay, "WM_DELETE_WINDOW", True);
  90. XSetWMProtocols(fDisplay, fHostWindow, &wmDelete, 1);
  91. const pid_t pid = getpid();
  92. const Atom _nwp = XInternAtom(fDisplay, "_NET_WM_PID", False);
  93. XChangeProperty(fDisplay, fHostWindow, _nwp, XA_CARDINAL, 32, PropModeReplace, (const uchar*)&pid, 1);
  94. const Atom _nwi = XInternAtom(fDisplay, "_NET_WM_ICON", False);
  95. XChangeProperty(fDisplay, fHostWindow, _nwi, XA_CARDINAL, 32, PropModeReplace, (const uchar*)sCarlaX11Icon, sCarlaX11IconSize);
  96. const Atom _wt = XInternAtom(fDisplay, "_NET_WM_WINDOW_TYPE", False);
  97. // Setting the window to both dialog and normal will produce a decorated floating dialog
  98. // Order is important: DIALOG needs to come before NORMAL
  99. const Atom _wts[2] = {
  100. XInternAtom(fDisplay, "_NET_WM_WINDOW_TYPE_DIALOG", False),
  101. XInternAtom(fDisplay, "_NET_WM_WINDOW_TYPE_NORMAL", False)
  102. };
  103. XChangeProperty(fDisplay, fHostWindow, _wt, XA_ATOM, 32, PropModeReplace, (const uchar*)&_wts, 2);
  104. if (parentId != 0)
  105. setTransientWinId(parentId);
  106. }
  107. ~X11PluginUI() override
  108. {
  109. CARLA_SAFE_ASSERT(! fIsVisible);
  110. if (fDisplay == nullptr)
  111. return;
  112. if (fIsVisible)
  113. {
  114. XUnmapWindow(fDisplay, fHostWindow);
  115. fIsVisible = false;
  116. }
  117. if (fHostWindow != 0)
  118. {
  119. XDestroyWindow(fDisplay, fHostWindow);
  120. fHostWindow = 0;
  121. }
  122. XCloseDisplay(fDisplay);
  123. fDisplay = nullptr;
  124. }
  125. void show() override
  126. {
  127. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  128. CARLA_SAFE_ASSERT_RETURN(fHostWindow != 0,);
  129. if (fFirstShow)
  130. {
  131. if (const Window childWindow = getChildWindow())
  132. {
  133. if (! fSetSizeCalledAtLeastOnce)
  134. {
  135. int width = 0;
  136. int height = 0;
  137. XWindowAttributes attrs;
  138. carla_zeroStruct(attrs);
  139. pthread_mutex_lock(&gErrorMutex);
  140. const XErrorHandler oldErrorHandler = XSetErrorHandler(temporaryErrorHandler);
  141. gErrorTriggered = false;
  142. if (XGetWindowAttributes(fDisplay, childWindow, &attrs))
  143. {
  144. width = attrs.width;
  145. height = attrs.height;
  146. }
  147. XSetErrorHandler(oldErrorHandler);
  148. pthread_mutex_unlock(&gErrorMutex);
  149. if (width == 0 && height == 0)
  150. {
  151. XSizeHints sizeHints;
  152. carla_zeroStruct(sizeHints);
  153. if (XGetNormalHints(fDisplay, childWindow, &sizeHints))
  154. {
  155. if (sizeHints.flags & PSize)
  156. {
  157. width = sizeHints.width;
  158. height = sizeHints.height;
  159. }
  160. else if (sizeHints.flags & PBaseSize)
  161. {
  162. width = sizeHints.base_width;
  163. height = sizeHints.base_height;
  164. }
  165. }
  166. }
  167. if (width > 1 && height > 1)
  168. setSize(static_cast<uint>(width), static_cast<uint>(height), false);
  169. }
  170. const Atom _xevp = XInternAtom(fDisplay, "_XEventProc", False);
  171. pthread_mutex_lock(&gErrorMutex);
  172. const XErrorHandler oldErrorHandler(XSetErrorHandler(temporaryErrorHandler));
  173. gErrorTriggered = false;
  174. Atom actualType;
  175. int actualFormat;
  176. ulong nitems, bytesAfter;
  177. uchar* data = nullptr;
  178. XGetWindowProperty(fDisplay, childWindow, _xevp, 0, 1, False, AnyPropertyType,
  179. &actualType, &actualFormat, &nitems, &bytesAfter, &data);
  180. XSetErrorHandler(oldErrorHandler);
  181. pthread_mutex_unlock(&gErrorMutex);
  182. if (nitems == 1 && ! gErrorTriggered)
  183. {
  184. fEventProc = *reinterpret_cast<EventProcPtr*>(data);
  185. XMapRaised(fDisplay, childWindow);
  186. }
  187. }
  188. }
  189. fIsVisible = true;
  190. fFirstShow = false;
  191. XMapRaised(fDisplay, fHostWindow);
  192. XSync(fDisplay, False);
  193. }
  194. void hide() override
  195. {
  196. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  197. CARLA_SAFE_ASSERT_RETURN(fHostWindow != 0,);
  198. fIsVisible = false;
  199. XUnmapWindow(fDisplay, fHostWindow);
  200. XFlush(fDisplay);
  201. }
  202. void idle() override
  203. {
  204. // prevent recursion
  205. if (fIsIdling) return;
  206. int nextWidth = 0;
  207. int nextHeight = 0;
  208. fIsIdling = true;
  209. for (XEvent event; XPending(fDisplay) > 0;)
  210. {
  211. XNextEvent(fDisplay, &event);
  212. if (! fIsVisible)
  213. continue;
  214. char* type = nullptr;
  215. switch (event.type)
  216. {
  217. case ConfigureNotify:
  218. CARLA_SAFE_ASSERT_CONTINUE(fCallback != nullptr);
  219. CARLA_SAFE_ASSERT_CONTINUE(event.xconfigure.width > 0);
  220. CARLA_SAFE_ASSERT_CONTINUE(event.xconfigure.height > 0);
  221. if (event.xconfigure.window == fHostWindow)
  222. {
  223. const uint width = static_cast<uint>(event.xconfigure.width);
  224. const uint height = static_cast<uint>(event.xconfigure.height);
  225. if (fChildWindow != 0)
  226. {
  227. if (! fChildWindowConfigured)
  228. {
  229. pthread_mutex_lock(&gErrorMutex);
  230. const XErrorHandler oldErrorHandler = XSetErrorHandler(temporaryErrorHandler);
  231. gErrorTriggered = false;
  232. XSizeHints sizeHints;
  233. carla_zeroStruct(sizeHints);
  234. if (XGetNormalHints(fDisplay, fChildWindow, &sizeHints) && !gErrorTriggered)
  235. {
  236. XSetNormalHints(fDisplay, fHostWindow, &sizeHints);
  237. }
  238. else
  239. {
  240. carla_stdout("Caught errors while accessing child window");
  241. fChildWindow = 0;
  242. }
  243. fChildWindowConfigured = true;
  244. XSetErrorHandler(oldErrorHandler);
  245. pthread_mutex_unlock(&gErrorMutex);
  246. }
  247. if (fChildWindow != 0)
  248. XResizeWindow(fDisplay, fChildWindow, width, height);
  249. }
  250. fCallback->handlePluginUIResized(width, height);
  251. }
  252. else if (fChildWindowMonitoring && event.xconfigure.window == fChildWindow && fChildWindow != 0)
  253. {
  254. nextWidth = event.xconfigure.width;
  255. nextHeight = event.xconfigure.height;
  256. }
  257. break;
  258. case ClientMessage:
  259. type = XGetAtomName(fDisplay, event.xclient.message_type);
  260. CARLA_SAFE_ASSERT_CONTINUE(type != nullptr);
  261. if (std::strcmp(type, "WM_PROTOCOLS") == 0)
  262. {
  263. fIsVisible = false;
  264. CARLA_SAFE_ASSERT_CONTINUE(fCallback != nullptr);
  265. fCallback->handlePluginUIClosed();
  266. }
  267. break;
  268. case KeyRelease:
  269. if (event.xkey.keycode == X11Key_Escape)
  270. {
  271. fIsVisible = false;
  272. CARLA_SAFE_ASSERT_CONTINUE(fCallback != nullptr);
  273. fCallback->handlePluginUIClosed();
  274. }
  275. break;
  276. case FocusIn:
  277. if (fChildWindow == 0)
  278. fChildWindow = getChildWindow();
  279. if (fChildWindow != 0)
  280. {
  281. XWindowAttributes wa;
  282. carla_zeroStruct(wa);
  283. if (XGetWindowAttributes(fDisplay, fChildWindow, &wa) && wa.map_state == IsViewable)
  284. XSetInputFocus(fDisplay, fChildWindow, RevertToPointerRoot, CurrentTime);
  285. }
  286. break;
  287. }
  288. if (type != nullptr)
  289. XFree(type);
  290. else if (fEventProc != nullptr && event.type != FocusIn && event.type != FocusOut)
  291. fEventProc(&event);
  292. }
  293. if (nextWidth != 0 && nextHeight != 0 && fChildWindow != 0)
  294. {
  295. XSizeHints sizeHints;
  296. carla_zeroStruct(sizeHints);
  297. if (XGetNormalHints(fDisplay, fChildWindow, &sizeHints))
  298. XSetNormalHints(fDisplay, fHostWindow, &sizeHints);
  299. XResizeWindow(fDisplay, fHostWindow, static_cast<uint>(nextWidth), static_cast<uint>(nextHeight));
  300. XFlush(fDisplay);
  301. }
  302. fIsIdling = false;
  303. }
  304. void focus() override
  305. {
  306. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  307. CARLA_SAFE_ASSERT_RETURN(fHostWindow != 0,);
  308. XWindowAttributes wa;
  309. carla_zeroStruct(wa);
  310. CARLA_SAFE_ASSERT_RETURN(XGetWindowAttributes(fDisplay, fHostWindow, &wa),);
  311. if (wa.map_state == IsViewable)
  312. {
  313. XRaiseWindow(fDisplay, fHostWindow);
  314. XSetInputFocus(fDisplay, fHostWindow, RevertToPointerRoot, CurrentTime);
  315. XSync(fDisplay, False);
  316. }
  317. }
  318. void setSize(const uint width, const uint height, const bool forceUpdate) override
  319. {
  320. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  321. CARLA_SAFE_ASSERT_RETURN(fHostWindow != 0,);
  322. fSetSizeCalledAtLeastOnce = true;
  323. XResizeWindow(fDisplay, fHostWindow, width, height);
  324. if (fChildWindow != 0)
  325. XResizeWindow(fDisplay, fChildWindow, width, height);
  326. if (! fIsResizable)
  327. {
  328. XSizeHints sizeHints;
  329. carla_zeroStruct(sizeHints);
  330. sizeHints.flags = PSize|PMinSize|PMaxSize;
  331. sizeHints.width = static_cast<int>(width);
  332. sizeHints.height = static_cast<int>(height);
  333. sizeHints.min_width = static_cast<int>(width);
  334. sizeHints.min_height = static_cast<int>(height);
  335. sizeHints.max_width = static_cast<int>(width);
  336. sizeHints.max_height = static_cast<int>(height);
  337. XSetNormalHints(fDisplay, fHostWindow, &sizeHints);
  338. }
  339. if (forceUpdate)
  340. XSync(fDisplay, False);
  341. }
  342. void setTitle(const char* const title) override
  343. {
  344. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  345. CARLA_SAFE_ASSERT_RETURN(fHostWindow != 0,);
  346. XStoreName(fDisplay, fHostWindow, title);
  347. const Atom _nwn = XInternAtom(fDisplay, "_NET_WM_NAME", False);
  348. const Atom utf8 = XInternAtom(fDisplay, "UTF8_STRING", True);
  349. XChangeProperty(fDisplay, fHostWindow, _nwn, utf8, 8,
  350. PropModeReplace,
  351. (const uchar*)(title),
  352. (int)strlen(title));
  353. }
  354. void setTransientWinId(const uintptr_t winId) override
  355. {
  356. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  357. CARLA_SAFE_ASSERT_RETURN(fHostWindow != 0,);
  358. XSetTransientForHint(fDisplay, fHostWindow, static_cast<Window>(winId));
  359. }
  360. void setChildWindow(void* const winId) override
  361. {
  362. CARLA_SAFE_ASSERT_RETURN(winId != nullptr,);
  363. fChildWindow = (Window)winId;
  364. }
  365. void* getPtr() const noexcept override
  366. {
  367. return (void*)fHostWindow;
  368. }
  369. void* getDisplay() const noexcept override
  370. {
  371. return fDisplay;
  372. }
  373. protected:
  374. Window getChildWindow() const
  375. {
  376. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr, 0);
  377. CARLA_SAFE_ASSERT_RETURN(fHostWindow != 0, 0);
  378. Window rootWindow, parentWindow, ret = 0;
  379. Window* childWindows = nullptr;
  380. uint numChildren = 0;
  381. XQueryTree(fDisplay, fHostWindow, &rootWindow, &parentWindow, &childWindows, &numChildren);
  382. if (numChildren > 0 && childWindows != nullptr)
  383. {
  384. ret = childWindows[0];
  385. XFree(childWindows);
  386. }
  387. return ret;
  388. }
  389. private:
  390. Display* fDisplay;
  391. Window fHostWindow;
  392. Window fChildWindow;
  393. bool fChildWindowConfigured;
  394. bool fChildWindowMonitoring;
  395. bool fIsVisible;
  396. bool fFirstShow;
  397. bool fSetSizeCalledAtLeastOnce;
  398. EventProcPtr fEventProc;
  399. CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(X11PluginUI)
  400. };
  401. #endif // HAVE_X11
  402. // ---------------------------------------------------------------------------------------------------------------------
  403. // MacOS / Cocoa
  404. #ifdef CARLA_OS_MAC
  405. #if defined(BUILD_BRIDGE_ALTERNATIVE_ARCH)
  406. # define CarlaPluginWindow CARLA_JOIN_MACRO3(CarlaPluginWindowBridgedArch, CARLA_BACKEND_NAMESPACE, CARLA_PLUGIN_UI_CLASS_PREFIX)
  407. # define CarlaPluginWindowDelegate CARLA_JOIN_MACRO3(CarlaPluginWindowDelegateBridgedArch, CARLA_BACKEND_NAMESPACE, CARLA_PLUGIN_UI_CLASS_PREFIX)
  408. #elif defined(BUILD_BRIDGE)
  409. # define CarlaPluginWindow CARLA_JOIN_MACRO3(CarlaPluginWindowBridged, CARLA_BACKEND_NAMESPACE, CARLA_PLUGIN_UI_CLASS_PREFIX)
  410. # define CarlaPluginWindowDelegate CARLA_JOIN_MACRO3(CarlaPluginWindowDelegateBridged, CARLA_BACKEND_NAMESPACE, CARLA_PLUGIN_UI_CLASS_PREFIX)
  411. #else
  412. # define CarlaPluginWindow CARLA_JOIN_MACRO3(CarlaPluginWindow, CARLA_BACKEND_NAMESPACE, CARLA_PLUGIN_UI_CLASS_PREFIX)
  413. # define CarlaPluginWindowDelegate CARLA_JOIN_MACRO3(CarlaPluginWindowDelegate, CARLA_BACKEND_NAMESPACE, CARLA_PLUGIN_UI_CLASS_PREFIX)
  414. #endif
  415. @interface CarlaPluginWindow : NSWindow
  416. - (id) initWithContentRect:(NSRect)contentRect
  417. styleMask:(unsigned int)aStyle
  418. backing:(NSBackingStoreType)bufferingType
  419. defer:(BOOL)flag;
  420. - (BOOL) canBecomeKeyWindow;
  421. - (BOOL) canBecomeMainWindow;
  422. @end
  423. @implementation CarlaPluginWindow
  424. - (id)initWithContentRect:(NSRect)contentRect
  425. styleMask:(unsigned int)aStyle
  426. backing:(NSBackingStoreType)bufferingType
  427. defer:(BOOL)flag
  428. {
  429. NSWindow* result = [super initWithContentRect:contentRect
  430. styleMask:aStyle
  431. backing:bufferingType
  432. defer:flag];
  433. [result setAcceptsMouseMovedEvents:YES];
  434. return (CarlaPluginWindow*)result;
  435. // unused
  436. (void)flag;
  437. }
  438. - (BOOL)canBecomeKeyWindow
  439. {
  440. return YES;
  441. }
  442. - (BOOL)canBecomeMainWindow
  443. {
  444. return NO;
  445. }
  446. @end
  447. @interface CarlaPluginWindowDelegate : NSObject<NSWindowDelegate>
  448. {
  449. CarlaPluginUI::Callback* callback;
  450. CarlaPluginWindow* window;
  451. }
  452. - (instancetype)initWithWindowAndCallback:(CarlaPluginWindow*)window
  453. callback:(CarlaPluginUI::Callback*)callback2;
  454. - (BOOL)windowShouldClose:(id)sender;
  455. - (NSSize)windowWillResize:(NSWindow*)sender toSize:(NSSize)frameSize;
  456. @end
  457. @implementation CarlaPluginWindowDelegate
  458. - (instancetype)initWithWindowAndCallback:(CarlaPluginWindow*)window2
  459. callback:(CarlaPluginUI::Callback*)callback2
  460. {
  461. if ((self = [super init]))
  462. {
  463. callback = callback2;
  464. window = window2;
  465. }
  466. return self;
  467. }
  468. - (BOOL)windowShouldClose:(id)sender
  469. {
  470. if (callback != nil)
  471. callback->handlePluginUIClosed();
  472. return NO;
  473. // unused
  474. (void)sender;
  475. }
  476. - (NSSize)windowWillResize:(NSWindow*)sender toSize:(NSSize)frameSize
  477. {
  478. for (NSView* subview in [[window contentView] subviews])
  479. {
  480. const NSSize minSize = [subview fittingSize];
  481. if (frameSize.width < minSize.width)
  482. frameSize.width = minSize.width;
  483. if (frameSize.height < minSize.height)
  484. frameSize.height = minSize.height;
  485. break;
  486. }
  487. return frameSize;
  488. }
  489. /*
  490. - (void)windowDidResize:(NSWindow*)sender
  491. {
  492. carla_stdout("window did resize %p %f %f", sender, [window frame].size.width, [window frame].size.height);
  493. const NSSize size = [window frame].size;
  494. NSView* const view = [window contentView];
  495. for (NSView* subview in [view subviews])
  496. {
  497. [subview setFrameSize:size];
  498. break;
  499. }
  500. }
  501. */
  502. @end
  503. class CocoaPluginUI : public CarlaPluginUI
  504. {
  505. public:
  506. CocoaPluginUI(Callback* const callback, const uintptr_t parentId, const bool isStandalone, const bool isResizable) noexcept
  507. : CarlaPluginUI(callback, isStandalone, isResizable),
  508. fView(nullptr),
  509. fParentWindow(nullptr),
  510. fWindow(nullptr)
  511. {
  512. carla_debug("CocoaPluginUI::CocoaPluginUI(%p, " P_UINTPTR, "%s)", callback, parentId, bool2str(isResizable));
  513. const CARLA_BACKEND_NAMESPACE::AutoNSAutoreleasePool arp;
  514. [NSApplication sharedApplication];
  515. fView = [[NSView new]retain];
  516. CARLA_SAFE_ASSERT_RETURN(fView != nullptr,)
  517. uint style = NSClosableWindowMask | NSTitledWindowMask;
  518. /*
  519. if (isResizable)
  520. style |= NSResizableWindowMask;
  521. */
  522. const NSRect frame = NSMakeRect(0, 0, 100, 100);
  523. fWindow = [[[CarlaPluginWindow alloc]
  524. initWithContentRect:frame
  525. styleMask:style
  526. backing:NSBackingStoreBuffered
  527. defer:NO
  528. ] retain];
  529. if (fWindow == nullptr)
  530. {
  531. [fView release];
  532. fView = nullptr;
  533. return;
  534. }
  535. ((NSWindow*)fWindow).delegate = [[[CarlaPluginWindowDelegate alloc]
  536. initWithWindowAndCallback:fWindow
  537. callback:callback] retain];
  538. /*
  539. if (isResizable)
  540. {
  541. [fView setAutoresizingMask:(NSViewWidthSizable |
  542. NSViewHeightSizable |
  543. NSViewMinXMargin |
  544. NSViewMaxXMargin |
  545. NSViewMinYMargin |
  546. NSViewMaxYMargin)];
  547. [fView setAutoresizesSubviews:YES];
  548. }
  549. else
  550. */
  551. {
  552. [fView setAutoresizingMask:NSViewNotSizable];
  553. [fView setAutoresizesSubviews:NO];
  554. [[fWindow standardWindowButton:NSWindowZoomButton] setHidden:YES];
  555. }
  556. [fWindow setContentView:fView];
  557. [fWindow makeFirstResponder:fView];
  558. [fView setHidden:NO];
  559. if (parentId != 0)
  560. setTransientWinId(parentId);
  561. }
  562. ~CocoaPluginUI() override
  563. {
  564. carla_debug("CocoaPluginUI::~CocoaPluginUI()");
  565. if (fView == nullptr)
  566. return;
  567. [fView setHidden:YES];
  568. [fView removeFromSuperview];
  569. [fWindow close];
  570. [fView release];
  571. [fWindow release];
  572. }
  573. void show() override
  574. {
  575. carla_debug("CocoaPluginUI::show()");
  576. CARLA_SAFE_ASSERT_RETURN(fView != nullptr,);
  577. if (fParentWindow != nullptr)
  578. {
  579. [fParentWindow addChildWindow:fWindow
  580. ordered:NSWindowAbove];
  581. }
  582. else
  583. {
  584. [fWindow setIsVisible:YES];
  585. }
  586. }
  587. void hide() override
  588. {
  589. carla_debug("CocoaPluginUI::hide()");
  590. CARLA_SAFE_ASSERT_RETURN(fView != nullptr,);
  591. [fWindow setIsVisible:NO];
  592. if (fParentWindow != nullptr)
  593. [fParentWindow removeChildWindow:fWindow];
  594. }
  595. void idle() override
  596. {
  597. // carla_debug("CocoaPluginUI::idle()");
  598. for (NSView* subview in [fView subviews])
  599. {
  600. const NSSize viewSize = [fView frame].size;
  601. const NSSize subviewSize = [subview frame].size;
  602. if (viewSize.width != subviewSize.width || viewSize.height != subviewSize.height)
  603. {
  604. [fView setFrameSize:subviewSize];
  605. [fWindow setContentSize:subviewSize];
  606. }
  607. break;
  608. }
  609. }
  610. void focus() override
  611. {
  612. carla_debug("CocoaPluginUI::focus()");
  613. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  614. [fWindow makeKeyAndOrderFront:fWindow];
  615. [fWindow orderFrontRegardless];
  616. [NSApp activateIgnoringOtherApps:YES];
  617. }
  618. void setSize(const uint width, const uint height, const bool forceUpdate) override
  619. {
  620. carla_debug("CocoaPluginUI::setSize(%u, %u, %s)", width, height, bool2str(forceUpdate));
  621. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  622. CARLA_SAFE_ASSERT_RETURN(fView != nullptr,);
  623. const NSSize size = NSMakeSize(width, height);
  624. [fView setFrameSize:size];
  625. [fWindow setContentSize:size];
  626. // this is needed for a few plugins
  627. if (forceUpdate)
  628. {
  629. for (NSView* subview in [fView subviews])
  630. {
  631. [subview setFrame:[fView frame]];
  632. break;
  633. }
  634. }
  635. /*
  636. if (fIsResizable)
  637. {
  638. [fWindow setContentMinSize:NSMakeSize(1, 1)];
  639. [fWindow setContentMaxSize:NSMakeSize(99999, 99999)];
  640. }
  641. else
  642. {
  643. [fWindow setContentMinSize:size];
  644. [fWindow setContentMaxSize:size];
  645. }
  646. */
  647. if (forceUpdate)
  648. {
  649. // FIXME, not enough
  650. [fView setNeedsDisplay:YES];
  651. }
  652. }
  653. void setTitle(const char* const title) override
  654. {
  655. carla_debug("CocoaPluginUI::setTitle(\"%s\")", title);
  656. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  657. NSString* titleString = [[NSString alloc]
  658. initWithBytes:title
  659. length:strlen(title)
  660. encoding:NSUTF8StringEncoding];
  661. [fWindow setTitle:titleString];
  662. }
  663. void setTransientWinId(const uintptr_t winId) override
  664. {
  665. carla_debug("CocoaPluginUI::setTransientWinId(" P_UINTPTR ")", winId);
  666. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  667. NSWindow* const parentWindow = [NSApp windowWithWindowNumber:winId];
  668. CARLA_SAFE_ASSERT_RETURN(parentWindow != nullptr,);
  669. fParentWindow = parentWindow;
  670. if ([fWindow isVisible])
  671. [fParentWindow addChildWindow:fWindow
  672. ordered:NSWindowAbove];
  673. }
  674. void setChildWindow(void* const childWindow) override
  675. {
  676. carla_debug("CocoaPluginUI::setChildWindow(%p)", childWindow);
  677. CARLA_SAFE_ASSERT_RETURN(childWindow != nullptr,);
  678. NSView* const view = (NSView*)childWindow;
  679. const NSRect frame = [view frame];
  680. [fWindow setContentSize:frame.size];
  681. [fView setFrame:frame];
  682. [fView setNeedsDisplay:YES];
  683. }
  684. void* getPtr() const noexcept override
  685. {
  686. carla_debug("CocoaPluginUI::getPtr()");
  687. return (void*)fView;
  688. }
  689. void* getDisplay() const noexcept
  690. {
  691. carla_debug("CocoaPluginUI::getDisplay()");
  692. return (void*)fWindow;
  693. }
  694. private:
  695. NSView* fView;
  696. NSWindow* fParentWindow;
  697. CarlaPluginWindow* fWindow;
  698. CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CocoaPluginUI)
  699. };
  700. #endif // CARLA_OS_MAC
  701. // ---------------------------------------------------------------------------------------------------------------------
  702. // Windows
  703. #ifdef CARLA_OS_WIN
  704. #define CARLA_LOCAL_CLOSE_MSG (WM_USER + 50)
  705. static LRESULT CALLBACK wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
  706. class WindowsPluginUI : public CarlaPluginUI
  707. {
  708. public:
  709. WindowsPluginUI(Callback* const cb, const uintptr_t parentId, const bool isStandalone, const bool isResizable) noexcept
  710. : CarlaPluginUI(cb, isStandalone, isResizable),
  711. fWindow(nullptr),
  712. fChildWindow(nullptr),
  713. fParentWindow(nullptr),
  714. fIsVisible(false),
  715. fFirstShow(true)
  716. {
  717. // FIXME
  718. static int wc_count = 0;
  719. char classNameBuf[32];
  720. std::srand((std::time(nullptr)));
  721. std::snprintf(classNameBuf, 32, "CarlaWin-%d-%d", ++wc_count, std::rand());
  722. classNameBuf[31] = '\0';
  723. const HINSTANCE hInstance = water::getCurrentModuleInstanceHandle();
  724. carla_zeroStruct(fWindowClass);
  725. fWindowClass.style = CS_OWNDC;
  726. fWindowClass.lpfnWndProc = wndProc;
  727. fWindowClass.hInstance = hInstance;
  728. fWindowClass.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
  729. fWindowClass.hCursor = LoadCursor(hInstance, IDC_ARROW);
  730. fWindowClass.lpszClassName = strdup(classNameBuf);
  731. if (!RegisterClass(&fWindowClass)) {
  732. free((void*)fWindowClass.lpszClassName);
  733. return;
  734. }
  735. int winFlags = WS_POPUPWINDOW | WS_CAPTION;
  736. if (isResizable)
  737. winFlags |= WS_SIZEBOX;
  738. #ifdef BUILDING_CARLA_FOR_WINE
  739. const uint winType = WS_EX_DLGMODALFRAME;
  740. const HWND parent = nullptr;
  741. #else
  742. const uint winType = WS_EX_TOOLWINDOW;
  743. const HWND parent = (HWND)parentId;
  744. #endif
  745. fWindow = CreateWindowEx(winType,
  746. classNameBuf, "Carla Plugin UI", winFlags,
  747. CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
  748. parent, nullptr,
  749. hInstance, nullptr);
  750. if (fWindow == nullptr)
  751. {
  752. const DWORD errorCode = ::GetLastError();
  753. carla_stderr2("CreateWindowEx failed with error code 0x%x, class name was '%s'",
  754. errorCode, fWindowClass.lpszClassName);
  755. UnregisterClass(fWindowClass.lpszClassName, nullptr);
  756. free((void*)fWindowClass.lpszClassName);
  757. return;
  758. }
  759. SetWindowLongPtr(fWindow, GWLP_USERDATA, (LONG_PTR)this);
  760. #ifndef BUILDING_CARLA_FOR_WINE
  761. if (parentId != 0)
  762. setTransientWinId(parentId);
  763. #endif
  764. return;
  765. // maybe unused
  766. (void)parentId;
  767. }
  768. ~WindowsPluginUI() override
  769. {
  770. CARLA_SAFE_ASSERT(! fIsVisible);
  771. if (fWindow != 0)
  772. {
  773. if (fIsVisible)
  774. ShowWindow(fWindow, SW_HIDE);
  775. DestroyWindow(fWindow);
  776. fWindow = 0;
  777. }
  778. // FIXME
  779. UnregisterClass(fWindowClass.lpszClassName, nullptr);
  780. free((void*)fWindowClass.lpszClassName);
  781. }
  782. void show() override
  783. {
  784. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  785. if (fFirstShow)
  786. {
  787. fFirstShow = false;
  788. RECT rectChild, rectParent;
  789. if (fChildWindow != nullptr && GetWindowRect(fChildWindow, &rectChild))
  790. setSize(rectChild.right - rectChild.left, rectChild.bottom - rectChild.top, false);
  791. if (fParentWindow != nullptr &&
  792. GetWindowRect(fWindow, &rectChild) &&
  793. GetWindowRect(fParentWindow, &rectParent))
  794. {
  795. SetWindowPos(fWindow, fParentWindow,
  796. rectParent.left + (rectChild.right-rectChild.left)/2,
  797. rectParent.top + (rectChild.bottom-rectChild.top)/2,
  798. 0, 0, SWP_SHOWWINDOW|SWP_NOSIZE);
  799. }
  800. else
  801. {
  802. ShowWindow(fWindow, SW_SHOWNORMAL);
  803. }
  804. }
  805. else
  806. {
  807. ShowWindow(fWindow, SW_RESTORE);
  808. }
  809. fIsVisible = true;
  810. UpdateWindow(fWindow);
  811. }
  812. void hide() override
  813. {
  814. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  815. ShowWindow(fWindow, SW_HIDE);
  816. fIsVisible = false;
  817. UpdateWindow(fWindow);
  818. }
  819. void idle() override
  820. {
  821. if (fIsIdling || fWindow == nullptr)
  822. return;
  823. MSG msg;
  824. fIsIdling = true;
  825. while (::PeekMessage(&msg, fWindow, 0, 0, PM_REMOVE))
  826. {
  827. switch (msg.message)
  828. {
  829. case WM_QUIT:
  830. case CARLA_LOCAL_CLOSE_MSG:
  831. fIsVisible = false;
  832. CARLA_SAFE_ASSERT_BREAK(fCallback != nullptr);
  833. fCallback->handlePluginUIClosed();
  834. break;
  835. }
  836. DispatchMessageA(&msg);
  837. }
  838. fIsIdling = false;
  839. }
  840. LRESULT checkAndHandleMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
  841. {
  842. if (fWindow == hwnd)
  843. {
  844. switch (message)
  845. {
  846. case WM_SIZE:
  847. if (fChildWindow != nullptr)
  848. {
  849. RECT rect;
  850. GetClientRect(fWindow, &rect);
  851. SetWindowPos(fChildWindow, 0, 0, 0, rect.right, rect.bottom,
  852. SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOOWNERZORDER|SWP_NOZORDER);
  853. }
  854. break;
  855. case WM_QUIT:
  856. case CARLA_LOCAL_CLOSE_MSG:
  857. fIsVisible = false;
  858. CARLA_SAFE_ASSERT_BREAK(fCallback != nullptr);
  859. fCallback->handlePluginUIClosed();
  860. break;
  861. }
  862. }
  863. return DefWindowProcA(hwnd, message, wParam, lParam);
  864. }
  865. void focus() override
  866. {
  867. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  868. SetForegroundWindow(fWindow);
  869. SetActiveWindow(fWindow);
  870. SetFocus(fWindow);
  871. }
  872. void setSize(const uint width, const uint height, const bool forceUpdate) override
  873. {
  874. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  875. const int winFlags = WS_POPUPWINDOW | WS_CAPTION | (fIsResizable ? WS_SIZEBOX : 0x0);
  876. RECT wr = { 0, 0, static_cast<long>(width), static_cast<long>(height) };
  877. AdjustWindowRectEx(&wr, winFlags, FALSE, WS_EX_TOPMOST);
  878. SetWindowPos(fWindow, 0, 0, 0, wr.right-wr.left, wr.bottom-wr.top,
  879. SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOOWNERZORDER|SWP_NOZORDER);
  880. if (forceUpdate)
  881. UpdateWindow(fWindow);
  882. }
  883. void setTitle(const char* const title) override
  884. {
  885. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  886. SetWindowTextA(fWindow, title);
  887. }
  888. void setTransientWinId(const uintptr_t winId) override
  889. {
  890. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  891. fParentWindow = (HWND)winId;
  892. SetWindowLongPtr(fWindow, GWLP_HWNDPARENT, (LONG_PTR)winId);
  893. }
  894. void setChildWindow(void* const winId) override
  895. {
  896. CARLA_SAFE_ASSERT_RETURN(winId != nullptr,);
  897. fChildWindow = (HWND)winId;
  898. }
  899. void* getPtr() const noexcept override
  900. {
  901. return (void*)fWindow;
  902. }
  903. void* getDisplay() const noexcept
  904. {
  905. return nullptr;
  906. }
  907. private:
  908. HWND fWindow;
  909. HWND fChildWindow;
  910. HWND fParentWindow;
  911. WNDCLASS fWindowClass;
  912. bool fIsVisible;
  913. bool fFirstShow;
  914. CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WindowsPluginUI)
  915. };
  916. LRESULT CALLBACK wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
  917. {
  918. switch (message)
  919. {
  920. case WM_CLOSE:
  921. PostMessage(hwnd, CARLA_LOCAL_CLOSE_MSG, wParam, lParam);
  922. return 0;
  923. #if 0
  924. case WM_CREATE:
  925. PostMessage(hwnd, WM_SHOWWINDOW, TRUE, 0);
  926. return 0;
  927. case WM_DESTROY:
  928. return 0;
  929. #endif
  930. default:
  931. if (WindowsPluginUI* const ui = (WindowsPluginUI*)GetWindowLongPtr(hwnd, GWLP_USERDATA))
  932. return ui->checkAndHandleMessage(hwnd, message, wParam, lParam);
  933. return DefWindowProcA(hwnd, message, wParam, lParam);
  934. }
  935. }
  936. #endif // CARLA_OS_WIN
  937. // -----------------------------------------------------
  938. #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
  939. bool CarlaPluginUI::tryTransientWinIdMatch(const uintptr_t pid, const char* const uiTitle, const uintptr_t winId, const bool centerUI)
  940. {
  941. CARLA_SAFE_ASSERT_RETURN(uiTitle != nullptr && uiTitle[0] != '\0', true);
  942. CARLA_SAFE_ASSERT_RETURN(winId != 0, true);
  943. #if defined(HAVE_X11)
  944. struct ScopedDisplay {
  945. Display* display;
  946. ScopedDisplay() : display(XOpenDisplay(nullptr)) {}
  947. ~ScopedDisplay() { if (display!=nullptr) XCloseDisplay(display); }
  948. // c++ compat stuff
  949. CARLA_PREVENT_HEAP_ALLOCATION
  950. CARLA_DECLARE_NON_COPYABLE(ScopedDisplay)
  951. };
  952. struct ScopedFreeData {
  953. union {
  954. char* data;
  955. uchar* udata;
  956. };
  957. ScopedFreeData(char* d) : data(d) {}
  958. ScopedFreeData(uchar* d) : udata(d) {}
  959. ~ScopedFreeData() { XFree(data); }
  960. // c++ compat stuff
  961. CARLA_PREVENT_HEAP_ALLOCATION
  962. CARLA_DECLARE_NON_COPYABLE(ScopedFreeData)
  963. };
  964. const ScopedDisplay sd;
  965. CARLA_SAFE_ASSERT_RETURN(sd.display != nullptr, true);
  966. const Window rootWindow(DefaultRootWindow(sd.display));
  967. const Atom _ncl = XInternAtom(sd.display, "_NET_CLIENT_LIST" , False);
  968. const Atom _nwn = XInternAtom(sd.display, "_NET_WM_NAME", False);
  969. const Atom _nwp = XInternAtom(sd.display, "_NET_WM_PID", False);
  970. const Atom utf8 = XInternAtom(sd.display, "UTF8_STRING", True);
  971. Atom actualType;
  972. int actualFormat;
  973. ulong numWindows, bytesAfter;
  974. uchar* data = nullptr;
  975. int status = XGetWindowProperty(sd.display, rootWindow, _ncl, 0L, (~0L), False, AnyPropertyType, &actualType, &actualFormat, &numWindows, &bytesAfter, &data);
  976. CARLA_SAFE_ASSERT_RETURN(data != nullptr, true);
  977. const ScopedFreeData sfd(data);
  978. CARLA_SAFE_ASSERT_RETURN(status == Success, true);
  979. CARLA_SAFE_ASSERT_RETURN(actualFormat == 32, true);
  980. CARLA_SAFE_ASSERT_RETURN(numWindows != 0, true);
  981. Window* windows = (Window*)data;
  982. Window lastGoodWindowPID = 0, lastGoodWindowNameSimple = 0, lastGoodWindowNameUTF8 = 0;
  983. for (ulong i = 0; i < numWindows; i++)
  984. {
  985. const Window window(windows[i]);
  986. CARLA_SAFE_ASSERT_CONTINUE(window != 0);
  987. // ------------------------------------------------
  988. // try using pid
  989. if (pid != 0)
  990. {
  991. ulong pidSize;
  992. uchar* pidData = nullptr;
  993. status = XGetWindowProperty(sd.display, window, _nwp, 0L, (~0L), False, XA_CARDINAL, &actualType, &actualFormat, &pidSize, &bytesAfter, &pidData);
  994. if (pidData != nullptr)
  995. {
  996. const ScopedFreeData sfd2(pidData);
  997. CARLA_SAFE_ASSERT_CONTINUE(status == Success);
  998. CARLA_SAFE_ASSERT_CONTINUE(pidSize != 0);
  999. if (*(ulong*)pidData == static_cast<ulong>(pid))
  1000. lastGoodWindowPID = window;
  1001. }
  1002. }
  1003. // ------------------------------------------------
  1004. // try using name (UTF-8)
  1005. ulong nameSize;
  1006. uchar* nameData = nullptr;
  1007. status = XGetWindowProperty(sd.display, window, _nwn, 0L, (~0L), False, utf8, &actualType, &actualFormat, &nameSize, &bytesAfter, &nameData);
  1008. if (nameData != nullptr)
  1009. {
  1010. const ScopedFreeData sfd2(nameData);
  1011. CARLA_SAFE_ASSERT_CONTINUE(status == Success);
  1012. if (nameSize != 0 && std::strstr((const char*)nameData, uiTitle) != nullptr)
  1013. lastGoodWindowNameUTF8 = window;
  1014. }
  1015. // ------------------------------------------------
  1016. // try using name (simple)
  1017. char* wmName = nullptr;
  1018. status = XFetchName(sd.display, window, &wmName);
  1019. if (wmName != nullptr)
  1020. {
  1021. const ScopedFreeData sfd2(wmName);
  1022. CARLA_SAFE_ASSERT_CONTINUE(status != 0);
  1023. if (std::strstr(wmName, uiTitle) != nullptr)
  1024. lastGoodWindowNameSimple = window;
  1025. }
  1026. }
  1027. if (lastGoodWindowPID == 0 && lastGoodWindowNameSimple == 0 && lastGoodWindowNameUTF8 == 0)
  1028. return false;
  1029. Window windowToMap;
  1030. if (lastGoodWindowPID != 0)
  1031. {
  1032. if (lastGoodWindowPID == lastGoodWindowNameSimple && lastGoodWindowPID == lastGoodWindowNameUTF8)
  1033. {
  1034. carla_stdout("Match found using pid, simple and UTF-8 name all at once, nice!");
  1035. windowToMap = lastGoodWindowPID;
  1036. }
  1037. else if (lastGoodWindowPID == lastGoodWindowNameUTF8)
  1038. {
  1039. carla_stdout("Match found using pid and UTF-8 name");
  1040. windowToMap = lastGoodWindowPID;
  1041. }
  1042. else if (lastGoodWindowPID == lastGoodWindowNameSimple)
  1043. {
  1044. carla_stdout("Match found using pid and simple name");
  1045. windowToMap = lastGoodWindowPID;
  1046. }
  1047. else if (lastGoodWindowNameUTF8 != 0)
  1048. {
  1049. if (lastGoodWindowNameUTF8 == lastGoodWindowNameSimple)
  1050. {
  1051. carla_stdout("Match found using simple and UTF-8 name (ignoring pid)");
  1052. windowToMap = lastGoodWindowNameUTF8;
  1053. }
  1054. else
  1055. {
  1056. carla_stdout("Match found using UTF-8 name (ignoring pid)");
  1057. windowToMap = lastGoodWindowNameUTF8;
  1058. }
  1059. }
  1060. else
  1061. {
  1062. carla_stdout("Match found using pid");
  1063. windowToMap = lastGoodWindowPID;
  1064. }
  1065. }
  1066. else if (lastGoodWindowNameUTF8 != 0)
  1067. {
  1068. if (lastGoodWindowNameUTF8 == lastGoodWindowNameSimple)
  1069. {
  1070. carla_stdout("Match found using simple and UTF-8 name");
  1071. windowToMap = lastGoodWindowNameUTF8;
  1072. }
  1073. else
  1074. {
  1075. carla_stdout("Match found using UTF-8 name");
  1076. windowToMap = lastGoodWindowNameUTF8;
  1077. }
  1078. }
  1079. else
  1080. {
  1081. carla_stdout("Match found using simple name");
  1082. windowToMap = lastGoodWindowNameSimple;
  1083. }
  1084. const Atom _nwt = XInternAtom(sd.display ,"_NET_WM_STATE", False);
  1085. const Atom _nws[2] = {
  1086. XInternAtom(sd.display, "_NET_WM_STATE_SKIP_TASKBAR", False),
  1087. XInternAtom(sd.display, "_NET_WM_STATE_SKIP_PAGER", False)
  1088. };
  1089. XChangeProperty(sd.display, windowToMap, _nwt, XA_ATOM, 32, PropModeAppend, (const uchar*)_nws, 2);
  1090. const Atom _nwi = XInternAtom(sd.display, "_NET_WM_ICON", False);
  1091. XChangeProperty(sd.display, windowToMap, _nwi, XA_CARDINAL, 32, PropModeReplace, (const uchar*)sCarlaX11Icon, sCarlaX11IconSize);
  1092. const Window hostWinId((Window)winId);
  1093. XSetTransientForHint(sd.display, windowToMap, hostWinId);
  1094. if (centerUI && false /* moving the window after being shown isn't pretty... */)
  1095. {
  1096. int hostX, hostY, pluginX, pluginY;
  1097. uint hostWidth, hostHeight, pluginWidth, pluginHeight, border, depth;
  1098. Window retWindow;
  1099. if (XGetGeometry(sd.display, hostWinId, &retWindow, &hostX, &hostY, &hostWidth, &hostHeight, &border, &depth) != 0 &&
  1100. XGetGeometry(sd.display, windowToMap, &retWindow, &pluginX, &pluginY, &pluginWidth, &pluginHeight, &border, &depth) != 0)
  1101. {
  1102. if (XTranslateCoordinates(sd.display, hostWinId, rootWindow, hostX, hostY, &hostX, &hostY, &retWindow) == True &&
  1103. XTranslateCoordinates(sd.display, windowToMap, rootWindow, pluginX, pluginY, &pluginX, &pluginY, &retWindow) == True)
  1104. {
  1105. const int newX = hostX + int(hostWidth/2 - pluginWidth/2);
  1106. const int newY = hostY + int(hostHeight/2 - pluginHeight/2);
  1107. XMoveWindow(sd.display, windowToMap, newX, newY);
  1108. }
  1109. }
  1110. }
  1111. // focusing the host UI and then the plugin UI forces the WM to repaint the plugin window icon
  1112. XRaiseWindow(sd.display, hostWinId);
  1113. XSetInputFocus(sd.display, hostWinId, RevertToPointerRoot, CurrentTime);
  1114. XRaiseWindow(sd.display, windowToMap);
  1115. XSetInputFocus(sd.display, windowToMap, RevertToPointerRoot, CurrentTime);
  1116. XFlush(sd.display);
  1117. return true;
  1118. #endif
  1119. #ifdef CARLA_OS_MAC
  1120. uint const hints = kCGWindowListOptionOnScreenOnly|kCGWindowListExcludeDesktopElements;
  1121. CFArrayRef const windowListRef = CGWindowListCopyWindowInfo(hints, kCGNullWindowID);
  1122. const NSArray* const windowList = (const NSArray*)windowListRef;
  1123. int windowToMap, windowWithPID = 0, windowWithNameAndPID = 0;
  1124. const NSDictionary* entry;
  1125. for (entry in windowList)
  1126. {
  1127. // FIXME: is this needed? is old version safe?
  1128. #if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
  1129. if ([entry[(id)kCGWindowSharingState] intValue] == kCGWindowSharingNone)
  1130. continue;
  1131. NSString* const windowName = entry[(id)kCGWindowName];
  1132. int const windowNumber = [entry[(id)kCGWindowNumber] intValue];
  1133. uintptr_t const windowPID = [entry[(id)kCGWindowOwnerPID] intValue];
  1134. #else
  1135. if ([[entry objectForKey:(id)kCGWindowSharingState] intValue] == kCGWindowSharingNone)
  1136. continue;
  1137. NSString* const windowName = [entry objectForKey:(id)kCGWindowName];
  1138. int const windowNumber = [[entry objectForKey:(id)kCGWindowNumber] intValue];
  1139. uintptr_t const windowPID = [[entry objectForKey:(id)kCGWindowOwnerPID] intValue];
  1140. #endif
  1141. if (windowPID != pid)
  1142. continue;
  1143. windowWithPID = windowNumber;
  1144. if (windowName != nullptr && std::strcmp([windowName UTF8String], uiTitle) == 0)
  1145. windowWithNameAndPID = windowNumber;
  1146. }
  1147. CFRelease(windowListRef);
  1148. if (windowWithNameAndPID != 0)
  1149. {
  1150. carla_stdout("Match found using pid and name");
  1151. windowToMap = windowWithNameAndPID;
  1152. }
  1153. else if (windowWithPID != 0)
  1154. {
  1155. carla_stdout("Match found using pid");
  1156. windowToMap = windowWithPID;
  1157. }
  1158. else
  1159. {
  1160. return false;
  1161. }
  1162. NSWindow* const parentWindow = [NSApp windowWithWindowNumber:winId];
  1163. CARLA_SAFE_ASSERT_RETURN(parentWindow != nullptr, false);
  1164. [parentWindow orderWindow:NSWindowBelow
  1165. relativeTo:windowToMap];
  1166. return true;
  1167. #endif
  1168. #ifdef CARLA_OS_WIN
  1169. if (HWND const childWindow = FindWindowA(nullptr, uiTitle))
  1170. {
  1171. HWND const parentWindow = (HWND)winId;
  1172. SetWindowLongPtr(childWindow, GWLP_HWNDPARENT, (LONG_PTR)parentWindow);
  1173. if (centerUI)
  1174. {
  1175. RECT rectChild, rectParent;
  1176. if (GetWindowRect(childWindow, &rectChild) && GetWindowRect(parentWindow, &rectParent))
  1177. {
  1178. SetWindowPos(childWindow, parentWindow,
  1179. rectParent.left + (rectChild.right-rectChild.left)/2,
  1180. rectParent.top + (rectChild.bottom-rectChild.top)/2,
  1181. 0, 0, SWP_NOSIZE);
  1182. }
  1183. }
  1184. carla_stdout("Match found using window name");
  1185. return true;
  1186. }
  1187. return false;
  1188. #endif
  1189. // fallback, may be unused
  1190. return true;
  1191. (void)pid; (void)centerUI;
  1192. }
  1193. #endif // BUILD_BRIDGE_ALTERNATIVE_ARCH
  1194. // -----------------------------------------------------
  1195. #ifdef HAVE_X11
  1196. CarlaPluginUI* CarlaPluginUI::newX11(Callback* const cb,
  1197. const uintptr_t parentId,
  1198. const bool isStandalone,
  1199. const bool isResizable,
  1200. const bool isLV2)
  1201. {
  1202. return new X11PluginUI(cb, parentId, isStandalone, isResizable, isLV2);
  1203. }
  1204. #endif
  1205. #ifdef CARLA_OS_MAC
  1206. CarlaPluginUI* CarlaPluginUI::newCocoa(Callback* const cb,
  1207. const uintptr_t parentId,
  1208. const bool isStandalone,
  1209. const bool isResizable)
  1210. {
  1211. return new CocoaPluginUI(cb, parentId, isStandalone, isResizable);
  1212. }
  1213. #endif
  1214. #ifdef CARLA_OS_WIN
  1215. CarlaPluginUI* CarlaPluginUI::newWindows(Callback* const cb,
  1216. const uintptr_t parentId,
  1217. const bool isStandalone,
  1218. const bool isResizable)
  1219. {
  1220. return new WindowsPluginUI(cb, parentId, isStandalone, isResizable);
  1221. }
  1222. #endif
  1223. // -----------------------------------------------------