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