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

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