diff -r 000000000000 -r 1918ee327afb src/gui/kernel/qx11embed_x11.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gui/kernel/qx11embed_x11.cpp Mon Jan 11 14:00:40 2010 +0000 @@ -0,0 +1,1804 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformdefs.h" +#include "qx11embed_x11.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define XK_MISCELLANY +#define XK_LATIN1 +#define None 0 +#include +#include +#include +#include +#include + +#ifndef XK_ISO_Left_Tab +#define XK_ISO_Left_Tab 0xFE20 +#endif + +//#define QX11EMBED_DEBUG +#ifdef QX11EMBED_DEBUG +#include +#endif + +QT_BEGIN_NAMESPACE + +/*! + \class QX11EmbedWidget + \ingroup advanced + + \brief The QX11EmbedWidget class provides an XEmbed client widget. + + XEmbed is an X11 protocol that supports the embedding of a widget + from one application into another application. + + An XEmbed \e{client widget} is a window that is embedded into a + \e container. A container is the graphical location that embeds + (or \e swallows) an external application. + + QX11EmbedWidget is a widget used for writing XEmbed applets or + plugins. When it has been embedded and the container receives tab + focus, focus is passed on to the widget. When the widget reaches + the end of its focus chain, focus is passed back to the + container. Window activation, accelerator support, modality and + drag and drop (XDND) are also handled. + + The widget and container can both initiate the embedding. If the + widget is the initiator, the X11 window ID of the container that + it wants to embed itself into must be passed to embedInto(). + + If the container initiates the embedding, the window ID of the + embedded widget must be known. The container calls embed(), + passing the window ID. + + This example shows an application that embeds a QX11EmbedWidget + subclass into the window whose ID is passed as a command-line + argument: + + \snippet doc/src/snippets/qx11embedwidget/main.cpp 0 + + The problem of obtaining the window IDs is often solved by the + container invoking the application that provides the widget as a + separate process (as a panel invokes a docked applet), passing + its window ID to the new process as a command-line argument. The + new process can then call embedInto() with the container's window + ID, as shown in the example code above. Similarly, the new + process can report its window ID to the container through IPC, in + which case the container can embed the widget. + + When the widget has been embedded, it emits the signal + embedded(). If it is closed by the container, the widget emits + containerClosed(). If an error occurs when embedding, error() is + emitted. + + There are XEmbed widgets available for KDE and GTK+. The GTK+ + equivalent of QX11EmbedWidget is GtkPlug. The corresponding KDE 3 + widget is called QXEmbed. + + \sa QX11EmbedContainer, {XEmbed Specification} +*/ + +/*! + \class QX11EmbedContainer + \ingroup advanced + + \brief The QX11EmbedContainer class provides an XEmbed container + widget. + + XEmbed is an X11 protocol that supports the embedding of a widget + from one application into another application. + + An XEmbed \e container is the graphical location that embeds an + external \e {client widget}. A client widget is a window that is + embedded into a container. + + When a widget has been embedded and the container receives tab + focus, focus is passed on to the widget. When the widget reaches + the end of its focus chain, focus is passed back to the + container. Window activation, accelerator support, modality and + drag and drop (XDND) are also handled. + + QX11EmbedContainer is commonly used for writing panels or + toolbars that hold applets, or for \e swallowing X11 + applications. When writing a panel application, one container + widget is created on the toolbar, and it can then either swallow + another widget using embed(), or allow an XEmbed widget to be + embedded into itself. The container's X11 window ID, which is + retrieved with winId(), must then be known to the client widget. + After embedding, the client's window ID can be retrieved with + clientWinId(). + + In the following example, a container widget is created as the + main widget. It then invokes an application called "playmovie", + passing its window ID as a command line argument. The "playmovie" + program is an XEmbed client widget. The widget embeds itself into + the container using the container's window ID. + + \snippet doc/src/snippets/qx11embedcontainer/main.cpp 0 + + When the client widget is embedded, the container emits the + signal clientIsEmbedded(). The signal clientClosed() is emitted + when a widget is closed. + + It is possible for QX11EmbedContainer to embed XEmbed widgets + from toolkits other than Qt, such as GTK+. Arbitrary (non-XEmbed) + X11 widgets can also be embedded, but the XEmbed-specific + features such as window activation and focus handling are then + lost. + + The GTK+ equivalent of QX11EmbedContainer is GtkSocket. The + corresponding KDE 3 widget is called QXEmbed. + + \sa QX11EmbedWidget, {XEmbed Specification} +*/ + +/*! \fn void QX11EmbedWidget::embedded() + + This signal is emitted by the widget that has been embedded by an + XEmbed container. +*/ + +/*! \fn void QX11EmbedWidget::containerClosed() + + This signal is emitted by the client widget when the container + closes the widget. This can happen if the container itself + closes, or if the widget is rejected. + + The container can reject a widget for any reason, but the most + common cause of a rejection is when an attempt is made to embed a + widget into a container that already has an embedded widget. +*/ + +/*! \fn void QX11EmbedContainer::clientIsEmbedded() + + This signal is emitted by the container when a client widget has + been embedded. +*/ + +/*! \fn void QX11EmbedContainer::clientClosed() + + This signal is emitted by the container when the client widget + closes. +*/ + +/*! + \fn void QX11EmbedWidget::error(QX11EmbedWidget::Error error) + + This signal is emitted if an error occurred as a result of + embedding into or communicating with a container. The specified + \a error describes the problem that occurred. + + \sa QX11EmbedWidget::Error +*/ + +/*! + \fn QX11EmbedContainer::Error QX11EmbedContainer::error() const + + Returns the last error that occurred. +*/ + +/*! \fn void QX11EmbedContainer::error(QX11EmbedContainer::Error error) + + This signal is emitted if an error occurred when embedding or + communicating with a client. The specified \a error describes the + problem that occurred. + + \sa QX11EmbedContainer::Error +*/ + +/*! + \enum QX11EmbedWidget::Error + + \value Unknown An unrecognized error occurred. + + \value InvalidWindowID The X11 window ID of the container was + invalid. This error is usually triggered by passing an invalid + window ID to embedInto(). + + \omitvalue Internal +*/ + +/*! + \enum QX11EmbedContainer::Error + + \value Unknown An unrecognized error occurred. + + \value InvalidWindowID The X11 window ID of the container was + invalid. This error is usually triggered by passing an invalid + window ID to embed(). + + \omitvalue Internal +*/ + +const int XButtonPress = ButtonPress; +const int XButtonRelease = ButtonRelease; +#undef ButtonPress +#undef ButtonRelease + +// This is a hack to move topData() out from QWidgetPrivate to public. We +// need to to inspect window()'s embedded state. +class QHackWidget : public QWidget +{ + Q_DECLARE_PRIVATE(QWidget) +public: + QTLWExtra* topData() { return d_func()->topData(); } +}; + +static unsigned int XEMBED_VERSION = 0; + +enum QX11EmbedMessageType { + XEMBED_EMBEDDED_NOTIFY = 0, + XEMBED_WINDOW_ACTIVATE = 1, + XEMBED_WINDOW_DEACTIVATE = 2, + XEMBED_REQUEST_FOCUS = 3, + XEMBED_FOCUS_IN = 4, + XEMBED_FOCUS_OUT = 5, + XEMBED_FOCUS_NEXT = 6, + XEMBED_FOCUS_PREV = 7, + XEMBED_MODALITY_ON = 10, + XEMBED_MODALITY_OFF = 11, + XEMBED_REGISTER_ACCELERATOR = 12, + XEMBED_UNREGISTER_ACCELERATOR = 13, + XEMBED_ACTIVATE_ACCELERATOR = 14 +}; + +enum QX11EmbedFocusInDetail { + XEMBED_FOCUS_CURRENT = 0, + XEMBED_FOCUS_FIRST = 1, + XEMBED_FOCUS_LAST = 2 +}; + +enum QX11EmbedFocusInFlags { + XEMBED_FOCUS_OTHER = (0 << 0), + XEMBED_FOCUS_WRAPAROUND = (1 << 0) +}; + +enum QX11EmbedInfoFlags { + XEMBED_MAPPED = (1 << 0) +}; + +enum QX11EmbedAccelModifiers { + XEMBED_MODIFIER_SHIFT = (1 << 0), + XEMBED_MODIFIER_CONTROL = (1 << 1), + XEMBED_MODIFIER_ALT = (1 << 2), + XEMBED_MODIFIER_SUPER = (1 << 3), + XEMBED_MODIFIER_HYPER = (1 << 4) +}; + +enum QX11EmbedAccelFlags { + XEMBED_ACCELERATOR_OVERLOADED = (1 << 0) +}; + +// Silence the default X11 error handler. +static int x11ErrorHandler(Display *, XErrorEvent *) +{ + return 0; +} + +// Returns the X11 timestamp. Maintained mainly by qapplication +// internals, but also updated by the XEmbed widgets. +static Time x11Time() +{ + return qt_x11Data->time; +} + +// Gives the version and flags of the supported XEmbed protocol. +static unsigned int XEmbedVersion() +{ + return 0; +} + +// Sends an XEmbed message. +static void sendXEmbedMessage(WId window, Display *display, long message, + long detail = 0, long data1 = 0, long data2 = 0) +{ + XClientMessageEvent c; + memset(&c, 0, sizeof(c)); + c.type = ClientMessage; + c.message_type = ATOM(_XEMBED); + c.format = 32; + c.display = display; + c.window = window; + + c.data.l[0] = x11Time(); + c.data.l[1] = message; + c.data.l[2] = detail; + c.data.l[3] = data1; + c.data.l[4] = data2; + + XSendEvent(display, window, false, NoEventMask, (XEvent *) &c); +} + +// From qapplication_x11.cpp +static XKeyEvent lastKeyEvent; + +static QCoreApplication::EventFilter oldX11EventFilter; + +// The purpose of this global x11 filter is for one to capture the key +// events in their original state, but most importantly this is the +// only way to get the WM_TAKE_FOCUS message from WM_PROTOCOLS. +static bool x11EventFilter(void *message, long *result) +{ + XEvent *event = reinterpret_cast(message); + if (event->type == XKeyPress || event->type == XKeyRelease) + lastKeyEvent = event->xkey; + + if (oldX11EventFilter && oldX11EventFilter != &x11EventFilter) + return oldX11EventFilter(message, result); + else + return false; +} + +// +struct functorData +{ + Window id, rootWindow; + bool clearedWmState; + bool reparentedToRoot; +}; + +static Bool functor(Display *display, XEvent *event, XPointer arg) +{ + functorData *data = (functorData *) arg; + + if (!data->reparentedToRoot && event->type == ReparentNotify + && event->xreparent.window == data->id + && event->xreparent.parent == data->rootWindow) { + data->reparentedToRoot = true; + return true; + } + + if (!data->clearedWmState + && event->type == PropertyNotify + && event->xproperty.window == data->id + && event->xproperty.atom == ATOM(WM_STATE)) { + if (event->xproperty.state == PropertyDelete) { + data->clearedWmState = true; + return true; + } + + Atom ret; + int format, status; + unsigned char *retval; + unsigned long nitems, after; + status = XGetWindowProperty(display, data->id, ATOM(WM_STATE), 0, 2, False, ATOM(WM_STATE), + &ret, &format, &nitems, &after, &retval ); + if (status == Success && ret == ATOM(WM_STATE) && format == 32 && nitems > 0) { + long *state = (long *)retval; + if (state[0] == WithdrawnState) { + data->clearedWmState = true; + return true; + } + } + } + + return false; +} + +class QX11EmbedWidgetPrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QX11EmbedWidget) +public: + inline QX11EmbedWidgetPrivate() + { + lastError = QX11EmbedWidget::Unknown; + container = 0; + } + + void setEmbedded(); + + void emitError(QX11EmbedWidget::Error error) { + Q_Q(QX11EmbedWidget); + + lastError = error; + emit q->error(error); + } + + enum FocusWidgets { + FirstFocusWidget, + LastFocusWidget + }; + + int focusOriginator; + QWidget *getFocusWidget(FocusWidgets fw); + void checkActivateWindow(QObject *o); + QX11EmbedWidget *xEmbedWidget(QObject *o) const; + void clearFocus(); + + WId container; + QPointer currentFocus; + + QX11EmbedWidget::Error lastError; + +}; + +/*! + Constructs a QX11EmbedWidget object with the given \a parent. +*/ +QX11EmbedWidget::QX11EmbedWidget(QWidget *parent) + : QWidget(*new QX11EmbedWidgetPrivate, parent, 0) +{ + XSetErrorHandler(x11ErrorHandler); + + setAttribute(Qt::WA_NativeWindow); + setAttribute(Qt::WA_DontCreateNativeAncestors); + createWinId(); + XSelectInput(x11Info().display(), internalWinId(), + KeyPressMask | KeyReleaseMask | ButtonPressMask + | ButtonReleaseMask + | KeymapStateMask | ButtonMotionMask | PointerMotionMask + | FocusChangeMask + | ExposureMask | StructureNotifyMask + | SubstructureNotifyMask | PropertyChangeMask); + + long data[] = {XEMBED_VERSION, XEMBED_MAPPED}; + XChangeProperty(x11Info().display(), internalWinId(), ATOM(_XEMBED_INFO), + ATOM(_XEMBED_INFO), 32, PropModeReplace, + (unsigned char*) data, 2); + + setFocusPolicy(Qt::StrongFocus); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + QApplication::instance()->installEventFilter(this); + +#ifdef QX11EMBED_DEBUG + qDebug() << "QX11EmbedWidget::QX11EmbedWidget: constructed client" + << (void *)this << "with winId" << winId(); +#endif +} + +/*! + Destructs the QX11EmbedWidget object. If the widget is embedded + when deleted, it is hidden and then detached from its container, + so that the container is free to embed a new widget. +*/ +QX11EmbedWidget::~QX11EmbedWidget() +{ + Q_D(QX11EmbedWidget); + if (d->container) { +#ifdef QX11EMBED_DEBUG + qDebug() << "QX11EmbedWidget::~QX11EmbedWidget: unmapping" + << (void *)this << "with winId" << winId() + << "from container with winId" << d->container; +#endif + XUnmapWindow(x11Info().display(), internalWinId()); + XReparentWindow(x11Info().display(), internalWinId(), x11Info().appRootWindow(), 0, 0); + } + +#ifdef QX11EMBED_DEBUG + qDebug() << "QX11EmbedWidget::~QX11EmbedWidget: destructed client" + << (void *)this << "with winId" << winId(); +#endif +} + +/*! + Returns the type of error that occurred last. This is the same error code + that is emitted by the error() signal. + + \sa Error +*/ +QX11EmbedWidget::Error QX11EmbedWidget::error() const +{ + return d_func()->lastError; +} + +/*! + When this function is called, the widget embeds itself into the + container whose window ID is \a id. + + If \a id is \e not the window ID of a container this function will + behave unpredictably. +*/ +void QX11EmbedWidget::embedInto(WId id) +{ + Q_D(QX11EmbedWidget); +#ifdef QX11EMBED_DEBUG + qDebug() << "QX11EmbedWidget::embedInto: embedding client" + << (void *)this << "with winId" << winId() << "into container" + << id; +#endif + + d->container = id; + switch (XReparentWindow(x11Info().display(), internalWinId(), d->container, 0, 0)) { + case BadWindow: + d->emitError(InvalidWindowID); + break; + case BadMatch: + d->emitError(Internal); + break; + case Success: + default: + break; + } + QTLWExtra* x = d->extra ? d->extra->topextra : 0; + if (x) + x->frameStrut.setCoords(0, 0, 0, 0); + d->data.fstrut_dirty = false; +} + +/*! \internal + + Gets the first or last child widget that can get focus. +*/ +QWidget *QX11EmbedWidgetPrivate::getFocusWidget(FocusWidgets fw) +{ + Q_Q(QX11EmbedWidget); + QWidget *tlw = q; + QWidget *w = tlw->nextInFocusChain(); + + QWidget *last = tlw; + + extern bool qt_tab_all_widgets; + uint focus_flag = qt_tab_all_widgets ? Qt::TabFocus : Qt::StrongFocus; + + while (w != tlw) + { + if (((w->focusPolicy() & focus_flag) == focus_flag) + && w->isVisibleTo(q) && w->isEnabled()) + { + last = w; + if (fw == FirstFocusWidget) + break; + } + w = w->nextInFocusChain(); + } + + return last; +} + +/*! \internal + + Returns the xembed widget that the widget is a child of +*/ +QX11EmbedWidget *QX11EmbedWidgetPrivate::xEmbedWidget(QObject *o) const +{ + QX11EmbedWidget *xec = 0; + + // Check the widget itself, then its parents, and find the first + // QX11EmbedWidget. + do { + if ((xec = qobject_cast(o))) + return xec; + } while ((o = o->parent())); + return 0; +} + +/*! \internal + + Checks the active window. +*/ +void QX11EmbedWidgetPrivate::checkActivateWindow(QObject *o) +{ + Q_Q(QX11EmbedWidget); + QX11EmbedWidget *xec = xEmbedWidget(o); + + // check if we are in the right xembed client + if (q != xec) + return; + + QWidget *w = qobject_cast(o); + + // if it is no active window, then don't do the change + if (!(w && qApp->activeWindow())) + return; + + // if it already is the active window, don't do anything + if (w->window() != qApp->activeWindow()) + { + qApp->setActiveWindow(w->window()); + currentFocus = w; + + sendXEmbedMessage(xec->containerWinId(), q->x11Info().display(), XEMBED_REQUEST_FOCUS); + } +} + +/*! \internal + + Clears the focus +*/ +void QX11EmbedWidgetPrivate::clearFocus() +{ + Q_Q(QX11EmbedWidget); + // Setting focus on the client itself removes Qt's + // logical focus rectangle. We can't just do a + // clearFocus here, because when we send the synthetic + // FocusIn to ourselves on activation, Qt will set + // focus on focusWidget() again. This way, we "hide" + // focus rather than clearing it. + + if (!q->window()->hasFocus()) + q->window()->setFocus(Qt::OtherFocusReason); + + currentFocus = 0; +} + +/*! \internal + + Sets the embedded flag on the client. +*/ +void QX11EmbedWidgetPrivate::setEmbedded() +{ + Q_Q(QX11EmbedWidget); + ((QHackWidget *)q->window())->topData()->embedded = 1; +} + +/*! \internal + + Handles WindowActivate and FocusIn events for the client. +*/ +bool QX11EmbedWidget::eventFilter(QObject *o, QEvent *event) +{ + Q_D(QX11EmbedWidget); + switch (event->type()) { + case QEvent::FocusIn: + switch (((QFocusEvent *)event)->reason()) { + case Qt::MouseFocusReason: + // If the user clicks into one of the client widget's + // children and we didn't have focus already, we request + // focus from our container. + if (d->xEmbedWidget(o) == this) { + if (d->currentFocus.isNull()) + sendXEmbedMessage(d->container, x11Info().display(), XEMBED_REQUEST_FOCUS); + + d->currentFocus = qobject_cast(o); + } + break; + case Qt::TabFocusReason: + // If the xembed client receives a focus event because of + // a Tab, then we are at the end of our focus chain and we + // ask the container to move to its next focus widget. + if (o == this) { + d->clearFocus(); + sendXEmbedMessage(d->container, x11Info().display(), XEMBED_FOCUS_NEXT); + return true; + } else { + // We're listening on events from qApp, so in order + // for us to know who to set focus on if we receive an + // activation event, we note the widget that got the + // focusin last. + if (d->xEmbedWidget(o) == this) + d->currentFocus = qobject_cast(o); + } + break; + case Qt::BacktabFocusReason: + // If the window receives a focus event because of + // a Backtab, then we are at the start of our focus chain + // and we ask the container to move to its previous focus + // widget. + if (o == this) { + // See comment for Tab. + // If we receive a XEMBED_FOCUS_IN + // XEMBED_FOCUS_CURRENT, we will set focus in + // currentFocus. To avoid that in this case, we reset + // currentFocus. + d->clearFocus(); + sendXEmbedMessage(d->container, x11Info().display(), XEMBED_FOCUS_PREV); + return true; + } else { + if (d->xEmbedWidget(o) == this) + d->currentFocus = qobject_cast(o); + } + break; + case Qt::ActiveWindowFocusReason: + if (isActiveWindow()) { + if (!d->currentFocus.isNull()) { + if (!d->currentFocus->hasFocus()) + d->currentFocus->setFocus(Qt::OtherFocusReason); + } else { + d->clearFocus(); + return true; + } + } + + break; + case Qt::PopupFocusReason: + case Qt::ShortcutFocusReason: + case Qt::OtherFocusReason: + // If focus is received to any child widget because of any + // other reason, remember the widget so that we can give + // it focus again if we're activated. + if (d->xEmbedWidget(o) == this) { + d->currentFocus = qobject_cast(o); + } + break; + default: + break; + } + break; + case QEvent::MouseButtonPress: + // If we get a mouse button press event inside a embedded widget + // make sure this is the active window in qapp. + d->checkActivateWindow(o); + break; + default: + break; + } + + return QWidget::eventFilter(o, event); +} + +/*! \internal + + Handles some notification events and client messages. Client side + XEmbed message receiving is also handled here. +*/ +bool QX11EmbedWidget::x11Event(XEvent *event) +{ + Q_D(QX11EmbedWidget); + switch (event->type) { + case DestroyNotify: +#ifdef QX11EMBED_DEBUG + qDebug() << "QX11EmbedWidget::x11Event: client" + << (void *)this << "with winId" << winId() + << "received a DestroyNotify"; +#endif + // If the container window is destroyed, we signal this to the user. + d->container = 0; + emit containerClosed(); + break; + case ReparentNotify: +#ifdef QX11EMBED_DEBUG + qDebug() << "QX11EmbedWidget::x11Event: client" + << (void *)this << "with winId" << winId() + << "received a ReparentNotify to" + << ((event->xreparent.parent == x11Info().appRootWindow()) + ? QString::fromLatin1("root") : QString::number(event->xreparent.parent)); +#endif + // If the container shuts down, we will be reparented to the + // root window. We must also consider the case that we may be + // reparented from one container to another. + if (event->xreparent.parent == x11Info().appRootWindow()) { + if (((QHackWidget *)this)->topData()->embedded) { + d->container = 0; + emit containerClosed(); + } + return true; + } else { + d->container = event->xreparent.parent; + } + break; + case UnmapNotify: + // Mapping and unmapping are handled by changes to the + // _XEMBED_INFO property. Any default map/unmap requests are + // ignored. + return true; + case PropertyNotify: + // The container sends us map/unmap messages through the + // _XEMBED_INFO property. We adhere to the XEMBED_MAPPED bit in + // data2. + if (event->xproperty.atom == ATOM(_XEMBED_INFO)) { + Atom actual_type_return; + int actual_format_return; + unsigned long nitems_return; + unsigned long bytes_after_return; + unsigned char *prop_return = 0; + if (XGetWindowProperty(x11Info().display(), internalWinId(), ATOM(_XEMBED_INFO), 0, 2, + false, ATOM(_XEMBED_INFO), &actual_type_return, + &actual_format_return, &nitems_return, + &bytes_after_return, &prop_return) == Success) { + if (nitems_return > 1) { + if (((long * )prop_return)[1] & XEMBED_MAPPED) { + XMapWindow(x11Info().display(), internalWinId()); + } else { + XUnmapWindow(x11Info().display(), internalWinId()); + } + } + } + } + + break; + case ClientMessage: + // XEMBED messages have message_type _XEMBED + if (event->xclient.message_type == ATOM(_XEMBED)) { + // Discard XEMBED messages not to ourselves. (### dead code?) + if (event->xclient.window != internalWinId()) + break; + + // Update qt_x_time if necessary + Time msgtime = (Time) event->xclient.data.l[0]; + if (msgtime > X11->time) + X11->time = msgtime; + + switch (event->xclient.data.l[1]) { + case XEMBED_WINDOW_ACTIVATE: { + // When we receive an XEMBED_WINDOW_ACTIVATE, we simply send + // ourselves a WindowActivate event. Real activation happens + // when receive XEMBED_FOCUS_IN. + if (!isActiveWindow()) { + QEvent ev(QEvent::WindowActivate); + QApplication::sendEvent(this, &ev); + } + } + break; + case XEMBED_WINDOW_DEACTIVATE: { + // When we receive an XEMBED_WINDOW_DEACTIVATE, we simply send + // ourselves a WindowDeactivate event. Real activation happens + // when receive XEMBED_FOCUS_IN. + if (isActiveWindow()) { + if (!qApp->activePopupWidget()) + QApplication::setActiveWindow(0); + } else { + QEvent ev(QEvent::WindowDeactivate); + QApplication::sendEvent(this, &ev); + } + } + break; + case XEMBED_EMBEDDED_NOTIFY: { +#ifdef QX11EMBED_DEBUG + qDebug() << "QX11EmbedWidget::x11Event: client" + << (void *)this << "with winId" << winId() + << "received an XEMBED EMBEDDED NOTIFY message"; +#endif + // In this message's l[2] we have the max version + // supported by both the client and the + // container. QX11EmbedWidget does not use this field. + + // We have been embedded, so we set our + // client's embedded flag. + d->setEmbedded(); + emit embedded(); + } + break; + case XEMBED_FOCUS_IN: + // don't set the focus if a modal dialog is open + if (qApp->activeModalWidget()) + break; + + // in case we embed more than one topLevel window inside the same + // host window. + if (window() != qApp->activeWindow()) + qApp->setActiveWindow(this); + + switch (event->xclient.data.l[2]) { + case XEMBED_FOCUS_CURRENT: + // The container sends us this message if it wants + // us to focus on the widget that last had focus. + // This is the reply when XEMBED_REQUEST_FOCUS is + // sent to the container. + if (!d->currentFocus.isNull()) { + if (!d->currentFocus->hasFocus()) + d->currentFocus->setFocus(Qt::OtherFocusReason); + } else { + // No widget currently has focus. We set focus + // on the first widget next to the + // client widget. Since the setFocus will not work + // if the window is disabled, set the currentFocus + // directly so that it's set on window activate. + d->currentFocus = d->getFocusWidget(QX11EmbedWidgetPrivate::FirstFocusWidget); + d->currentFocus->setFocus(Qt::OtherFocusReason); + } + break; + case XEMBED_FOCUS_FIRST: + // The container sends this message when it wants + // us to focus on the first widget in our focus + // chain (typically because of a tab). + d->currentFocus = d->getFocusWidget(QX11EmbedWidgetPrivate::FirstFocusWidget); + d->currentFocus->setFocus(Qt::TabFocusReason); + break; + case XEMBED_FOCUS_LAST: + // The container sends this message when it wants + // us to focus on the last widget in our focus + // chain (typically because of a backtab). + d->currentFocus = d->getFocusWidget(QX11EmbedWidgetPrivate::LastFocusWidget); + d->currentFocus->setFocus(Qt::BacktabFocusReason); + break; + default: + // Ignore any other XEMBED_FOCUS_IN details. + break; + } + break; + case XEMBED_FOCUS_OUT: + // The container sends us this message when it wants us + // to lose focus and forget about the widget that last + // had focus. Typically sent by the container when it + // loses focus because of mouse or tab activity. We do + // then not want to set focus on anything if we're + // activated. + if (isActiveWindow()) + d->clearFocus(); + + break; + default: + // Ignore any other XEMBED messages. + break; + }; + } else { + // Non-XEMBED client messages are not interesting. + } + + break; + default: + // Ignore all other x11 events. + break; + } + + // Allow default handling. + return QWidget::x11Event(event); +} + +/*! + \reimp +*/ +bool QX11EmbedWidget::event(QEvent *event) +{ + if (event->type() == QEvent::ParentChange) { + XSelectInput(x11Info().display(), internalWinId(), + KeyPressMask | KeyReleaseMask | ButtonPressMask + | ButtonReleaseMask + | KeymapStateMask | ButtonMotionMask | PointerMotionMask + | FocusChangeMask + | ExposureMask | StructureNotifyMask + | SubstructureNotifyMask | PropertyChangeMask); + } + return QWidget::event(event); +} + +/*! + \reimp +*/ +void QX11EmbedWidget::resizeEvent(QResizeEvent *event) +{ + if (layout()) + layout()->update(); + QWidget::resizeEvent(event); +} + +/*! + If the widget is embedded, returns the window ID of the + container; otherwize returns 0. +*/ +WId QX11EmbedWidget::containerWinId() const +{ + Q_D(const QX11EmbedWidget); + return d->container; +} + +class QX11EmbedContainerPrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QX11EmbedContainer) +public: + inline QX11EmbedContainerPrivate() + { + lastError = QX11EmbedContainer::Unknown; + client = 0; + focusProxy = 0; + clientIsXEmbed = false; + xgrab = false; + } + + bool isEmbedded() const; + void moveInputToProxy(); + + void acceptClient(WId window); + void rejectClient(WId window); + + void checkGrab(); + + WId topLevelParentWinId() const; + + void emitError(QX11EmbedContainer::Error error) { + Q_Q(QX11EmbedContainer); + lastError = error; + emit q->error(error); + } + + WId client; + QWidget *focusProxy; + bool clientIsXEmbed; + bool xgrab; + QRect clientOriginalRect; + QSize wmMinimumSizeHint; + + QX11EmbedContainer::Error lastError; + + static QX11EmbedContainer *activeContainer; +}; + +QX11EmbedContainer *QX11EmbedContainerPrivate::activeContainer = 0; + +/*! + Creates a QX11EmbedContainer object with the given \a parent. +*/ +QX11EmbedContainer::QX11EmbedContainer(QWidget *parent) + : QWidget(*new QX11EmbedContainerPrivate, parent, 0) +{ + Q_D(QX11EmbedContainer); + XSetErrorHandler(x11ErrorHandler); + + setAttribute(Qt::WA_NativeWindow); + setAttribute(Qt::WA_DontCreateNativeAncestors); + createWinId(); + + setFocusPolicy(Qt::StrongFocus); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + // ### PORT setKeyCompression(false); + setAcceptDrops(true); + setEnabled(false); + + // Everybody gets a focus proxy, but only one toplevel container's + // focus proxy is actually in use. + d->focusProxy = new QWidget(this); + d->focusProxy->setAttribute(Qt::WA_NativeWindow); + d->focusProxy->setAttribute(Qt::WA_DontCreateNativeAncestors); + d->focusProxy->setGeometry(-1, -1, 1, 1); + + // We need events from the window (activation status) and + // from qApp (keypress/release). + qApp->installEventFilter(this); + + // Install X11 event filter. + if (!oldX11EventFilter) + oldX11EventFilter = QCoreApplication::instance()->setEventFilter(x11EventFilter); + + XSelectInput(x11Info().display(), internalWinId(), + KeyPressMask | KeyReleaseMask + | ButtonPressMask | ButtonReleaseMask | ButtonMotionMask + | KeymapStateMask + | PointerMotionMask + | EnterWindowMask | LeaveWindowMask + | FocusChangeMask + | ExposureMask + | StructureNotifyMask + | SubstructureNotifyMask); + + // Make sure our new event mask takes effect as soon as possible. + XFlush(x11Info().display()); + + // Move input to our focusProxy if this widget is active, and not + // shaded by a modal dialog (in which case isActiveWindow() would + // still return true, but where we must not move input focus). + if (qApp->activeWindow() == window() && !d->isEmbedded()) + d->moveInputToProxy(); + +#ifdef QX11EMBED_DEBUG + qDebug() << "QX11EmbedContainer::QX11EmbedContainer: constructed container" + << (void *)this << "with winId" << winId(); +#endif +} + +/*! + Destructs a QX11EmbedContainer. +*/ +QX11EmbedContainer::~QX11EmbedContainer() +{ + Q_D(QX11EmbedContainer); + if (d->client) { + XUnmapWindow(x11Info().display(), d->client); + XReparentWindow(x11Info().display(), d->client, x11Info().appRootWindow(), 0, 0); + } + + if (d->xgrab) + XUngrabButton(x11Info().display(), AnyButton, AnyModifier, internalWinId()); +} + + +QX11EmbedContainer::Error QX11EmbedContainer::error() const { + return d_func()->lastError; +} + + +/*! \reimp +*/ +void QX11EmbedContainer::paintEvent(QPaintEvent *) +{ +} + +/*! \internal + + Returns wether or not the windows' embedded flag is set. +*/ +bool QX11EmbedContainerPrivate::isEmbedded() const +{ + Q_Q(const QX11EmbedContainer); + return ((QHackWidget *)q->window())->topData()->embedded == 1; +} + +/*! \internal + + Returns the parentWinId of the window. +*/ +WId QX11EmbedContainerPrivate::topLevelParentWinId() const +{ + Q_Q(const QX11EmbedContainer); + return ((QHackWidget *)q->window())->topData()->parentWinId; +} + +/*! + If the container has an embedded widget, this function returns + the X11 window ID of the client; otherwise it returns 0. +*/ +WId QX11EmbedContainer::clientWinId() const +{ + Q_D(const QX11EmbedContainer); + return d->client; +} + +/*! + Instructs the container to embed the X11 window with window ID \a + id. The client widget will then move on top of the container + window and be resized to fit into the container. + + The \a id should be the ID of a window controlled by an XEmbed + enabled application, but this is not mandatory. If \a id does not + belong to an XEmbed client widget, then focus handling, + activation, accelerators and other features will not work + properly. +*/ +void QX11EmbedContainer::embedClient(WId id) +{ + Q_D(QX11EmbedContainer); + + if (id == 0) { + d->emitError(InvalidWindowID); + return; + } + + // Walk up the tree of parent windows to prevent embedding of ancestors. + WId thisId = internalWinId(); + Window rootReturn; + Window parentReturn; + Window *childrenReturn = 0; + unsigned int nchildrenReturn; + do { + if (XQueryTree(x11Info().display(), thisId, &rootReturn, + &parentReturn, &childrenReturn, &nchildrenReturn) == 0) { + d->emitError(InvalidWindowID); + return; + } + if (childrenReturn) { + XFree(childrenReturn); + childrenReturn = 0; + } + + thisId = parentReturn; + if (id == thisId) { + d->emitError(InvalidWindowID); + return; + } + } while (thisId != rootReturn); + + // watch for property notify events (see below) + XGrabServer(x11Info().display()); + XWindowAttributes attrib; + if (!XGetWindowAttributes(x11Info().display(), id, &attrib)) { + XUngrabServer(x11Info().display()); + d->emitError(InvalidWindowID); + return; + } + XSelectInput(x11Info().display(), id, attrib.your_event_mask | PropertyChangeMask | StructureNotifyMask); + XUngrabServer(x11Info().display()); + + // Put the window into WithdrawnState + XUnmapWindow(x11Info().display(), id); + XSync(x11Info().display(), False); // make sure the window is hidden + + /* + Wait for notification from the window manager that the window is + in withdrawn state. According to the ICCCM section 4.1.3.1, + we should wait for the WM_STATE property to either be deleted or + set to WithdrawnState. + + For safety, we will not wait more than 500 ms, so that we can + preemptively workaround buggy window managers. + */ + QTime t; + t.start(); + + functorData data; + data.id = id; + data.rootWindow = attrib.root; + data.clearedWmState = false; + data.reparentedToRoot = false; + + do { + if (t.elapsed() > 500) // time-out after 500 ms + break; + + XEvent event; + if (!XCheckIfEvent(x11Info().display(), &event, functor, (XPointer) &data)) { + XSync(x11Info().display(), False); + usleep(50000); + continue; + } + + qApp->x11ProcessEvent(&event); + } while (!data.clearedWmState || !data.reparentedToRoot); + + // restore the event mask + XSelectInput(x11Info().display(), id, attrib.your_event_mask); + + switch (XReparentWindow(x11Info().display(), id, internalWinId(), 0, 0)) { + case BadWindow: + case BadMatch: + d->emitError(InvalidWindowID); + break; + default: + break; + } +} + +/*! \internal + + Handles key, activation and focus events for the container. +*/ +bool QX11EmbedContainer::eventFilter(QObject *o, QEvent *event) +{ + Q_D(QX11EmbedContainer); + switch (event->type()) { + case QEvent::KeyPress: + // Forward any keypresses to our client. + if (o == this && d->client) { + lastKeyEvent.window = d->client; + XSendEvent(x11Info().display(), d->client, false, KeyPressMask, (XEvent *) &lastKeyEvent); + return true; + } + break; + case QEvent::KeyRelease: + // Forward any keyreleases to our client. + if (o == this && d->client) { + lastKeyEvent.window = d->client; + XSendEvent(x11Info().display(), d->client, false, KeyReleaseMask, (XEvent *) &lastKeyEvent); + return true; + } + break; + + case QEvent::WindowActivate: + // When our container window is activated, we pass the + // activation message on to our client. Note that X input + // focus is set to our focus proxy. We want to intercept all + // keypresses. + if (o == window() && d->client) { + if (d->clientIsXEmbed) { + sendXEmbedMessage(d->client, x11Info().display(), XEMBED_WINDOW_ACTIVATE); + } else { + d->checkGrab(); + if (hasFocus()) + XSetInputFocus(x11Info().display(), d->client, XRevertToParent, x11Time()); + } + if (!d->isEmbedded()) + d->moveInputToProxy(); + } + break; + case QEvent::WindowDeactivate: + // When our container window is deactivated, we pass the + // deactivation message to our client. + if (o == window() && d->client) { + if (d->clientIsXEmbed) + sendXEmbedMessage(d->client, x11Info().display(), XEMBED_WINDOW_DEACTIVATE); + else + d->checkGrab(); + } + break; + case QEvent::FocusIn: + // When receiving FocusIn events generated by Tab or Backtab, + // we pass focus on to our client. Any mouse activity is sent + // directly to the client, and it will ask us for focus with + // XEMBED_REQUEST_FOCUS. + if (o == this && d->client) { + if (!d->isEmbedded()) + d->activeContainer = this; + + if (d->clientIsXEmbed) { + if (!d->isEmbedded()) + d->moveInputToProxy(); + + QFocusEvent *fe = (QFocusEvent *)event; + switch (fe->reason()) { + case Qt::TabFocusReason: + sendXEmbedMessage(d->client, x11Info().display(), XEMBED_FOCUS_IN, XEMBED_FOCUS_FIRST); + break; + case Qt::BacktabFocusReason: + sendXEmbedMessage(d->client, x11Info().display(), XEMBED_FOCUS_IN, XEMBED_FOCUS_LAST); + break; + default: + sendXEmbedMessage(d->client, x11Info().display(), XEMBED_FOCUS_IN, XEMBED_FOCUS_CURRENT); + break; + } + } else { + d->checkGrab(); + XSetInputFocus(x11Info().display(), d->client, XRevertToParent, x11Time()); + } + } + + break; + case QEvent::FocusOut: { + // When receiving a FocusOut, we ask our client to remove its + // focus. + if (o == this && d->client) { + if (!d->isEmbedded()) { + d->activeContainer = 0; + if (isActiveWindow()) + d->moveInputToProxy(); + } + + if (d->clientIsXEmbed) { + QFocusEvent *fe = (QFocusEvent *)event; + if (o == this && d->client && fe->reason() != Qt::ActiveWindowFocusReason) + sendXEmbedMessage(d->client, x11Info().display(), XEMBED_FOCUS_OUT); + } else { + d->checkGrab(); + } + } + } + break; + + case QEvent::Close: { + if (o == this && d->client) { + // Unmap the client and reparent it to the root window. + // Wait until the messages have been processed. Then ask + // the window manager to delete the window. + XUnmapWindow(x11Info().display(), d->client); + XReparentWindow(x11Info().display(), d->client, x11Info().appRootWindow(), 0, 0); + XSync(x11Info().display(), false); + + XEvent ev; + memset(&ev, 0, sizeof(ev)); + ev.xclient.type = ClientMessage; + ev.xclient.window = d->client; + ev.xclient.message_type = ATOM(WM_PROTOCOLS); + ev.xclient.format = 32; + ev.xclient.data.s[0] = ATOM(WM_DELETE_WINDOW); + XSendEvent(x11Info().display(), d->client, false, NoEventMask, &ev); + + XFlush(x11Info().display()); + d->client = 0; + d->clientIsXEmbed = false; + d->wmMinimumSizeHint = QSize(); + updateGeometry(); + setEnabled(false); + update(); + + emit clientClosed(); + } + } + default: + break; + } + + return QWidget::eventFilter(o, event); +} + +/*! \internal + + Handles X11 events for the container. +*/ +bool QX11EmbedContainer::x11Event(XEvent *event) +{ + Q_D(QX11EmbedContainer); + + switch (event->type) { + case CreateNotify: + // The client created an embedded window. + if (d->client) + d->rejectClient(event->xcreatewindow.window); + else + d->acceptClient(event->xcreatewindow.window); + break; + case DestroyNotify: + if (event->xdestroywindow.window == d->client) { + // The client died. + d->client = 0; + d->clientIsXEmbed = false; + d->wmMinimumSizeHint = QSize(); + updateGeometry(); + update(); + setEnabled(false); + emit clientClosed(); + } + break; + case ReparentNotify: + // The client sends us this if it reparents itself out of our + // widget. + if (event->xreparent.window == d->client && event->xreparent.parent != internalWinId()) { + d->client = 0; + d->clientIsXEmbed = false; + d->wmMinimumSizeHint = QSize(); + updateGeometry(); + update(); + setEnabled(false); + emit clientClosed(); + } else if (event->xreparent.parent == internalWinId()) { + // The client reparented itself into this window. + if (d->client) + d->rejectClient(event->xreparent.window); + else + d->acceptClient(event->xreparent.window); + } + break; + case ClientMessage: { + if (event->xclient.message_type == ATOM(_XEMBED)) { + // Ignore XEMBED messages not to ourselves + if (event->xclient.window != internalWinId()) + break; + + // Receiving an XEmbed message means the client + // is an XEmbed client. + d->clientIsXEmbed = true; + + Time msgtime = (Time) event->xclient.data.l[0]; + if (msgtime > X11->time) + X11->time = msgtime; + + switch (event->xclient.data.l[1]) { + case XEMBED_REQUEST_FOCUS: { + // This typically happens when the client gets focus + // because of a mouse click. + if (!hasFocus()) + setFocus(Qt::OtherFocusReason); + + // The message is passed along to the topmost container + // that eventually responds with a XEMBED_FOCUS_IN + // message. The focus in message is passed all the way + // back until it reaches the original focus + // requestor. In the end, not only the original client + // has focus, but also all its ancestor containers. + if (d->isEmbedded()) { + // If our window's embedded flag is set, then + // that suggests that we are part of a client. The + // parentWinId will then point to an container to whom + // we must pass this message. + sendXEmbedMessage(d->topLevelParentWinId(), x11Info().display(), XEMBED_REQUEST_FOCUS); + } else { + // Our window's embedded flag is not set, + // so we are the topmost container. We respond to + // the focus request message with a focus in + // message. This message will pass on from client + // to container to client until it reaches the + // originator of the XEMBED_REQUEST_FOCUS message. + sendXEmbedMessage(d->client, x11Info().display(), XEMBED_FOCUS_IN, XEMBED_FOCUS_CURRENT); + } + + break; + } + case XEMBED_FOCUS_NEXT: + // Client sends this event when it received a tab + // forward and was at the end of its focus chain. If + // we are the only widget in the focus chain, we send + // ourselves a FocusIn event. + if (d->focus_next != this) { + focusNextPrevChild(true); + } else { + QFocusEvent event(QEvent::FocusIn, Qt::TabFocusReason); + qApp->sendEvent(this, &event); + } + + break; + case XEMBED_FOCUS_PREV: + // Client sends this event when it received a backtab + // and was at the start of its focus chain. If we are + // the only widget in the focus chain, we send + // ourselves a FocusIn event. + if (d->focus_next != this) { + focusNextPrevChild(false); + } else { + QFocusEvent event(QEvent::FocusIn, Qt::BacktabFocusReason); + qApp->sendEvent(this, &event); + } + + break; + default: + break; + } + } + } + break; + case XButtonPress: + if (!d->clientIsXEmbed) { + setFocus(Qt::MouseFocusReason); + XAllowEvents(x11Info().display(), ReplayPointer, CurrentTime); + return true; + } + break; + case XButtonRelease: + if (!d->clientIsXEmbed) + XAllowEvents(x11Info().display(), SyncPointer, CurrentTime); + break; + default: + break; + } + + return QWidget::x11Event(event); +} + +/*! \internal + + Whenever the container is resized, we need to resize our client. +*/ +void QX11EmbedContainer::resizeEvent(QResizeEvent *) +{ + Q_D(QX11EmbedContainer); + if (d->client) + XResizeWindow(x11Info().display(), d->client, width(), height()); +} + +/*! \internal + + We use the QShowEvent to signal to our client that we want it to + map itself. We do this by changing its window property + XEMBED_INFO. The client will get an X11 PropertyNotify. +*/ +void QX11EmbedContainer::showEvent(QShowEvent *) +{ + Q_D(QX11EmbedContainer); + if (d->client) { + long data[] = {XEMBED_VERSION, XEMBED_MAPPED}; + XChangeProperty(x11Info().display(), d->client, ATOM(_XEMBED_INFO), ATOM(_XEMBED_INFO), 32, + PropModeReplace, (unsigned char *) data, 2); + } +} + +/*! \internal + + We use the QHideEvent to signal to our client that we want it to + unmap itself. We do this by changing its window property + XEMBED_INFO. The client will get an X11 PropertyNotify. +*/ +void QX11EmbedContainer::hideEvent(QHideEvent *) +{ + Q_D(QX11EmbedContainer); + if (d->client) { + long data[] = {XEMBED_VERSION, XEMBED_MAPPED}; + XChangeProperty(x11Info().display(), d->client, ATOM(_XEMBED_INFO), ATOM(_XEMBED_INFO), 32, + PropModeReplace, (unsigned char *) data, 2); + } +} + +/*! + \reimp +*/ +bool QX11EmbedContainer::event(QEvent *event) +{ + if (event->type() == QEvent::ParentChange) { + XSelectInput(x11Info().display(), internalWinId(), + KeyPressMask | KeyReleaseMask + | ButtonPressMask | ButtonReleaseMask | ButtonMotionMask + | KeymapStateMask + | PointerMotionMask + | EnterWindowMask | LeaveWindowMask + | FocusChangeMask + | ExposureMask + | StructureNotifyMask + | SubstructureNotifyMask); + } + return QWidget::event(event); +} + +/*! \internal + + Rejects a client window by reparenting it to the root window. The + client will receive a reparentnotify, and will most likely assume + that the container has shut down. The XEmbed protocol does not + define any way to reject a client window, but this is a clean way + to do it. +*/ +void QX11EmbedContainerPrivate::rejectClient(WId window) +{ + Q_Q(QX11EmbedContainer); + q->setEnabled(false); + XRemoveFromSaveSet(q->x11Info().display(), client); + XReparentWindow(q->x11Info().display(), window, q->x11Info().appRootWindow(), 0, 0); +} + +/*! \internal + + Accepts a client by mapping it, resizing it and optionally + activating and giving it logical focusing through XEMBED messages. +*/ +void QX11EmbedContainerPrivate::acceptClient(WId window) +{ + Q_Q(QX11EmbedContainer); + client = window; + q->setEnabled(true); + + // This tells Qt that we wish to forward DnD messages to + // our client. + if (!extra) + createExtra(); + extraData()->xDndProxy = client; + + unsigned int version = XEmbedVersion(); + + Atom actual_type_return; + int actual_format_return; + unsigned long nitems_return = 0; + unsigned long bytes_after_return; + unsigned char *prop_return = 0; + unsigned int clientversion = 0; + + // Add this client to our saveset, so if we crash, the client window + // doesn't get destroyed. This is useful for containers that restart + // automatically after a crash, because it can simply reembed its clients + // without having to restart them (KDE panel). + XAddToSaveSet(q->x11Info().display(), client); + + // XEmbed clients have an _XEMBED_INFO property in which we can + // fetch the version + if (XGetWindowProperty(q->x11Info().display(), client, ATOM(_XEMBED_INFO), 0, 2, false, + ATOM(_XEMBED_INFO), &actual_type_return, &actual_format_return, + &nitems_return, &bytes_after_return, &prop_return) == Success) { + + if (actual_type_return != None && actual_format_return != 0) { + // Clients with the _XEMBED_INFO property are XEMBED clients. + clientIsXEmbed = true; + + long *p = (long *)prop_return; + if (nitems_return >= 2) + clientversion = (unsigned int)p[0]; + } + + XFree(prop_return); + } + + // Store client window's original size and placement. + Window root; + int x_return, y_return; + unsigned int width_return, height_return, border_width_return, depth_return; + XGetGeometry(q->x11Info().display(), client, &root, &x_return, &y_return, + &width_return, &height_return, &border_width_return, &depth_return); + clientOriginalRect.setCoords(x_return, y_return, + x_return + width_return - 1, + y_return + height_return - 1); + + // Ask the client for its minimum size. + XSizeHints size; + long msize; + if (XGetWMNormalHints(q->x11Info().display(), client, &size, &msize) && (size.flags & PMinSize)) { + wmMinimumSizeHint = QSize(size.min_width, size.min_height); + q->updateGeometry(); + } + + // The container should set the data2 field to the lowest of its + // supported version number and that of the client (from + // _XEMBED_INFO property). + unsigned int minversion = version > clientversion ? clientversion : version; + sendXEmbedMessage(client, q->x11Info().display(), XEMBED_EMBEDDED_NOTIFY, q->internalWinId(), minversion); + XMapWindow(q->x11Info().display(), client); + + // Resize it, but no smaller than its minimum size hint. + XResizeWindow(q->x11Info().display(), + client, + qMax(q->width(), wmMinimumSizeHint.width()), + qMax(q->height(), wmMinimumSizeHint.height())); + q->update(); + + // Not mentioned in the protocol is that if the container + // is already active, the client must be activated to work + // properly. + if (q->window()->isActiveWindow()) + sendXEmbedMessage(client, q->x11Info().display(), XEMBED_WINDOW_ACTIVATE); + + // Also, if the container already has focus, then it must + // send a focus in message to its new client; otherwise we ask + // it to remove focus. + if (q->focusWidget() == q && q->hasFocus()) + sendXEmbedMessage(client, q->x11Info().display(), XEMBED_FOCUS_IN, XEMBED_FOCUS_FIRST); + else + sendXEmbedMessage(client, q->x11Info().display(), XEMBED_FOCUS_OUT); + + if (!clientIsXEmbed) { + checkGrab(); + if (q->hasFocus()) { + XSetInputFocus(q->x11Info().display(), client, XRevertToParent, x11Time()); + } + } else { + if (!isEmbedded()) + moveInputToProxy(); + } + + emit q->clientIsEmbedded(); +} + +/*! \internal + + Moves X11 keyboard input focus to the focusProxy, unless the focus + is there already. When X11 keyboard input focus is on the + focusProxy, which is a child of the container and a sibling of the + client, X11 keypresses and keyreleases will always go to the proxy + and not to the client. +*/ +void QX11EmbedContainerPrivate::moveInputToProxy() +{ + Q_Q(QX11EmbedContainer); + // Following Owen Taylor's advice from the XEmbed specification to + // always use CurrentTime when no explicit user action is involved. + XSetInputFocus(q->x11Info().display(), focusProxy->internalWinId(), XRevertToParent, CurrentTime); +} + +/*! \internal + + Ask the window manager to give us a default minimum size. +*/ +QSize QX11EmbedContainer::minimumSizeHint() const +{ + Q_D(const QX11EmbedContainer); + if (!d->client || !d->wmMinimumSizeHint.isValid()) + return QWidget::minimumSizeHint(); + return d->wmMinimumSizeHint; +} + +/*! \internal + +*/ +void QX11EmbedContainerPrivate::checkGrab() +{ + Q_Q(QX11EmbedContainer); + if (!clientIsXEmbed && q->isActiveWindow() && !q->hasFocus()) { + if (!xgrab) { + XGrabButton(q->x11Info().display(), AnyButton, AnyModifier, q->internalWinId(), + true, ButtonPressMask, GrabModeSync, GrabModeAsync, + None, None); + } + xgrab = true; + } else { + if (xgrab) + XUngrabButton(q->x11Info().display(), AnyButton, AnyModifier, q->internalWinId()); + xgrab = false; + } +} + +/*! + Detaches the client from the embedder. The client will appear as a + standalone window on the desktop. +*/ +void QX11EmbedContainer::discardClient() +{ + Q_D(QX11EmbedContainer); + if (d->client) { + XResizeWindow(x11Info().display(), d->client, d->clientOriginalRect.width(), + d->clientOriginalRect.height()); + + d->rejectClient(d->client); + } +} + +QT_END_NAMESPACE