1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Copyright (C) 2017 Lukas W <lukaswhl/at/gmail.com>
5 ** Contact: http://www.qt.io/licensing/
6 **
7 ** GNU Lesser General Public License Usage
8 ** This file may be used under the terms of the GNU Lesser General Public
9 ** License version 2.1 or version 3 as published by the Free Software
10 ** Foundation and appearing in the file LICENSE.LGPLv21 and LICENSE.LGPLv3
11 ** included in the packaging of this file. Please review the following
12 ** information to ensure the GNU Lesser General Public License requirements
13 ** will be met: https://www.gnu.org/licenses/lgpl.html and
14 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3.0 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.GPL included in the
20 ** packaging of this file. Please review the following information to
21 ** ensure the GNU General Public License version 3.0 requirements will be
22 ** met: http://www.gnu.org/copyleft/gpl.html.
23 **
24 ****************************************************************************/
25
26 #include "X11EmbedContainer.h"
27
28 #include <QAbstractNativeEventFilter>
29 #include <qcoreapplication.h>
30 #include <qevent.h>
31 #include <qpainter.h>
32 #include <qelapsedtimer.h>
33 #include <qpointer.h>
34 #include <qdebug.h>
35 #include <QtX11Extras/QX11Info>
36 #include <QThread>
37
38 #include <QtCore/QMutex>
39
40 #include <QtCore/private/qobject_p.h>
41 #include <QtWidgets/private/qwidget_p.h>
42 #include <QtGui/private/qguiapplication_p.h>
43
44 #include <QWindow>
45 #include <QGuiApplication>
46 #include <qpa/qplatformintegration.h>
47 //#include <private/qt_x11_p.h>
48
49 #include <queue>
50 #include <cstring>
51
52 #define XK_MISCELLANY
53 #define XK_LATIN1
54 #define None 0
55 #include <X11/Xlib-xcb.h>
56 #include <X11/Xutil.h>
57 #include <xcb/xcb.h>
58 #include <xcb/xcb_atom.h>
59 #include <xcb/xcb_util.h>
60 #include <xcb/xcb_keysyms.h>
61 #include <xcb/xproto.h>
62
63 #ifndef XK_ISO_Left_Tab
64 #define XK_ISO_Left_Tab 0xFE20
65 #endif
66
67 //#define QX11EMBED_DEBUG
68 #ifdef QX11EMBED_DEBUG
69 #include <qdebug.h>
70 #endif
71
72
73 /*!
74 \class QX11EmbedContainer
75 \ingroup advanced
76
77 \brief The QX11EmbedContainer class provides an XEmbed container
78 widget.
79
80 XEmbed is an X11 protocol that supports the embedding of a widget
81 from one application into another application.
82
83 An XEmbed \e container is the graphical location that embeds an
84 external \e {client widget}. A client widget is a window that is
85 embedded into a container.
86
87 When a widget has been embedded and the container receives tab
88 focus, focus is passed on to the widget. When the widget reaches
89 the end of its focus chain, focus is passed back to the
90 container. Window activation, accelerator support, modality and
91 drag and drop (XDND) are also handled.
92
93 QX11EmbedContainer is commonly used for writing panels or
94 toolbars that hold applets, or for \e swallowing X11
95 applications. When writing a panel application, one container
96 widget is created on the toolbar, and it can then either swallow
97 another widget using embed(), or allow an XEmbed widget to be
98 embedded into itself. The container's X11 window ID, which is
99 retrieved with winId(), must then be known to the client widget.
100 After embedding, the client's window ID can be retrieved with
101 clientWinId().
102
103 In the following example, a container widget is created as the
104 main widget. It then invokes an application called "playmovie",
105 passing its window ID as a command line argument. The "playmovie"
106 program is an XEmbed client widget. The widget embeds itself into
107 the container using the container's window ID.
108
109 \snippet doc/src/snippets/qx11embedcontainer/main.cpp 0
110
111 When the client widget is embedded, the container emits the
112 signal clientIsEmbedded(). The signal clientClosed() is emitted
113 when a widget is closed.
114
115 It is possible for QX11EmbedContainer to embed XEmbed widgets
116 from toolkits other than Qt, such as GTK+. Arbitrary (non-XEmbed)
117 X11 widgets can also be embedded, but the XEmbed-specific
118 features such as window activation and focus handling are then
119 lost.
120
121 The GTK+ equivalent of QX11EmbedContainer is GtkSocket. The
122 corresponding KDE 3 widget is called QXEmbed.
123 */
124
125 /*! \fn void QX11EmbedContainer::clientIsEmbedded()
126
127 This signal is emitted by the container when a client widget has
128 been embedded.
129 */
130
131 /*! \fn void QX11EmbedContainer::clientClosed()
132
133 This signal is emitted by the container when the client widget
134 closes.
135 */
136
137 /*!
138 \fn QX11EmbedContainer::Error QX11EmbedContainer::error() const
139
140 Returns the last error that occurred.
141 */
142
143 /*! \fn void QX11EmbedContainer::error(QX11EmbedContainer::Error error)
144
145 This signal is emitted if an error occurred when embedding or
146 communicating with a client. The specified \a error describes the
147 problem that occurred.
148
149 \sa QX11EmbedContainer::Error
150 */
151
152 /*!
153 \enum QX11EmbedContainer::Error
154
155 \value Unknown An unrecognized error occurred.
156
157 \value InvalidWindowID The X11 window ID of the container was
158 invalid. This error is usually triggered by passing an invalid
159 window ID to embed().
160
161 \omitvalue Internal
162 */
163
164 #undef KeyPress
165 #undef KeyRelease
166 #undef FocusIn
167 #undef FocusOut
168
169 static const int XRevertToParent = RevertToParent;
170 #undef RevertToParent
171
172 QT_BEGIN_NAMESPACE
173
174 enum ATOM_ID : int {
175 _XEMBED
176 ,_XEMBED_INFO
177 ,WM_PROTOCOLS
178 ,WM_DELETE_WINDOW
179 ,WM_STATE
180 };
181
182 static const std::vector<std::pair<int, std::string>> atom_list({
183 {_XEMBED, "_XEMBED"},
184 {_XEMBED_INFO, "_XEMBED_INFO"},
185 {WM_PROTOCOLS, "WM_PROTOCOLS"},
186 {WM_DELETE_WINDOW, "WM_DELETE_WINDOW"},
187 {WM_STATE, "WM_STATE"},
188 });
189
190 static QMap<int, xcb_atom_t> atoms;
191 static QMutex atoms_lock;
192
initAtoms()193 void initAtoms()
194 {
195 QMutexLocker locker(&atoms_lock); Q_UNUSED(locker);
196
197 std::queue<xcb_intern_atom_cookie_t> cookies;
198
199 for (const auto& pair : atom_list)
200 {
201 cookies.push(xcb_intern_atom(QX11Info::connection(), false, pair.second.length(), pair.second.data()));
202 }
203
204 for (const auto& pair : atom_list)
205 {
206 auto cookie = cookies.front();
207 cookies.pop();
208
209 auto reply = xcb_intern_atom_reply(QX11Info::connection(), cookie, nullptr);
210 atoms[pair.first] = reply->atom;
211
212 Q_ASSERT(pair.second == XGetAtomName(QX11Info::display(), reply->atom));
213
214 #ifdef QX11EMBED_DEBUG
215 qDebug() << "atom" << QString::fromStdString(pair.second)
216 << XGetAtomName(QX11Info::display(), reply->atom) << reply->atom;
217 #endif
218 free(reply);
219 }
220 }
221
ATOM(int atomID)222 xcb_atom_t ATOM(int atomID)
223 {
224 return atoms.value(atomID);
225 }
226
227
228 struct xembed_info
229 {
230 uint32_t version;
231 uint32_t flags;
232 };
233
get_xembed_info(xcb_window_t window)234 xembed_info* get_xembed_info(xcb_window_t window)
235 {
236 auto cookie = xcb_get_property(QX11Info::connection(), 0, window, ATOM(_XEMBED_INFO), ATOM(_XEMBED_INFO), 0, 2);
237 if (auto reply = xcb_get_property_reply(QX11Info::connection(), cookie, nullptr)) {
238 auto val_len = xcb_get_property_value_length(reply);
239 if (val_len < 2) {
240 #ifdef QX11EMBED_DEBUG
241 qDebug() << "Client has malformed _XEMBED_INFO property, len is" << val_len;
242 #endif
243 free(reply);
244 return nullptr;
245 }
246
247 void* result = malloc(sizeof(xembed_info));
248 memcpy(result, xcb_get_property_value(reply), sizeof(xembed_info));
249 return reinterpret_cast<xembed_info*>(result);
250 }
251
252 return nullptr;
253 }
254
255 // This is a hack to move topData() out from QWidgetPrivate to public. We
256 // need to to inspect window()'s embedded state.
257 class QHackWidget : public QWidget
258 {
259 Q_DECLARE_PRIVATE(QWidget)
260 public:
topData()261 QTLWExtra* topData() { return d_func()->topData(); }
262 };
263
264 static unsigned int XEMBED_VERSION = 0;
265
266 enum QX11EmbedMessageType {
267 XEMBED_EMBEDDED_NOTIFY = 0,
268 XEMBED_WINDOW_ACTIVATE = 1,
269 XEMBED_WINDOW_DEACTIVATE = 2,
270 XEMBED_REQUEST_FOCUS = 3,
271 XEMBED_FOCUS_IN = 4,
272 XEMBED_FOCUS_OUT = 5,
273 XEMBED_FOCUS_NEXT = 6,
274 XEMBED_FOCUS_PREV = 7,
275 XEMBED_MODALITY_ON = 10,
276 XEMBED_MODALITY_OFF = 11,
277 XEMBED_REGISTER_ACCELERATOR = 12,
278 XEMBED_UNREGISTER_ACCELERATOR = 13,
279 XEMBED_ACTIVATE_ACCELERATOR = 14
280 };
281
282 enum QX11EmbedFocusInDetail {
283 XEMBED_FOCUS_CURRENT = 0,
284 XEMBED_FOCUS_FIRST = 1,
285 XEMBED_FOCUS_LAST = 2
286 };
287
288 enum QX11EmbedFocusInFlags {
289 XEMBED_FOCUS_OTHER = (0 << 0),
290 XEMBED_FOCUS_WRAPAROUND = (1 << 0)
291 };
292
293 enum QX11EmbedInfoFlags {
294 XEMBED_MAPPED = (1 << 0)
295 };
296
297 enum QX11EmbedAccelModifiers {
298 XEMBED_MODIFIER_SHIFT = (1 << 0),
299 XEMBED_MODIFIER_CONTROL = (1 << 1),
300 XEMBED_MODIFIER_ALT = (1 << 2),
301 XEMBED_MODIFIER_SUPER = (1 << 3),
302 XEMBED_MODIFIER_HYPER = (1 << 4)
303 };
304
305 enum QX11EmbedAccelFlags {
306 XEMBED_ACCELERATOR_OVERLOADED = (1 << 0)
307 };
308
309 // Silence the default X11 error handler.
310 /*static int x11ErrorHandler(Display *, xcb_generic_error_t *)
311 {
312 return 0;
313 }*/
314
315 // Returns the X11 timestamp. Maintained mainly by qapplication
316 // internals, but also updated by the XEmbed widgets.
x11Time()317 static xcb_timestamp_t x11Time()
318 {
319 return QX11Info::getTimestamp();
320 }
321
322
323 // Sends an XEmbed message.
sendXEmbedMessage(WId window,long message,long detail=0,long data1=0,long data2=0)324 static void sendXEmbedMessage(WId window, long message,
325 long detail = 0, long data1 = 0, long data2 = 0)
326 {
327 auto display = QX11Info::display();
328
329 XClientMessageEvent c;
330 memset(&c, 0, sizeof(c));
331 c.type = ClientMessage;
332 c.message_type = ATOM(_XEMBED);
333 c.format = 32;
334 c.display = display;
335 c.window = window;
336
337 c.data.l[0] = x11Time();
338 c.data.l[1] = message;
339 c.data.l[2] = detail;
340 c.data.l[3] = data1;
341 c.data.l[4] = data2;
342
343 #ifdef QX11EMBED_DEBUG
344 qDebug() << "Sending XEMBED message" << message << detail << data1 << data2;
345 #endif
346 XSendEvent(display, window, false, NoEventMask, (XEvent *) &c);
347 }
348
349 // From qapplication_x11.cpp
350 static xcb_key_press_event_t lastKeyEvent;
351
352 // The purpose of this global x11 filter is for one to capture the key
353 // events in their original state, but most importantly this is the
354 // only way to get the WM_TAKE_FOCUS message from WM_PROTOCOLS.
355 class X11EventFilter : public QAbstractNativeEventFilter
356 {
357 public:
nativeEventFilter(const QByteArray & eventType,void * message,long * result)358 bool nativeEventFilter(const QByteArray &eventType, void *message, long *result)
359 {
360 if (eventType != "xcb_generic_event_t") {
361 return false;
362 }
363
364 xcb_generic_event_t *event = reinterpret_cast<xcb_generic_event_t *>(message);
365 if (event->response_type == XCB_KEY_PRESS || event->response_type == XCB_KEY_RELEASE) {
366 lastKeyEvent = *reinterpret_cast<xcb_key_press_event_t*>(message);
367 }
368
369 return false;
370 }
371 } static x11EventFilter;
372
373
374 class QX11EmbedContainerPrivate : public QWidgetPrivate
375 {
376 Q_DECLARE_PUBLIC(QX11EmbedContainer)
377 public:
QX11EmbedContainerPrivate()378 inline QX11EmbedContainerPrivate()
379 {
380 lastError = QX11EmbedContainer::Unknown;
381 client = 0;
382 focusProxy = 0;
383 clientIsXEmbed = false;
384 xgrab = false;
385 }
386
387 bool isEmbedded() const;
388 void moveInputToProxy();
389
390 void acceptClient(WId window);
391 void rejectClient(WId window);
392
393 void checkGrab();
394 void checkXembedInfo();
395
396 WId topLevelParentWinId() const;
397
emitError(QX11EmbedContainer::Error error)398 void emitError(QX11EmbedContainer::Error error) {
399 Q_Q(QX11EmbedContainer);
400 lastError = error;
401 emit q->error(error);
402 }
403
404 WId client;
405 QWidget *focusProxy;
406 bool clientIsXEmbed;
407 bool xgrab;
408 QRect clientOriginalRect;
409 QSize wmMinimumSizeHint;
410
411 QX11EmbedContainer::Error lastError;
412
413 static QX11EmbedContainer *activeContainer;
414 };
415
416 QX11EmbedContainer *QX11EmbedContainerPrivate::activeContainer = 0;
417
418 /*!
419 Creates a QX11EmbedContainer object with the given \a parent.
420 */
QX11EmbedContainer(QWidget * parent)421 QX11EmbedContainer::QX11EmbedContainer(QWidget *parent)
422 : QWidget(*new QX11EmbedContainerPrivate, parent, 0)
423 {
424 initAtoms();
425 Q_D(QX11EmbedContainer);
426 //XSetErrorHandler(x11ErrorHandler);
427
428 setAttribute(Qt::WA_NativeWindow);
429 //setAttribute(Qt::WA_DontCreateNativeAncestors);
430 createWinId();
431
432 setFocusPolicy(Qt::StrongFocus);
433 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
434 // ### PORT setKeyCompression(false);
435 setAcceptDrops(true);
436 setEnabled(false);
437
438 // Everybody gets a focus proxy, but only one toplevel container's
439 // focus proxy is actually in use.
440 d->focusProxy = new QWidget(this);
441 d->focusProxy->setAttribute(Qt::WA_NativeWindow);
442 //d->focusProxy->setAttribute(Qt::WA_DontCreateNativeAncestors);
443 d->focusProxy->createWinId();
444 d->focusProxy->winId();
445 d->focusProxy->setGeometry(-1, -1, 1, 1);
446
447 // We need events from the window (activation status) and
448 // from qApp (keypress/release).
449 qApp->installEventFilter(this);
450
451 // Install X11 event filter.
452 QCoreApplication::instance()->installNativeEventFilter(&x11EventFilter);
453 QCoreApplication::instance()->installNativeEventFilter(this);
454
455 XSelectInput(QX11Info::display(), internalWinId(),
456 KeyPressMask | KeyReleaseMask
457 | ButtonPressMask | ButtonReleaseMask | ButtonMotionMask
458 | KeymapStateMask
459 | PointerMotionMask
460 | EnterWindowMask | LeaveWindowMask
461 | FocusChangeMask
462 | ExposureMask
463 | StructureNotifyMask
464 | SubstructureNotifyMask);
465
466 // Make sure our new event mask takes effect as soon as possible.
467 XFlush(QX11Info::display());
468
469 // Move input to our focusProxy if this widget is active, and not
470 // shaded by a modal dialog (in which case isActiveWindow() would
471 // still return true, but where we must not move input focus).
472 if (qApp->activeWindow() == window() && !d->isEmbedded())
473 d->moveInputToProxy();
474
475 #ifdef QX11EMBED_DEBUG
476 qDebug() << "QX11EmbedContainer::QX11EmbedContainer: constructed container"
477 << (void *)this << "with winId" << winId();
478 #endif
479 }
480
481 /*!
482 Destructs a QX11EmbedContainer.
483 */
~QX11EmbedContainer()484 QX11EmbedContainer::~QX11EmbedContainer()
485 {
486 Q_D(QX11EmbedContainer);
487 if (d->client) {
488 XUnmapWindow(QX11Info::display(), d->client);
489 XReparentWindow(QX11Info::display(), d->client, QX11Info::appRootWindow(QX11Info::appScreen()), 0, 0);
490 }
491
492 if (d->xgrab)
493 XUngrabButton(QX11Info::display(), AnyButton, AnyModifier, internalWinId());
494 }
495
496
error() const497 QX11EmbedContainer::Error QX11EmbedContainer::error() const {
498 return d_func()->lastError;
499 }
500
nativeEventFilter(const QByteArray & eventType,void * message,long * result)501 bool QX11EmbedContainer::nativeEventFilter(const QByteArray &eventType, void *message, long *result)
502 {
503 if (eventType == "xcb_generic_event_t") {
504 return x11Event(message, result);
505 } else {
506 return false;
507 }
508 }
509
510
511
512 /*! \reimp
513 */
paintEvent(QPaintEvent *)514 void QX11EmbedContainer::paintEvent(QPaintEvent *)
515 {
516 }
517
518 /*! \internal
519
520 Returns whether or not the windows' embedded flag is set.
521 */
isEmbedded() const522 bool QX11EmbedContainerPrivate::isEmbedded() const
523 {
524 Q_Q(const QX11EmbedContainer);
525 return ((QHackWidget *)q->window())->topData()->embedded == 1;
526 }
527
528 /*! \internal
529
530 Returns the parentWinId of the window.
531 */
topLevelParentWinId() const532 WId QX11EmbedContainerPrivate::topLevelParentWinId() const
533 {
534 Q_Q(const QX11EmbedContainer);
535 return q->window()->effectiveWinId();
536 //TODO
537 //return ((QHackWidget *)q->window())->topData()->parentWinId;
538 }
539
540 /*!
541 If the container has an embedded widget, this function returns
542 the X11 window ID of the client; otherwise it returns 0.
543 */
clientWinId() const544 WId QX11EmbedContainer::clientWinId() const
545 {
546 Q_D(const QX11EmbedContainer);
547 return d->client;
548 }
549
550 /*!
551 Instructs the container to embed the X11 window with window ID \a
552 id. The client widget will then move on top of the container
553 window and be resized to fit into the container.
554
555 The \a id should be the ID of a window controlled by an XEmbed
556 enabled application, but this is not mandatory. If \a id does not
557 belong to an XEmbed client widget, then focus handling,
558 activation, accelerators and other features will not work
559 properly.
560 */
embedClient(WId id)561 void QX11EmbedContainer::embedClient(WId id)
562 {
563 Q_D(QX11EmbedContainer);
564
565 if (id == 0) {
566 d->emitError(InvalidWindowID);
567 return;
568 }
569
570 // Walk up the tree of parent windows to prevent embedding of ancestors.
571 WId thisId = internalWinId();
572 xcb_window_t rootReturn;
573 xcb_window_t parentReturn;
574 do {
575 auto cookie = xcb_query_tree(QX11Info::connection(), thisId);
576 xcb_generic_error_t* error = nullptr;
577 auto reply = xcb_query_tree_reply(QX11Info::connection(), cookie, &error);
578
579 if (error) {
580 d->emitError(InvalidWindowID);
581 return;
582 }
583
584 rootReturn = reply->root;
585 parentReturn = reply->parent;
586
587 thisId = parentReturn;
588 if (id == thisId) {
589 d->emitError(InvalidWindowID);
590 return;
591 }
592 } while (thisId != rootReturn);
593
594 switch (XReparentWindow(QX11Info::display(), id, internalWinId(), 0, 0)) {
595 case BadWindow:
596 case BadMatch:
597 d->emitError(InvalidWindowID);
598 break;
599 default:
600 break;
601 }
602
603 #ifdef QX11EMBED_DEBUG
604 qDebug() << "reparented client" << id << "into" << winId();
605 #endif
606 }
607
608 /*! \internal
609
610 Handles key, activation and focus events for the container.
611 */
eventFilter(QObject * o,QEvent * event)612 bool QX11EmbedContainer::eventFilter(QObject *o, QEvent *event)
613 {
614 Q_D(QX11EmbedContainer);
615 switch (event->type()) {
616 case QEvent::KeyPress:
617 // Forward any keypresses to our client.
618 if (o == this && d->client) {
619 lastKeyEvent.event = d->client;
620 xcb_send_event(QX11Info::connection(), false, d->client, KeyPressMask, (char*) &lastKeyEvent);
621 return true;
622 }
623 break;
624 case QEvent::KeyRelease:
625 // Forward any keyreleases to our client.
626 if (o == this && d->client) {
627 lastKeyEvent.event = d->client;
628 xcb_send_event(QX11Info::connection(), false, d->client, KeyReleaseMask, (char*) &lastKeyEvent);
629 return true;
630 }
631 break;
632
633 case QEvent::WindowActivate:
634 // When our container window is activated, we pass the
635 // activation message on to our client. Note that X input
636 // focus is set to our focus proxy. We want to intercept all
637 // keypresses.
638 if (o == window() && d->client) {
639 if (d->clientIsXEmbed) {
640 sendXEmbedMessage(d->client, XEMBED_WINDOW_ACTIVATE);
641 } else {
642 d->checkGrab();
643 if (hasFocus())
644 XSetInputFocus(QX11Info::display(), d->client, XRevertToParent, x11Time());
645 }
646 if (!d->isEmbedded())
647 d->moveInputToProxy();
648 }
649 break;
650 case QEvent::WindowDeactivate:
651 // When our container window is deactivated, we pass the
652 // deactivation message to our client.
653 if (o == window() && d->client) {
654 if (d->clientIsXEmbed)
655 sendXEmbedMessage(d->client, XEMBED_WINDOW_DEACTIVATE);
656 else
657 d->checkGrab();
658 }
659 break;
660 case QEvent::FocusIn:
661 // When receiving FocusIn events generated by Tab or Backtab,
662 // we pass focus on to our client. Any mouse activity is sent
663 // directly to the client, and it will ask us for focus with
664 // XEMBED_REQUEST_FOCUS.
665 if (o == this && d->client) {
666 if (!d->isEmbedded())
667 d->activeContainer = this;
668
669 if (d->clientIsXEmbed) {
670 if (!d->isEmbedded())
671 d->moveInputToProxy();
672
673 QFocusEvent *fe = (QFocusEvent *)event;
674 switch (fe->reason()) {
675 case Qt::TabFocusReason:
676 sendXEmbedMessage(d->client, XEMBED_FOCUS_IN, XEMBED_FOCUS_FIRST);
677 break;
678 case Qt::BacktabFocusReason:
679 sendXEmbedMessage(d->client, XEMBED_FOCUS_IN, XEMBED_FOCUS_LAST);
680 break;
681 default:
682 sendXEmbedMessage(d->client, XEMBED_FOCUS_IN, XEMBED_FOCUS_CURRENT);
683 break;
684 }
685 } else {
686 d->checkGrab();
687 XSetInputFocus(QX11Info::display(), d->client, XRevertToParent, x11Time());
688 }
689 }
690
691 break;
692 case QEvent::FocusOut: {
693 // When receiving a FocusOut, we ask our client to remove its
694 // focus.
695 if (o == this && d->client) {
696 if (!d->isEmbedded()) {
697 d->activeContainer = 0;
698 if (isActiveWindow())
699 d->moveInputToProxy();
700 }
701
702 if (d->clientIsXEmbed) {
703 QFocusEvent *fe = (QFocusEvent *)event;
704 if (o == this && d->client && fe->reason() != Qt::ActiveWindowFocusReason)
705 sendXEmbedMessage(d->client, XEMBED_FOCUS_OUT);
706 } else {
707 d->checkGrab();
708 }
709 }
710 }
711 break;
712
713 case QEvent::Close: {
714 if (o == this && d->client) {
715 // Unmap the client and reparent it to the root window.
716 // Wait until the messages have been processed. Then ask
717 // the window manager to delete the window.
718 XUnmapWindow(QX11Info::display(), d->client);
719 XReparentWindow(QX11Info::display(), d->client, QX11Info::appRootWindow(QX11Info::appScreen()), 0, 0);
720 XSync(QX11Info::display(), false);
721
722 XEvent ev;
723 memset(&ev, 0, sizeof(ev));
724 ev.xclient.type = ClientMessage;
725 ev.xclient.window = d->client;
726 ev.xclient.message_type = ATOM(WM_PROTOCOLS);
727 ev.xclient.format = 32;
728 ev.xclient.data.s[0] = ATOM(WM_DELETE_WINDOW);
729 XSendEvent(QX11Info::display(), d->client, false, NoEventMask, &ev);
730
731 XFlush(QX11Info::display());
732 d->client = 0;
733 d->clientIsXEmbed = false;
734 d->wmMinimumSizeHint = QSize();
735 updateGeometry();
736 setEnabled(false);
737 update();
738
739 emit clientClosed();
740 }
741 }
742 default:
743 break;
744 }
745
746 return QWidget::eventFilter(o, event);
747 }
748
749 /*! \internal
750
751 Handles X11 events for the container.
752 */
x11Event(void * message,long *)753 bool QX11EmbedContainer::x11Event(void *message, long*)
754 {
755 xcb_generic_event_t* e = reinterpret_cast<xcb_generic_event_t*>(message);
756 Q_D(QX11EmbedContainer);
757
758 switch (e->response_type & ~0x80) {
759 case XCB_CREATE_NOTIFY:
760 #ifdef QX11EMBED_DEBUG
761 qDebug() << "client created" << reinterpret_cast<xcb_create_notify_event_t*>(e)->window;
762 #endif
763 // The client created an embedded window.
764 if (d->client)
765 d->rejectClient(reinterpret_cast<xcb_create_notify_event_t*>(e)->window);
766 else
767 d->acceptClient(reinterpret_cast<xcb_create_notify_event_t*>(e)->window);
768 break;
769 case XCB_DESTROY_NOTIFY:
770 if (reinterpret_cast<xcb_destroy_notify_event_t*>(e)->window == d->client) {
771 #ifdef QX11EMBED_DEBUG
772 qDebug() << "client died";
773 #endif
774 // The client died.
775 d->client = 0;
776 d->clientIsXEmbed = false;
777 d->wmMinimumSizeHint = QSize();
778 updateGeometry();
779 update();
780 setEnabled(false);
781 emit clientClosed();
782 }
783 break;
784 case XCB_REPARENT_NOTIFY: {
785 // The client sends us this if it reparents itself out of our
786 // widget.
787 auto* event = reinterpret_cast<xcb_reparent_notify_event_t*>(e);
788 if (event->window == d->client && event->parent != internalWinId()) {
789 d->client = 0;
790 d->clientIsXEmbed = false;
791 d->wmMinimumSizeHint = QSize();
792 updateGeometry();
793 update();
794 setEnabled(false);
795 emit clientClosed();
796 } else if (event->parent == internalWinId()) {
797 // The client reparented itself into this window.
798 if (d->client)
799 d->rejectClient(event->window);
800 else
801 d->acceptClient(event->window);
802 }
803 break;
804 }
805 case XCB_CLIENT_MESSAGE: {
806 auto* event = reinterpret_cast<xcb_client_message_event_t*>(e);
807 if (event->type == ATOM(_XEMBED)) {
808 // Ignore XEMBED messages not to ourselves
809 if (event->window != internalWinId())
810 break;
811
812 // Receiving an XEmbed message means the client
813 // is an XEmbed client.
814 d->clientIsXEmbed = true;
815
816 //TODO: Port to Qt5, if needed
817 //Time msgtime = (Time) event->data.data32[0];
818 //if (msgtime > X11->time)
819 //X11->time = msgtime;
820
821 switch (event->data.data32[1]) {
822 case XEMBED_REQUEST_FOCUS: {
823 // This typically happens when the client gets focus
824 // because of a mouse click.
825 if (!hasFocus())
826 setFocus(Qt::OtherFocusReason);
827
828 // The message is passed along to the topmost container
829 // that eventually responds with a XEMBED_FOCUS_IN
830 // message. The focus in message is passed all the way
831 // back until it reaches the original focus
832 // requestor. In the end, not only the original client
833 // has focus, but also all its ancestor containers.
834 if (d->isEmbedded()) {
835 // If our window's embedded flag is set, then
836 // that suggests that we are part of a client. The
837 // parentWinId will then point to an container to whom
838 // we must pass this message.
839 sendXEmbedMessage(d->topLevelParentWinId(), XEMBED_REQUEST_FOCUS);
840 } else {
841 // Our window's embedded flag is not set,
842 // so we are the topmost container. We respond to
843 // the focus request message with a focus in
844 // message. This message will pass on from client
845 // to container to client until it reaches the
846 // originator of the XEMBED_REQUEST_FOCUS message.
847 sendXEmbedMessage(d->client, XEMBED_FOCUS_IN, XEMBED_FOCUS_CURRENT);
848 }
849
850 break;
851 }
852 case XEMBED_FOCUS_NEXT:
853 // Client sends this event when it received a tab
854 // forward and was at the end of its focus chain. If
855 // we are the only widget in the focus chain, we send
856 // ourselves a FocusIn event.
857 if (d->focus_next != this) {
858 focusNextPrevChild(true);
859 } else {
860 QFocusEvent event(QEvent::FocusIn, Qt::TabFocusReason);
861 qApp->sendEvent(this, &event);
862 }
863
864 break;
865 case XEMBED_FOCUS_PREV:
866 // Client sends this event when it received a backtab
867 // and was at the start of its focus chain. If we are
868 // the only widget in the focus chain, we send
869 // ourselves a FocusIn event.
870 if (d->focus_next != this) {
871 focusNextPrevChild(false);
872 } else {
873 QFocusEvent event(QEvent::FocusIn, Qt::BacktabFocusReason);
874 qApp->sendEvent(this, &event);
875 }
876
877 break;
878 default:
879 break;
880 }
881 }
882 }
883 break;
884 case XCB_BUTTON_PRESS:
885 {
886 auto event = reinterpret_cast<xcb_key_press_event_t*>(e);
887 if (event->child == d->client && !d->clientIsXEmbed) {
888 setFocus(Qt::MouseFocusReason);
889 XAllowEvents(QX11Info::display(), ReplayPointer, CurrentTime);
890 return true;
891 }
892 }
893 break;
894 case XCB_BUTTON_RELEASE:
895 if (!d->clientIsXEmbed)
896 XAllowEvents(QX11Info::display(), SyncPointer, CurrentTime);
897 break;
898 case XCB_PROPERTY_NOTIFY:
899 {
900 auto event = reinterpret_cast<xcb_property_notify_event_t*>(e);
901
902 if (event->atom == ATOM(_XEMBED_INFO) && event->window == d->client) {
903 if (auto info = get_xembed_info(d->client)) {
904 if (info->flags & XEMBED_MAPPED) {
905 #ifdef QX11EMBED_DEBUG
906 qDebug() << "mapping client per _xembed_info";
907 #endif
908 XMapWindow(QX11Info::display(), d->client);
909 XRaiseWindow(QX11Info::display(), d->client);
910 } else {
911 #ifdef QX11EMBED_DEBUG
912 qDebug() << "unmapping client per _xembed_info";
913 #endif
914 XUnmapWindow(QX11Info::display(), d->client);
915 }
916
917 free(info);
918 }
919 }
920 break;
921 }
922 default:
923 break;
924 }
925
926 return false;
927 }
928
929 /*! \internal
930
931 Whenever the container is resized, we need to resize our client.
932 */
resizeEvent(QResizeEvent *)933 void QX11EmbedContainer::resizeEvent(QResizeEvent *)
934 {
935 Q_D(QX11EmbedContainer);
936 if (d->client)
937 XResizeWindow(QX11Info::display(), d->client, width(), height());
938 }
939
940 /*!
941 \reimp
942 */
event(QEvent * event)943 bool QX11EmbedContainer::event(QEvent *event)
944 {
945 if (event->type() == QEvent::ParentChange) {
946 XSelectInput(QX11Info::display(), internalWinId(),
947 KeyPressMask | KeyReleaseMask
948 | ButtonPressMask | ButtonReleaseMask | ButtonMotionMask
949 | KeymapStateMask
950 | PointerMotionMask
951 | EnterWindowMask | LeaveWindowMask
952 | FocusChangeMask
953 | ExposureMask
954 | StructureNotifyMask
955 | SubstructureNotifyMask);
956 }
957 return QWidget::event(event);
958 }
959
960 /*! \internal
961
962 Rejects a client window by reparenting it to the root window. The
963 client will receive a reparentnotify, and will most likely assume
964 that the container has shut down. The XEmbed protocol does not
965 define any way to reject a client window, but this is a clean way
966 to do it.
967 */
rejectClient(WId window)968 void QX11EmbedContainerPrivate::rejectClient(WId window)
969 {
970 Q_Q(QX11EmbedContainer);
971 q->setEnabled(false);
972 XRemoveFromSaveSet(QX11Info::display(), client);
973 XReparentWindow(QX11Info::display(), window, QX11Info::appRootWindow(QX11Info::appScreen()), 0, 0);
974 }
975
976 /*! \internal
977
978 Accepts a client by mapping it, resizing it and optionally
979 activating and giving it logical focusing through XEMBED messages.
980 */
acceptClient(WId window)981 void QX11EmbedContainerPrivate::acceptClient(WId window)
982 {
983 Q_Q(QX11EmbedContainer);
984 client = window;
985 q->setEnabled(true);
986
987 XSelectInput(QX11Info::display(), client, PropertyChangeMask);
988
989 // This tells Qt that we wish to forward DnD messages to
990 // our client.
991 if (!extra)
992 createExtra();
993 //TODO
994 //extraData()->xDndProxy = client;
995
996 unsigned int version = XEMBED_VERSION;
997 unsigned int clientversion = 0;
998
999 // Add this client to our saveset, so if we crash, the client window
1000 // doesn't get destroyed. This is useful for containers that restart
1001 // automatically after a crash, because it can simply reembed its clients
1002 // without having to restart them (KDE panel).
1003 XAddToSaveSet(QX11Info::display(), client);
1004
1005 // XEmbed clients have an _XEMBED_INFO property in which we can
1006 // fetch the version
1007 if (auto info = get_xembed_info(client)) {
1008 clientIsXEmbed = true;
1009 clientversion = info->version;
1010 free(info);
1011 }
1012
1013 // Store client window's original size and placement.
1014 Window root;
1015 int x_return, y_return;
1016 unsigned int width_return, height_return, border_width_return, depth_return;
1017 XGetGeometry(QX11Info::display(), client, &root, &x_return, &y_return,
1018 &width_return, &height_return, &border_width_return, &depth_return);
1019 clientOriginalRect.setCoords(x_return, y_return,
1020 x_return + width_return - 1,
1021 y_return + height_return - 1);
1022
1023 // Ask the client for its minimum size.
1024 XSizeHints size;
1025 long msize;
1026 if (XGetWMNormalHints(QX11Info::display(), client, &size, &msize) && (size.flags & PMinSize)) {
1027 wmMinimumSizeHint = QSize(size.min_width, size.min_height);
1028 q->updateGeometry();
1029 }
1030
1031 // The container should set the data2 field to the lowest of its
1032 // supported version number and that of the client (from
1033 // _XEMBED_INFO property).
1034 unsigned int minversion = version > clientversion ? clientversion : version;
1035 sendXEmbedMessage(client, XEMBED_EMBEDDED_NOTIFY, 0, q->internalWinId(), minversion);
1036
1037 // Resize it, but no smaller than its minimum size hint.
1038 XResizeWindow(QX11Info::display(),
1039 client,
1040 qMax(q->width(), wmMinimumSizeHint.width()),
1041 qMax(q->height(), wmMinimumSizeHint.height()));
1042 q->update();
1043
1044 // Not mentioned in the protocol is that if the container
1045 // is already active, the client must be activated to work
1046 // properly.
1047 if (q->window()->isActiveWindow())
1048 sendXEmbedMessage(client, XEMBED_WINDOW_ACTIVATE);
1049
1050 // Also, if the container already has focus, then it must
1051 // send a focus in message to its new client; otherwise we ask
1052 // it to remove focus.
1053 if (q->focusWidget() == q && q->hasFocus())
1054 sendXEmbedMessage(client, XEMBED_FOCUS_IN, XEMBED_FOCUS_FIRST);
1055 else
1056 sendXEmbedMessage(client, XEMBED_FOCUS_OUT);
1057
1058 // This is from the original Qt implementation. Disabled for now because it appears
1059 // to cause the mouse being grabbed permanently in some environments
1060 /*
1061 if (!clientIsXEmbed) {
1062 checkGrab();
1063 if (q->hasFocus()) {
1064 XSetInputFocus(QX11Info::display(), client, XRevertToParent, x11Time());
1065 }
1066 } else {
1067 if (!isEmbedded())
1068 moveInputToProxy();
1069 }
1070 */
1071
1072 emit q->clientIsEmbedded();
1073 }
1074
1075 /*! \internal
1076
1077 Moves X11 keyboard input focus to the focusProxy, unless the focus
1078 is there already. When X11 keyboard input focus is on the
1079 focusProxy, which is a child of the container and a sibling of the
1080 client, X11 keypresses and keyreleases will always go to the proxy
1081 and not to the client.
1082 */
moveInputToProxy()1083 void QX11EmbedContainerPrivate::moveInputToProxy()
1084 {
1085 // Following Owen Taylor's advice from the XEmbed specification to
1086 // always use CurrentTime when no explicit user action is involved.
1087 XSetInputFocus(QX11Info::display(), focusProxy->internalWinId(), XRevertToParent, CurrentTime);
1088 }
1089
1090 /*! \internal
1091
1092 Ask the window manager to give us a default minimum size.
1093 */
minimumSizeHint() const1094 QSize QX11EmbedContainer::minimumSizeHint() const
1095 {
1096 Q_D(const QX11EmbedContainer);
1097 if (!d->client || !d->wmMinimumSizeHint.isValid())
1098 return QWidget::minimumSizeHint();
1099 return d->wmMinimumSizeHint;
1100 }
1101
1102 /*! \internal
1103
1104 */
checkGrab()1105 void QX11EmbedContainerPrivate::checkGrab()
1106 {
1107 Q_Q(QX11EmbedContainer);
1108 if (!clientIsXEmbed && q->isActiveWindow() && !q->hasFocus()) {
1109 if (!xgrab) {
1110 XGrabButton(QX11Info::display(), AnyButton, AnyModifier, q->internalWinId(),
1111 true, ButtonPressMask, GrabModeSync, GrabModeAsync,
1112 None, None);
1113 }
1114 xgrab = true;
1115 } else {
1116 if (xgrab)
1117 XUngrabButton(QX11Info::display(), AnyButton, AnyModifier, q->internalWinId());
1118 xgrab = false;
1119 }
1120 }
1121
1122 /*!
1123 Detaches the client from the embedder. The client will appear as a
1124 standalone window on the desktop.
1125 */
discardClient()1126 void QX11EmbedContainer::discardClient()
1127 {
1128 Q_D(QX11EmbedContainer);
1129 if (d->client) {
1130 XResizeWindow(QX11Info::display(), d->client, d->clientOriginalRect.width(),
1131 d->clientOriginalRect.height());
1132
1133 d->rejectClient(d->client);
1134 }
1135 }
1136
1137 QT_END_NAMESPACE
1138