The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
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.

659 lines
22KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE 6 technical preview.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. You may use this code under the terms of the GPL v3
  6. (see www.gnu.org/licenses).
  7. For this technical preview, this file is not subject to commercial licensing.
  8. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  9. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  10. DISCLAIMED.
  11. ==============================================================================
  12. */
  13. namespace juce
  14. {
  15. bool juce_handleXEmbedEvent (ComponentPeer*, void*);
  16. Window juce_getCurrentFocusWindow (ComponentPeer*);
  17. //==============================================================================
  18. unsigned long juce_createKeyProxyWindow (ComponentPeer*);
  19. void juce_deleteKeyProxyWindow (ComponentPeer*);
  20. //==============================================================================
  21. class XEmbedComponent::Pimpl : private ComponentListener
  22. {
  23. public:
  24. //==============================================================================
  25. struct SharedKeyWindow : public ReferenceCountedObject
  26. {
  27. SharedKeyWindow (ComponentPeer* peerToUse)
  28. : keyPeer (peerToUse),
  29. keyProxy (juce_createKeyProxyWindow (keyPeer))
  30. {}
  31. ~SharedKeyWindow()
  32. {
  33. juce_deleteKeyProxyWindow (keyPeer);
  34. auto& keyWindows = getKeyWindows();
  35. keyWindows.remove (keyPeer);
  36. }
  37. using Ptr = ReferenceCountedObjectPtr<SharedKeyWindow>;
  38. //==============================================================================
  39. Window getHandle() { return keyProxy; }
  40. static Window getCurrentFocusWindow (ComponentPeer* peerToLookFor)
  41. {
  42. auto& keyWindows = getKeyWindows();
  43. if (peerToLookFor != nullptr)
  44. if (auto* foundKeyWindow = keyWindows[peerToLookFor])
  45. return foundKeyWindow->keyProxy;
  46. return {};
  47. }
  48. static SharedKeyWindow::Ptr getKeyWindowForPeer (ComponentPeer* peerToLookFor)
  49. {
  50. jassert (peerToLookFor != nullptr);
  51. auto& keyWindows = getKeyWindows();
  52. auto foundKeyWindow = keyWindows[peerToLookFor];
  53. if (foundKeyWindow == nullptr)
  54. {
  55. foundKeyWindow = new SharedKeyWindow (peerToLookFor);
  56. keyWindows.set (peerToLookFor, foundKeyWindow);
  57. }
  58. return foundKeyWindow;
  59. }
  60. private:
  61. //==============================================================================
  62. ComponentPeer* keyPeer;
  63. Window keyProxy;
  64. static HashMap<ComponentPeer*, SharedKeyWindow*>& getKeyWindows()
  65. {
  66. // store a weak reference to the shared key windows
  67. static HashMap<ComponentPeer*, SharedKeyWindow*> keyWindows;
  68. return keyWindows;
  69. }
  70. };
  71. public:
  72. //==============================================================================
  73. Pimpl (XEmbedComponent& parent, Window x11Window,
  74. bool wantsKeyboardFocus, bool isClientInitiated, bool shouldAllowResize)
  75. : owner (parent), atoms (x11display.display), clientInitiated (isClientInitiated),
  76. wantsFocus (wantsKeyboardFocus), allowResize (shouldAllowResize)
  77. {
  78. getWidgets().add (this);
  79. createHostWindow();
  80. if (clientInitiated)
  81. setClient (x11Window, true);
  82. owner.setWantsKeyboardFocus (wantsFocus);
  83. owner.addComponentListener (this);
  84. }
  85. ~Pimpl() override
  86. {
  87. owner.removeComponentListener (this);
  88. setClient (0, true);
  89. if (host != 0)
  90. {
  91. auto dpy = getDisplay();
  92. XDestroyWindow (dpy, host);
  93. XSync (dpy, false);
  94. const long mask = NoEventMask | KeyPressMask | KeyReleaseMask
  95. | EnterWindowMask | LeaveWindowMask | PointerMotionMask
  96. | KeymapStateMask | ExposureMask | StructureNotifyMask
  97. | FocusChangeMask;
  98. XEvent event;
  99. while (XCheckWindowEvent (dpy, host, mask, &event) == True)
  100. {}
  101. host = 0;
  102. }
  103. getWidgets().removeAllInstancesOf (this);
  104. }
  105. //==============================================================================
  106. void setClient (Window xembedClient, bool shouldReparent)
  107. {
  108. removeClient();
  109. if (xembedClient != 0)
  110. {
  111. auto dpy = getDisplay();
  112. client = xembedClient;
  113. // if the client has initiated the component then keep the clients size
  114. // otherwise the client should use the host's window' size
  115. if (clientInitiated)
  116. {
  117. configureNotify();
  118. }
  119. else
  120. {
  121. auto newBounds = getX11BoundsFromJuce();
  122. XResizeWindow (dpy, client, static_cast<unsigned int> (newBounds.getWidth()),
  123. static_cast<unsigned int> (newBounds.getHeight()));
  124. }
  125. auto eventMask = StructureNotifyMask | PropertyChangeMask | FocusChangeMask;
  126. XWindowAttributes clientAttr;
  127. XGetWindowAttributes (dpy, client, &clientAttr);
  128. if ((eventMask & clientAttr.your_event_mask) != eventMask)
  129. XSelectInput (dpy, client, clientAttr.your_event_mask | eventMask);
  130. getXEmbedMappedFlag();
  131. if (shouldReparent)
  132. XReparentWindow (dpy, client, host, 0, 0);
  133. if (supportsXembed)
  134. sendXEmbedEvent (CurrentTime, XEMBED_EMBEDDED_NOTIFY, 0, (long) host, xembedVersion);
  135. updateMapping();
  136. }
  137. }
  138. void focusGained (FocusChangeType changeType)
  139. {
  140. if (client != 0 && supportsXembed && wantsFocus)
  141. {
  142. updateKeyFocus();
  143. sendXEmbedEvent (CurrentTime, XEMBED_FOCUS_IN,
  144. (changeType == focusChangedByTabKey ? XEMBED_FOCUS_FIRST : XEMBED_FOCUS_CURRENT));
  145. }
  146. }
  147. void focusLost (FocusChangeType)
  148. {
  149. if (client != 0 && supportsXembed && wantsFocus)
  150. {
  151. sendXEmbedEvent (CurrentTime, XEMBED_FOCUS_OUT);
  152. updateKeyFocus();
  153. }
  154. }
  155. void broughtToFront()
  156. {
  157. if (client != 0 && supportsXembed)
  158. sendXEmbedEvent (CurrentTime, XEMBED_WINDOW_ACTIVATE);
  159. }
  160. unsigned long getHostWindowID()
  161. {
  162. // You are using the client initiated version of the protocol. You cannot
  163. // retrieve the window id of the host. Please read the documentation for
  164. // the XEmebedComponent class.
  165. jassert (! clientInitiated);
  166. return host;
  167. }
  168. private:
  169. //==============================================================================
  170. XEmbedComponent& owner;
  171. Window client = 0, host = 0;
  172. ScopedXDisplay x11display;
  173. Atoms atoms;
  174. bool clientInitiated;
  175. bool wantsFocus = false;
  176. bool allowResize = false;
  177. bool supportsXembed = false;
  178. bool hasBeenMapped = false;
  179. int xembedVersion = maxXEmbedVersionToSupport;
  180. ComponentPeer* lastPeer = nullptr;
  181. SharedKeyWindow::Ptr keyWindow;
  182. //==============================================================================
  183. void componentParentHierarchyChanged (Component&) override { peerChanged (owner.getPeer()); }
  184. void componentMovedOrResized (Component&, bool, bool) override
  185. {
  186. if (host != 0 && lastPeer != nullptr)
  187. {
  188. auto dpy = getDisplay();
  189. auto newBounds = getX11BoundsFromJuce();
  190. XWindowAttributes attr;
  191. if (XGetWindowAttributes (dpy, host, &attr))
  192. {
  193. Rectangle<int> currentBounds (attr.x, attr.y, attr.width, attr.height);
  194. if (currentBounds != newBounds)
  195. {
  196. XMoveResizeWindow (dpy, host, newBounds.getX(), newBounds.getY(),
  197. static_cast<unsigned int> (newBounds.getWidth()),
  198. static_cast<unsigned int> (newBounds.getHeight()));
  199. }
  200. }
  201. if (client != 0 && XGetWindowAttributes (dpy, client, &attr))
  202. {
  203. Rectangle<int> currentBounds (attr.x, attr.y, attr.width, attr.height);
  204. if ((currentBounds.getWidth() != newBounds.getWidth()
  205. || currentBounds.getHeight() != newBounds.getHeight()))
  206. {
  207. XMoveResizeWindow (dpy, client, 0, 0,
  208. static_cast<unsigned int> (newBounds.getWidth()),
  209. static_cast<unsigned int> (newBounds.getHeight()));
  210. }
  211. }
  212. }
  213. }
  214. //==============================================================================
  215. void createHostWindow()
  216. {
  217. auto dpy = getDisplay();
  218. int defaultScreen = XDefaultScreen (dpy);
  219. Window root = RootWindow (dpy, defaultScreen);
  220. XSetWindowAttributes swa;
  221. swa.border_pixel = 0;
  222. swa.background_pixmap = None;
  223. swa.override_redirect = True;
  224. swa.event_mask = SubstructureNotifyMask | StructureNotifyMask | FocusChangeMask;
  225. host = XCreateWindow (dpy, root, 0, 0, 1, 1, 0, CopyFromParent,
  226. InputOutput, CopyFromParent,
  227. CWEventMask | CWBorderPixel | CWBackPixmap | CWOverrideRedirect,
  228. &swa);
  229. }
  230. void removeClient()
  231. {
  232. if (client != 0)
  233. {
  234. auto dpy = getDisplay();
  235. XSelectInput (dpy, client, 0);
  236. keyWindow = nullptr;
  237. int defaultScreen = XDefaultScreen (dpy);
  238. Window root = RootWindow (dpy, defaultScreen);
  239. if (hasBeenMapped)
  240. {
  241. XUnmapWindow (dpy, client);
  242. hasBeenMapped = false;
  243. }
  244. XReparentWindow (dpy, client, root, 0, 0);
  245. client = 0;
  246. }
  247. }
  248. void updateMapping()
  249. {
  250. if (client != 0)
  251. {
  252. const bool shouldBeMapped = getXEmbedMappedFlag();
  253. if (shouldBeMapped != hasBeenMapped)
  254. {
  255. hasBeenMapped = shouldBeMapped;
  256. if (shouldBeMapped)
  257. XMapWindow (getDisplay(), client);
  258. else
  259. XUnmapWindow (getDisplay(), client);
  260. }
  261. }
  262. }
  263. Window getParentX11Window()
  264. {
  265. if (auto* peer = owner.getPeer())
  266. return reinterpret_cast<Window> (peer->getNativeHandle());
  267. return {};
  268. }
  269. Display* getDisplay() { return reinterpret_cast<Display*> (x11display.display); }
  270. //==============================================================================
  271. bool getXEmbedMappedFlag()
  272. {
  273. GetXProperty embedInfo (x11display.display, client, atoms.XembedInfo, 0, 2, false, atoms.XembedInfo);
  274. if (embedInfo.success && embedInfo.actualFormat == 32
  275. && embedInfo.numItems >= 2 && embedInfo.data != nullptr)
  276. {
  277. long version;
  278. memcpy (&version, embedInfo.data, sizeof (long));
  279. supportsXembed = true;
  280. xembedVersion = jmin ((int) maxXEmbedVersionToSupport, (int) version);
  281. long flags;
  282. memcpy (&flags, embedInfo.data + sizeof (long), sizeof (long));
  283. return ((flags & XEMBED_MAPPED) != 0);
  284. }
  285. else
  286. {
  287. supportsXembed = false;
  288. xembedVersion = maxXEmbedVersionToSupport;
  289. }
  290. return true;
  291. }
  292. //==============================================================================
  293. void propertyChanged (const Atom& a)
  294. {
  295. if (a == atoms.XembedInfo)
  296. updateMapping();
  297. }
  298. void configureNotify()
  299. {
  300. XWindowAttributes attr;
  301. auto dpy = getDisplay();
  302. if (XGetWindowAttributes (dpy, client, &attr))
  303. {
  304. XWindowAttributes hostAttr;
  305. if (XGetWindowAttributes (dpy, host, &hostAttr))
  306. if (attr.width != hostAttr.width || attr.height != hostAttr.height)
  307. XResizeWindow (dpy, host, (unsigned int) attr.width, (unsigned int) attr.height);
  308. // as the client window is not on any screen yet, we need to guess
  309. // on which screen it might appear to get a scaling factor :-(
  310. auto& displays = Desktop::getInstance().getDisplays();
  311. auto* peer = owner.getPeer();
  312. const double scale = (peer != nullptr ? peer->getPlatformScaleFactor()
  313. : displays.getMainDisplay().scale);
  314. Point<int> topLeftInPeer
  315. = (peer != nullptr ? peer->getComponent().getLocalPoint (&owner, Point<int> (0, 0))
  316. : owner.getBounds().getTopLeft());
  317. Rectangle<int> newBounds (topLeftInPeer.getX(), topLeftInPeer.getY(),
  318. static_cast<int> (static_cast<double> (attr.width) / scale),
  319. static_cast<int> (static_cast<double> (attr.height) / scale));
  320. if (peer != nullptr)
  321. newBounds = owner.getLocalArea (&peer->getComponent(), newBounds);
  322. jassert (newBounds.getX() == 0 && newBounds.getY() == 0);
  323. if (newBounds != owner.getLocalBounds())
  324. owner.setSize (newBounds.getWidth(), newBounds.getHeight());
  325. }
  326. }
  327. void peerChanged (ComponentPeer* newPeer)
  328. {
  329. if (newPeer != lastPeer)
  330. {
  331. if (lastPeer != nullptr)
  332. keyWindow = nullptr;
  333. auto dpy = getDisplay();
  334. Window rootWindow = RootWindow (dpy, DefaultScreen (dpy));
  335. Rectangle<int> newBounds = getX11BoundsFromJuce();
  336. if (newPeer == nullptr)
  337. XUnmapWindow (dpy, host);
  338. Window newParent = (newPeer != nullptr ? getParentX11Window() : rootWindow);
  339. XReparentWindow (dpy, host, newParent, newBounds.getX(), newBounds.getY());
  340. lastPeer = newPeer;
  341. if (newPeer != nullptr)
  342. {
  343. if (wantsFocus)
  344. {
  345. keyWindow = SharedKeyWindow::getKeyWindowForPeer (newPeer);
  346. updateKeyFocus();
  347. }
  348. componentMovedOrResized (owner, true, true);
  349. XMapWindow (dpy, host);
  350. broughtToFront();
  351. }
  352. }
  353. }
  354. void updateKeyFocus()
  355. {
  356. if (lastPeer != nullptr && lastPeer->isFocused())
  357. XSetInputFocus (getDisplay(), getCurrentFocusWindow (lastPeer), RevertToParent, CurrentTime);
  358. }
  359. //==============================================================================
  360. void handleXembedCmd (const ::Time& /*xTime*/, long opcode, long /*detail*/, long /*data1*/, long /*data2*/)
  361. {
  362. switch (opcode)
  363. {
  364. case XEMBED_REQUEST_FOCUS:
  365. if (wantsFocus)
  366. owner.grabKeyboardFocus();
  367. break;
  368. case XEMBED_FOCUS_NEXT:
  369. if (wantsFocus)
  370. owner.moveKeyboardFocusToSibling (true);
  371. break;
  372. case XEMBED_FOCUS_PREV:
  373. if (wantsFocus)
  374. owner.moveKeyboardFocusToSibling (false);
  375. break;
  376. default:
  377. break;
  378. }
  379. }
  380. bool handleX11Event (const XEvent& e)
  381. {
  382. if (e.xany.window == client && client != 0)
  383. {
  384. switch (e.type)
  385. {
  386. case PropertyNotify:
  387. propertyChanged (e.xproperty.atom);
  388. return true;
  389. case ConfigureNotify:
  390. if (allowResize)
  391. configureNotify();
  392. else
  393. MessageManager::callAsync ([this] {componentMovedOrResized (owner, true, true);});
  394. return true;
  395. default:
  396. break;
  397. }
  398. }
  399. else if (e.xany.window == host && host != 0)
  400. {
  401. switch (e.type)
  402. {
  403. case ReparentNotify:
  404. if (e.xreparent.parent == host && e.xreparent.window != client)
  405. {
  406. setClient (e.xreparent.window, false);
  407. return true;
  408. }
  409. break;
  410. case CreateNotify:
  411. if (e.xcreatewindow.parent != e.xcreatewindow.window && e.xcreatewindow.parent == host && e.xcreatewindow.window != client)
  412. {
  413. setClient (e.xcreatewindow.window, false);
  414. return true;
  415. }
  416. break;
  417. case GravityNotify:
  418. componentMovedOrResized (owner, true, true);
  419. return true;
  420. case ClientMessage:
  421. if (e.xclient.message_type == atoms.XembedMsgType && e.xclient.format == 32)
  422. {
  423. handleXembedCmd ((::Time) e.xclient.data.l[0], e.xclient.data.l[1],
  424. e.xclient.data.l[2], e.xclient.data.l[3],
  425. e.xclient.data.l[4]);
  426. return true;
  427. }
  428. break;
  429. default:
  430. break;
  431. }
  432. }
  433. return false;
  434. }
  435. void sendXEmbedEvent (const ::Time& xTime, long opcode,
  436. long opcodeMinor = 0, long data1 = 0, long data2 = 0)
  437. {
  438. XClientMessageEvent msg;
  439. auto dpy = getDisplay();
  440. ::memset (&msg, 0, sizeof (XClientMessageEvent));
  441. msg.window = client;
  442. msg.type = ClientMessage;
  443. msg.message_type = atoms.XembedMsgType;
  444. msg.format = 32;
  445. msg.data.l[0] = (long) xTime;
  446. msg.data.l[1] = opcode;
  447. msg.data.l[2] = opcodeMinor;
  448. msg.data.l[3] = data1;
  449. msg.data.l[4] = data2;
  450. XSendEvent (dpy, client, False, NoEventMask, (XEvent*) &msg);
  451. XSync (dpy, False);
  452. }
  453. Rectangle<int> getX11BoundsFromJuce()
  454. {
  455. if (auto* peer = owner.getPeer())
  456. {
  457. auto r = peer->getComponent().getLocalArea (&owner, owner.getLocalBounds());
  458. return r * peer->getPlatformScaleFactor();
  459. }
  460. return owner.getLocalBounds();
  461. }
  462. //==============================================================================
  463. friend bool juce::juce_handleXEmbedEvent (ComponentPeer*, void*);
  464. friend unsigned long juce::juce_getCurrentFocusWindow (ComponentPeer*);
  465. static Array<Pimpl*>& getWidgets()
  466. {
  467. static Array<Pimpl*> i;
  468. return i;
  469. }
  470. static bool dispatchX11Event (ComponentPeer* p, const XEvent* eventArg)
  471. {
  472. if (eventArg != nullptr)
  473. {
  474. auto& e = *eventArg;
  475. if (auto w = e.xany.window)
  476. for (auto* widget : getWidgets())
  477. if (w == widget->host || w == widget->client)
  478. return widget->handleX11Event (e);
  479. }
  480. else
  481. {
  482. for (auto* widget : getWidgets())
  483. if (widget->owner.getPeer() == p)
  484. widget->peerChanged (nullptr);
  485. }
  486. return false;
  487. }
  488. static Window getCurrentFocusWindow (ComponentPeer* p)
  489. {
  490. if (p != nullptr)
  491. {
  492. for (auto* widget : getWidgets())
  493. if (widget->owner.getPeer() == p && widget->owner.hasKeyboardFocus (false))
  494. return widget->client;
  495. }
  496. return SharedKeyWindow::getCurrentFocusWindow (p);
  497. }
  498. };
  499. //==============================================================================
  500. XEmbedComponent::XEmbedComponent (bool wantsKeyboardFocus, bool allowForeignWidgetToResizeComponent)
  501. : pimpl (new Pimpl (*this, 0, wantsKeyboardFocus, false, allowForeignWidgetToResizeComponent))
  502. {
  503. setOpaque (true);
  504. }
  505. XEmbedComponent::XEmbedComponent (unsigned long wID, bool wantsKeyboardFocus, bool allowForeignWidgetToResizeComponent)
  506. : pimpl (new Pimpl (*this, wID, wantsKeyboardFocus, true, allowForeignWidgetToResizeComponent))
  507. {
  508. setOpaque (true);
  509. }
  510. XEmbedComponent::~XEmbedComponent() {}
  511. void XEmbedComponent::paint (Graphics& g)
  512. {
  513. g.fillAll (Colours::lightgrey);
  514. }
  515. void XEmbedComponent::focusGained (FocusChangeType changeType) { pimpl->focusGained (changeType); }
  516. void XEmbedComponent::focusLost (FocusChangeType changeType) { pimpl->focusLost (changeType); }
  517. void XEmbedComponent::broughtToFront() { pimpl->broughtToFront(); }
  518. unsigned long XEmbedComponent::getHostWindowID() { return pimpl->getHostWindowID(); }
  519. //==============================================================================
  520. bool juce_handleXEmbedEvent (ComponentPeer* p, void* e)
  521. {
  522. return XEmbedComponent::Pimpl::dispatchX11Event (p, reinterpret_cast<const XEvent*> (e));
  523. }
  524. unsigned long juce_getCurrentFocusWindow (ComponentPeer* peer)
  525. {
  526. return (unsigned long) XEmbedComponent::Pimpl::getCurrentFocusWindow (peer);
  527. }
  528. } // namespace juce