diff --git a/plugins/vst_base/CMakeLists.txt b/plugins/vst_base/CMakeLists.txt index a9a8088411b..0f5db8f5931 100644 --- a/plugins/vst_base/CMakeLists.txt +++ b/plugins/vst_base/CMakeLists.txt @@ -28,6 +28,7 @@ SET(REMOTE_VST_PLUGIN_FILEPATH "RemoteVstPlugin" CACHE STRING "Relative file pat ADD_DEFINITIONS(-DREMOTE_VST_PLUGIN_FILEPATH="${REMOTE_VST_PLUGIN_FILEPATH}") BUILD_PLUGIN(vstbase vst_base.cpp VstPlugin.cpp VstPlugin.h communication.h MOCFILES VstPlugin.h) +TARGET_LINK_LIBRARIES(vstbase x11embedcontainer) IF(LMMS_BUILD_LINUX AND NOT WANT_VST_NOWINE) diff --git a/plugins/vst_base/VstPlugin.cpp b/plugins/vst_base/VstPlugin.cpp index 22c9f7c20e6..010774d8574 100644 --- a/plugins/vst_base/VstPlugin.cpp +++ b/plugins/vst_base/VstPlugin.cpp @@ -32,10 +32,7 @@ #include #include #ifdef LMMS_BUILD_LINUX -#if QT_VERSION < 0x050000 -#include -#include -#endif +#include "X11EmbedContainer.h" #else #include #endif @@ -244,25 +241,21 @@ void VstPlugin::showEditor( QWidget * _parent, bool isEffect ) sw->setAttribute( Qt::WA_TranslucentBackground ); sw->setWindowFlags( Qt::FramelessWindowHint ); sw->setWidget( m_pluginWidget ); -#if QT_VERSION < 0x050000 QX11EmbedContainer * xe = new QX11EmbedContainer( sw ); xe->embedClient( m_pluginWindowID ); xe->setFixedSize( m_pluginGeometry ); xe->show(); -#endif } else { sw->setWindowFlags( Qt::WindowCloseButtonHint ); sw->setWidget( m_pluginWidget ); -#if QT_VERSION < 0x050000 QX11EmbedContainer * xe = new QX11EmbedContainer( sw ); xe->embedClient( m_pluginWindowID ); xe->setFixedSize( m_pluginGeometry ); xe->move( 4, 24 ); xe->show(); -#endif } } diff --git a/plugins/vst_base/VstPlugin.h b/plugins/vst_base/VstPlugin.h index 1b6d62ff715..6d2317c85bd 100644 --- a/plugins/vst_base/VstPlugin.h +++ b/plugins/vst_base/VstPlugin.h @@ -130,7 +130,7 @@ public slots: QString m_plugin; QPointer m_pluginWidget; - int m_pluginWindowID; + WId m_pluginWindowID; QSize m_pluginGeometry; bool m_badDllFormat; diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 5b4050bca70..58fb8e2dddd 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -91,6 +91,25 @@ SET(LMMS_SRCS PARENT_SCOPE ) +IF(QT5) + add_library(x11embedcontainer STATIC + X11EmbedContainer + ) + FIND_PACKAGE(Qt5Core COMPONENTS Private) + FIND_PACKAGE(Qt5Widgets COMPONENTS Private) + FIND_PACKAGE(Qt5X11Extras REQUIRED) + + include_directories(${Qt5Core_PRIVATE_INCLUDE_DIRS}) + include_directories(${Qt5Widgets_PRIVATE_INCLUDE_DIRS}) + + + target_link_Libraries(x11embedcontainer + xcb xcb-util X11-xcb Qt5::X11Extras + ) +ELSE() + add_library(x11embedcontainer STATIC /dev/null) +ENDIF() + set(LMMS_UIS ${LMMS_UIS} gui/dialogs/about_dialog.ui diff --git a/src/gui/X11EmbedContainer.cpp b/src/gui/X11EmbedContainer.cpp new file mode 100644 index 00000000000..f34ddb38a55 --- /dev/null +++ b/src/gui/X11EmbedContainer.cpp @@ -0,0 +1,1175 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Copyright (C) 2017 Lukas W +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** 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 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "X11EmbedContainer.h" + +#include +#include +#include +#include +#include +#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 +#include +#include + +#ifndef XK_ISO_Left_Tab +#define XK_ISO_Left_Tab 0xFE20 +#endif + +//#define QX11EMBED_DEBUG +#ifdef QX11EMBED_DEBUG +#include +#endif + +#undef KeyPress +#undef KeyRelease +#undef FocusIn +#undef FocusOut + +static const int XRevertToParent = RevertToParent; +#undef RevertToParent + +QT_BEGIN_NAMESPACE + +/*! + \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 +*/ + + +enum ATOM_ID : int { + _XEMBED + ,_XEMBED_INFO + ,WM_PROTOCOLS + ,WM_DELETE_WINDOW + ,WM_STATE +}; + +static const std::vector> atom_list({ + {_XEMBED, "_XEMBED"}, + {_XEMBED, "_XEMBED_INFO"}, + {WM_PROTOCOLS, "WM_PROTOCOLS"}, + {WM_DELETE_WINDOW, "WM_DELETE_WINDOW"}, + {WM_STATE, "WM_STATE"}, +}); + +static QMap atoms; +static QMutex atoms_lock; + +void initAtoms() +{ + atoms_lock.lock(); + std::vector cookies; + cookies.reserve(atom_list.size()); + + for (const auto& pair : atom_list) + { + cookies.push_back(xcb_intern_atom(QX11Info::connection(), false, pair.second.length(), pair.second.data())); + } + + for (const auto& pair : atom_list) + { + auto cookie = cookies.back(); + + auto reply = xcb_intern_atom_reply(QX11Info::connection(), cookie, nullptr); + atoms[pair.first] = reply->atom; + free(reply); + + cookies.pop_back(); + } + atoms_lock.unlock(); +} + +xcb_atom_t ATOM(int atomID) +{ + return atoms.value(atomID); +} + + +// 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 *, xcb_generic_error_t *) +{ + return 0; +}*/ + +// Returns the X11 timestamp. Maintained mainly by qapplication +// internals, but also updated by the XEmbed widgets. +static xcb_timestamp_t x11Time() +{ + return QX11Info::getTimestamp(); +} + +// 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, long message, + long detail = 0, long data1 = 0, long data2 = 0) +{ + auto display = QX11Info::display(); + + XClientMessageEvent c; + memset(&c, 0, sizeof(c)); + c.type = ClientMessage; + + c.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 xcb_key_press_event_t lastKeyEvent; + +// 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. +class X11EventFilter : public QAbstractNativeEventFilter +{ +public: + bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) + { + if (eventType != "xcb_generic_event_t") { + return false; + } + + xcb_generic_event_t *event = reinterpret_cast(message); + if (event->response_type == XCB_KEY_PRESS || event->response_type == XCB_KEY_RELEASE) { + lastKeyEvent = *reinterpret_cast(message); + } + + return false; + } +} static x11EventFilter; + +// +struct functorData +{ + xcb_window_t id, rootWindow; + bool clearedWmState; + bool reparentedToRoot; +}; + + +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) +{ + initAtoms(); + 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->createWinId(); + d->focusProxy->winId(); + 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. + QCoreApplication::instance()->installNativeEventFilter(&x11EventFilter); + + XSelectInput(QX11Info::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(QX11Info::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(QX11Info::display(), d->client); + XReparentWindow(QX11Info::display(), d->client, QX11Info::appRootWindow(QX11Info::appScreen()), 0, 0); + } + + if (d->xgrab) + XUngrabButton(QX11Info::display(), AnyButton, AnyModifier, internalWinId()); +} + + +QX11EmbedContainer::Error QX11EmbedContainer::error() const { + return d_func()->lastError; +} + +bool QX11EmbedContainer::nativeEvent(const QByteArray &eventType, void *message, long *result) +{ + if (eventType == "xcb_generic_event_t") { + return x11Event(message, result); + } else { + return false; + } +} + + +/*! \reimp +*/ +void QX11EmbedContainer::paintEvent(QPaintEvent *) +{ +} + +/*! \internal + + Returns whether 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 q->window()->effectiveWinId(); + //TODO + //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(); + xcb_window_t rootReturn; + xcb_window_t parentReturn; + do { + auto cookie = xcb_query_tree(QX11Info::connection(), thisId); + xcb_generic_error_t* error = nullptr; + auto reply = xcb_query_tree_reply(QX11Info::connection(), cookie, &error); + + if (error) { + d->emitError(InvalidWindowID); + return; + } + + rootReturn = reply->root; + parentReturn = reply->parent; + + thisId = parentReturn; + if (id == thisId) { + d->emitError(InvalidWindowID); + return; + } + } while (thisId != rootReturn); + + switch (XReparentWindow(QX11Info::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.event = d->client; + xcb_send_event(QX11Info::connection(), false, d->client, KeyPressMask, (char*) &lastKeyEvent); + return true; + } + break; + case QEvent::KeyRelease: + // Forward any keyreleases to our client. + if (o == this && d->client) { + lastKeyEvent.event = d->client; + xcb_send_event(QX11Info::connection(), false, d->client, KeyReleaseMask, (char*) &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, XEMBED_WINDOW_ACTIVATE); + } else { + d->checkGrab(); + if (hasFocus()) + XSetInputFocus(QX11Info::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, 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, XEMBED_FOCUS_IN, XEMBED_FOCUS_FIRST); + break; + case Qt::BacktabFocusReason: + sendXEmbedMessage(d->client, XEMBED_FOCUS_IN, XEMBED_FOCUS_LAST); + break; + default: + sendXEmbedMessage(d->client, XEMBED_FOCUS_IN, XEMBED_FOCUS_CURRENT); + break; + } + } else { + d->checkGrab(); + XSetInputFocus(QX11Info::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, 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(QX11Info::display(), d->client); + XReparentWindow(QX11Info::display(), d->client, QX11Info::appRootWindow(QX11Info::appScreen()), 0, 0); + XSync(QX11Info::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(QX11Info::display(), d->client, false, NoEventMask, &ev); + + XFlush(QX11Info::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(void *e, long* result) +{ + xcb_generic_event_t* event = reinterpret_cast(e); + Q_D(QX11EmbedContainer); + + switch (event->response_type & ~0x80) { + case XCB_CREATE_NOTIFY: + // The client created an embedded window. + if (d->client) + d->rejectClient(reinterpret_cast(event)->window); + else + d->acceptClient(reinterpret_cast(event)->window); + break; + case XCB_DESTROY_NOTIFY: + if (reinterpret_cast(event)->window == d->client) { + // The client died. + d->client = 0; + d->clientIsXEmbed = false; + d->wmMinimumSizeHint = QSize(); + updateGeometry(); + update(); + setEnabled(false); + emit clientClosed(); + } + break; + case XCB_REPARENT_NOTIFY: + // The client sends us this if it reparents itself out of our + // widget. + { + auto* event = reinterpret_cast(e); + if (event->window == d->client && event->parent != internalWinId()) { + d->client = 0; + d->clientIsXEmbed = false; + d->wmMinimumSizeHint = QSize(); + updateGeometry(); + update(); + setEnabled(false); + emit clientClosed(); + } else if (event->parent == internalWinId()) { + // The client reparented itself into this window. + if (d->client) + d->rejectClient(event->window); + else + d->acceptClient(event->window); + } + break; + } + case XCB_CLIENT_MESSAGE: { + auto* event = reinterpret_cast(e); + if (event->type == ATOM(_XEMBED)) { + // Ignore XEMBED messages not to ourselves + if (event->window != internalWinId()) + break; + + // Receiving an XEmbed message means the client + // is an XEmbed client. + d->clientIsXEmbed = true; + + Time msgtime = (Time) event->data.data32[0]; + //TODO + //if (msgtime > X11->time) + //X11->time = msgtime; + + switch (event->data.data32[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(), 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, 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 XCB_BUTTON_PRESS: + if (!d->clientIsXEmbed) { + setFocus(Qt::MouseFocusReason); + XAllowEvents(QX11Info::display(), ReplayPointer, CurrentTime); + return true; + } + break; + case XCB_BUTTON_RELEASE: + if (!d->clientIsXEmbed) + XAllowEvents(QX11Info::display(), SyncPointer, CurrentTime); + break; + default: + break; + } + + return QWidget::nativeEvent("xcb_generic_event_t", e, result); +} + +/*! \internal + + Whenever the container is resized, we need to resize our client. +*/ +void QX11EmbedContainer::resizeEvent(QResizeEvent *) +{ + Q_D(QX11EmbedContainer); + if (d->client) + XResizeWindow(QX11Info::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(QX11Info::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(QX11Info::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(QX11Info::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(QX11Info::display(), client); + XReparentWindow(QX11Info::display(), window, QX11Info::appRootWindow(QX11Info::appScreen()), 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(); + //TODO + //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(QX11Info::display(), client); + + // XEmbed clients have an _XEMBED_INFO property in which we can + // fetch the version + if (XGetWindowProperty(QX11Info::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(QX11Info::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(QX11Info::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, XEMBED_EMBEDDED_NOTIFY, q->internalWinId(), minversion); + XMapWindow(QX11Info::display(), client); + + // Resize it, but no smaller than its minimum size hint. + XResizeWindow(QX11Info::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, 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, XEMBED_FOCUS_IN, XEMBED_FOCUS_FIRST); + else + sendXEmbedMessage(client, XEMBED_FOCUS_OUT); + + if (!clientIsXEmbed) { + checkGrab(); + if (q->hasFocus()) { + XSetInputFocus(QX11Info::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(QX11Info::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(QX11Info::display(), AnyButton, AnyModifier, q->internalWinId(), + true, ButtonPressMask, GrabModeSync, GrabModeAsync, + None, None); + } + xgrab = true; + } else { + if (xgrab) + XUngrabButton(QX11Info::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(QX11Info::display(), d->client, d->clientOriginalRect.width(), + d->clientOriginalRect.height()); + + d->rejectClient(d->client); + } +} + +QT_END_NAMESPACE diff --git a/src/gui/X11EmbedContainer.h b/src/gui/X11EmbedContainer.h new file mode 100644 index 00000000000..8e261098f30 --- /dev/null +++ b/src/gui/X11EmbedContainer.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** 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 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef X11EMBEDCONTAINER_H +#define X11EMBEDCONTAINER_H + +#include + +#if QT_VERSION < 0x050000 +#include +#else + +#include + +class QX11EmbedContainerPrivate; +class Q_GUI_EXPORT QX11EmbedContainer : public QWidget +{ + Q_OBJECT +public: + QX11EmbedContainer(QWidget *parent = 0); + ~QX11EmbedContainer(); + + void embedClient(WId id); + void discardClient(); + + WId clientWinId() const; + + QSize minimumSizeHint() const; + + enum Error { + Unknown, + Internal, + InvalidWindowID + }; + Error error() const; + +Q_SIGNALS: + void clientIsEmbedded(); + void clientClosed(); + void error(QX11EmbedContainer::Error); + +protected: + //bool x11Event(XEvent *); + bool nativeEvent(const QByteArray &eventType, void *message, long *result); + bool eventFilter(QObject *, QEvent *) override; + void paintEvent(QPaintEvent *e) override; + void resizeEvent(QResizeEvent *); + void showEvent(QShowEvent *); + void hideEvent(QHideEvent *); + bool event(QEvent *); + +private: + Q_DECLARE_PRIVATE(QX11EmbedContainer) + Q_DISABLE_COPY(QX11EmbedContainer) + bool x11Event(void *e, long *result); +}; + +#endif // X11EMBEDCONTAINER_H + +#endif