From e661d26f42dbf69a5e525a288b5a766568fbd662 Mon Sep 17 00:00:00 2001 From: Lukas W Date: Sat, 1 Apr 2017 15:36:06 +0200 Subject: [PATCH 01/12] Add X11EmbedContainer Qt5 port --- plugins/vst_base/CMakeLists.txt | 1 + plugins/vst_base/VstPlugin.cpp | 9 +- plugins/vst_base/VstPlugin.h | 2 +- src/gui/CMakeLists.txt | 19 + src/gui/X11EmbedContainer.cpp | 1175 +++++++++++++++++++++++++++++++ src/gui/X11EmbedContainer.h | 98 +++ 6 files changed, 1295 insertions(+), 9 deletions(-) create mode 100644 src/gui/X11EmbedContainer.cpp create mode 100644 src/gui/X11EmbedContainer.h diff --git a/plugins/vst_base/CMakeLists.txt b/plugins/vst_base/CMakeLists.txt index a9a808841..0f5db8f59 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 22c9f7c20..010774d85 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 1b6d62ff7..6d2317c85 100644 --- a/plugins/vst_base/VstPlugin.h +++ b/plugins/vst_base/VstPlugin.h @@ -130,7 +130,7 @@ private: 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 5b4050bca..58fb8e2dd 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 000000000..f34ddb38a --- /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 000000000..8e261098f --- /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 From adef05fb71ddf595138e3eee8dddf0514072b8cf Mon Sep 17 00:00:00 2001 From: Lukas W Date: Sat, 8 Apr 2017 17:11:56 +0200 Subject: [PATCH 02/12] X11EmbedContainer: Fix XEMBED protocol implementation --- include/RemotePlugin.h | 16 +- plugins/vst_base/RemoteVstPlugin.cpp | 8 +- plugins/vst_base/VstPlugin.cpp | 24 +- src/core/RemotePlugin.cpp | 14 ++ src/gui/SubWindow.cpp | 5 +- src/gui/X11EmbedContainer.cpp | 330 +++++++++------------------ src/gui/X11EmbedContainer.h | 10 +- 7 files changed, 153 insertions(+), 254 deletions(-) diff --git a/include/RemotePlugin.h b/include/RemotePlugin.h index d016c5acd..977f041a5 100644 --- a/include/RemotePlugin.h +++ b/include/RemotePlugin.h @@ -787,19 +787,6 @@ public: unlock(); } - void showUI() - { - lock(); - sendMessage( IdShowUI ); - unlock(); - } - - void hideUI() - { - lock(); - sendMessage( IdHideUI ); - unlock(); - } inline bool failed() const { @@ -816,6 +803,9 @@ public: m_commMutex.unlock(); } +public slots: + void showUI(); + void hideUI(); protected: inline void setSplittedChannels( bool _on ) diff --git a/plugins/vst_base/RemoteVstPlugin.cpp b/plugins/vst_base/RemoteVstPlugin.cpp index 03e160a8d..c0d4b9b92 100644 --- a/plugins/vst_base/RemoteVstPlugin.cpp +++ b/plugins/vst_base/RemoteVstPlugin.cpp @@ -556,6 +556,11 @@ bool RemoteVstPlugin::processMessage( const message & _m ) break; } + case IdShowUI: + ShowWindow( m_window, SW_SHOWNORMAL ); + UpdateWindow( m_window ); + break; + default: return RemotePluginClient::processMessage( _m ); } @@ -687,9 +692,6 @@ void RemoteVstPlugin::initEditor() SWP_NOMOVE | SWP_NOZORDER ); pluginDispatch( effEditTop ); - ShowWindow( m_window, SW_SHOWNORMAL ); - UpdateWindow( m_window ); - #ifdef LMMS_BUILD_LINUX m_windowID = (intptr_t) GetProp( m_window, "__wine_x11_whole_window" ); #endif diff --git a/plugins/vst_base/VstPlugin.cpp b/plugins/vst_base/VstPlugin.cpp index 010774d85..83d82e75e 100644 --- a/plugins/vst_base/VstPlugin.cpp +++ b/plugins/vst_base/VstPlugin.cpp @@ -229,35 +229,31 @@ void VstPlugin::showEditor( QWidget * _parent, bool isEffect ) return; } - m_pluginWidget = new QWidget( _parent ); + QX11EmbedContainer * xe = new QX11EmbedContainer; + m_pluginWidget = xe; m_pluginWidget->setFixedSize( m_pluginGeometry ); m_pluginWidget->setWindowTitle( name() ); + + connect(xe, SIGNAL(clientIsEmbedded()), this, SLOT(showUI())); + if( _parent == NULL ) { vstSubWin * sw = new vstSubWin( gui->mainWindow()->workspace() ); + sw->setWidget( m_pluginWidget ); + if( isEffect ) { sw->setAttribute( Qt::WA_TranslucentBackground ); sw->setWindowFlags( Qt::FramelessWindowHint ); - sw->setWidget( m_pluginWidget ); - QX11EmbedContainer * xe = new QX11EmbedContainer( sw ); - xe->embedClient( m_pluginWindowID ); - xe->setFixedSize( m_pluginGeometry ); - xe->show(); - } + } else { sw->setWindowFlags( Qt::WindowCloseButtonHint ); - sw->setWidget( m_pluginWidget ); - - QX11EmbedContainer * xe = new QX11EmbedContainer( sw ); - xe->embedClient( m_pluginWindowID ); - xe->setFixedSize( m_pluginGeometry ); - xe->move( 4, 24 ); - xe->show(); } } + xe->embedClient( m_pluginWindowID ); + xe->setFixedSize( m_pluginGeometry ); #endif diff --git a/src/core/RemotePlugin.cpp b/src/core/RemotePlugin.cpp index bb7e39c6a..064d77b2c 100644 --- a/src/core/RemotePlugin.cpp +++ b/src/core/RemotePlugin.cpp @@ -392,6 +392,20 @@ void RemotePlugin::processMidiEvent( const MidiEvent & _e, unlock(); } +void RemotePlugin::showUI() +{ + lock(); + sendMessage( IdShowUI ); + unlock(); +} + +void RemotePlugin::hideUI() +{ + lock(); + sendMessage( IdHideUI ); + unlock(); +} + diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index e92c4cbcb..284e116d8 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -97,7 +97,10 @@ void SubWindow::paintEvent( QPaintEvent * ) { QPainter p( this ); QRect rect( 0, 0, width(), m_titleBarHeight ); - bool isActive = SubWindow::mdiArea()->activeSubWindow() == this; + + bool isActive = mdiArea() + ? mdiArea()->activeSubWindow() == this + : false; p.fillRect( rect, isActive ? activeColor() : p.pen().brush() ); diff --git a/src/gui/X11EmbedContainer.cpp b/src/gui/X11EmbedContainer.cpp index f34ddb38a..f5a22eb87 100644 --- a/src/gui/X11EmbedContainer.cpp +++ b/src/gui/X11EmbedContainer.cpp @@ -59,8 +59,15 @@ #include #include +#include + +#include +#include +#include //#include -//#include + +#include +#include #define XK_MISCELLANY #define XK_LATIN1 @@ -92,139 +99,6 @@ static const int XRevertToParent = 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 @@ -235,7 +109,7 @@ enum ATOM_ID : int { static const std::vector> atom_list({ {_XEMBED, "_XEMBED"}, - {_XEMBED, "_XEMBED_INFO"}, + {_XEMBED_INFO, "_XEMBED_INFO"}, {WM_PROTOCOLS, "WM_PROTOCOLS"}, {WM_DELETE_WINDOW, "WM_DELETE_WINDOW"}, {WM_STATE, "WM_STATE"}, @@ -246,26 +120,31 @@ static QMutex atoms_lock; void initAtoms() { - atoms_lock.lock(); - std::vector cookies; - cookies.reserve(atom_list.size()); + QMutexLocker locker(&atoms_lock); Q_UNUSED(locker); + + std::queue cookies; for (const auto& pair : atom_list) { - cookies.push_back(xcb_intern_atom(QX11Info::connection(), false, pair.second.length(), pair.second.data())); + cookies.push(xcb_intern_atom(QX11Info::connection(), false, pair.second.length(), pair.second.data())); } for (const auto& pair : atom_list) { - auto cookie = cookies.back(); + auto cookie = cookies.front(); + cookies.pop(); auto reply = xcb_intern_atom_reply(QX11Info::connection(), cookie, nullptr); atoms[pair.first] = reply->atom; - free(reply); - cookies.pop_back(); + Q_ASSERT(pair.second == XGetAtomName(QX11Info::display(), reply->atom)); + +#ifdef QX11EMBED_DEBUG + qDebug() << "atom" << QString::fromStdString(pair.second) + << XGetAtomName(QX11Info::display(), reply->atom) << reply->atom; +#endif + free(reply); } - atoms_lock.unlock(); } xcb_atom_t ATOM(int atomID) @@ -274,6 +153,33 @@ xcb_atom_t ATOM(int atomID) } +struct xembed_info +{ + uint32_t version; + uint32_t flags; +}; + +xembed_info* get_xembed_info(xcb_window_t window) +{ + auto cookie = xcb_get_property(QX11Info::connection(), 0, window, ATOM(_XEMBED_INFO), ATOM(_XEMBED_INFO), 0, 2); + if (auto reply = xcb_get_property_reply(QX11Info::connection(), cookie, nullptr)) { + auto val_len = xcb_get_property_value_length(reply); + if (val_len < 2) { +#ifdef QX11EMBED_DEBUG + qDebug() << "Client has malformed _XEMBED_INFO property, len is" << val_len; +#endif + free(reply); + return nullptr; + } + + void* result = malloc(sizeof(xembed_info)); + memcpy(result, xcb_get_property_value(reply), sizeof(xembed_info)); + return reinterpret_cast(result); + } + + return nullptr; +} + // 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 @@ -347,6 +253,7 @@ 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) @@ -356,8 +263,7 @@ static void sendXEmbedMessage(WId window, long message, XClientMessageEvent c; memset(&c, 0, sizeof(c)); c.type = ClientMessage; - - c.type = ATOM(_XEMBED); + c.message_type = ATOM(_XEMBED); c.format = 32; c.display = display; c.window = window; @@ -395,14 +301,6 @@ public: } } static x11EventFilter; -// -struct functorData -{ - xcb_window_t id, rootWindow; - bool clearedWmState; - bool reparentedToRoot; -}; - class QX11EmbedContainerPrivate : public QWidgetPrivate { @@ -424,6 +322,7 @@ public: void rejectClient(WId window); void checkGrab(); + void checkXembedInfo(); WId topLevelParentWinId() const; @@ -482,6 +381,7 @@ QX11EmbedContainer::QX11EmbedContainer(QWidget *parent) // Install X11 event filter. QCoreApplication::instance()->installNativeEventFilter(&x11EventFilter); + QCoreApplication::instance()->installNativeEventFilter(this); XSelectInput(QX11Info::display(), internalWinId(), KeyPressMask | KeyReleaseMask @@ -500,6 +400,7 @@ QX11EmbedContainer::QX11EmbedContainer(QWidget *parent) // 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(); @@ -529,7 +430,7 @@ QX11EmbedContainer::Error QX11EmbedContainer::error() const { return d_func()->lastError; } -bool QX11EmbedContainer::nativeEvent(const QByteArray &eventType, void *message, long *result) +bool QX11EmbedContainer::nativeEventFilter(const QByteArray &eventType, void *message, long *result) { if (eventType == "xcb_generic_event_t") { return x11Event(message, result); @@ -539,6 +440,7 @@ bool QX11EmbedContainer::nativeEvent(const QByteArray &eventType, void *message, } + /*! \reimp */ void QX11EmbedContainer::paintEvent(QPaintEvent *) @@ -629,6 +531,10 @@ void QX11EmbedContainer::embedClient(WId id) default: break; } + +#ifdef QX11EMBED_DEBUG + qDebug() << "reparented client" << id << "into" << winId(); +#endif } /*! \internal @@ -776,21 +682,27 @@ bool QX11EmbedContainer::eventFilter(QObject *o, QEvent *event) Handles X11 events for the container. */ -bool QX11EmbedContainer::x11Event(void *e, long* result) +bool QX11EmbedContainer::x11Event(void *message, long*) { - xcb_generic_event_t* event = reinterpret_cast(e); + xcb_generic_event_t* e = reinterpret_cast(message); Q_D(QX11EmbedContainer); - switch (event->response_type & ~0x80) { + switch (e->response_type & ~0x80) { case XCB_CREATE_NOTIFY: +#ifdef QX11EMBED_DEBUG + qDebug() << "client created" << reinterpret_cast(e)->window; +#endif // The client created an embedded window. if (d->client) - d->rejectClient(reinterpret_cast(event)->window); + d->rejectClient(reinterpret_cast(e)->window); else - d->acceptClient(reinterpret_cast(event)->window); + d->acceptClient(reinterpret_cast(e)->window); break; case XCB_DESTROY_NOTIFY: - if (reinterpret_cast(event)->window == d->client) { + if (reinterpret_cast(e)->window == d->client) { +#ifdef QX11EMBED_DEBUG + qDebug() << "client died"; +#endif // The client died. d->client = 0; d->clientIsXEmbed = false; @@ -834,8 +746,8 @@ bool QX11EmbedContainer::x11Event(void *e, long* result) // is an XEmbed client. d->clientIsXEmbed = true; - Time msgtime = (Time) event->data.data32[0]; - //TODO + //TODO: Port to Qt5, if needed + //Time msgtime = (Time) event->data.data32[0]; //if (msgtime > X11->time) //X11->time = msgtime; @@ -903,21 +815,50 @@ bool QX11EmbedContainer::x11Event(void *e, long* result) } break; case XCB_BUTTON_PRESS: - if (!d->clientIsXEmbed) { + { + auto event = reinterpret_cast(e); + if (event->child == d->client && !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; + case XCB_PROPERTY_NOTIFY: + { + auto event = reinterpret_cast(e); + + if (event->atom == ATOM(_XEMBED_INFO) && event->window == d->client) { + if (auto info = get_xembed_info(d->client)) { + if (info->flags & XEMBED_MAPPED) { +#ifdef QX11EMBED_DEBUG + qDebug() << "mapping client per _xembed_info"; +#endif + XMapWindow(QX11Info::display(), d->client); + XRaiseWindow(QX11Info::display(), d->client); + } else { +#ifdef QX11EMBED_DEBUG + qDebug() << "unmapping client per _xembed_info"; +#endif + XUnmapWindow(QX11Info::display(), d->client); + } + + free(info); + } + } + break; + } + case XCB_CONFIGURE_NOTIFY: + return true; default: break; } - return QWidget::nativeEvent("xcb_generic_event_t", e, result); + return false; } /*! \internal @@ -931,38 +872,6 @@ void QX11EmbedContainer::resizeEvent(QResizeEvent *) 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 */ @@ -1010,6 +919,8 @@ void QX11EmbedContainerPrivate::acceptClient(WId window) client = window; q->setEnabled(true); + XSelectInput(QX11Info::display(), client, PropertyChangeMask); + // This tells Qt that we wish to forward DnD messages to // our client. if (!extra) @@ -1018,12 +929,6 @@ void QX11EmbedContainerPrivate::acceptClient(WId window) //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 @@ -1034,20 +939,10 @@ void QX11EmbedContainerPrivate::acceptClient(WId window) // 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); + if (auto info = get_xembed_info(client)) { + clientIsXEmbed = true; + clientversion = info->version; + free(info); } // Store client window's original size and placement. @@ -1072,8 +967,7 @@ void QX11EmbedContainerPrivate::acceptClient(WId window) // 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); + sendXEmbedMessage(client, XEMBED_EMBEDDED_NOTIFY, 0, q->internalWinId(), minversion); // Resize it, but no smaller than its minimum size hint. XResizeWindow(QX11Info::display(), diff --git a/src/gui/X11EmbedContainer.h b/src/gui/X11EmbedContainer.h index 8e261098f..d91258c28 100644 --- a/src/gui/X11EmbedContainer.h +++ b/src/gui/X11EmbedContainer.h @@ -48,10 +48,11 @@ #include #else +#include #include class QX11EmbedContainerPrivate; -class Q_GUI_EXPORT QX11EmbedContainer : public QWidget +class Q_GUI_EXPORT QX11EmbedContainer : public QWidget, public QAbstractNativeEventFilter { Q_OBJECT public: @@ -72,19 +73,18 @@ public: }; Error error() const; +public: + bool nativeEventFilter(const QByteArray &eventType, void *message, long *result); + 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: From f097be6c404eacd2f118a7aa9a7b2d54504b27fe Mon Sep 17 00:00:00 2001 From: Lukas W Date: Sat, 8 Apr 2017 17:14:22 +0200 Subject: [PATCH 03/12] RemoteVst: Fix flickering when moving window --- plugins/vst_base/RemoteVstPlugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/vst_base/RemoteVstPlugin.cpp b/plugins/vst_base/RemoteVstPlugin.cpp index c0d4b9b92..15be00606 100644 --- a/plugins/vst_base/RemoteVstPlugin.cpp +++ b/plugins/vst_base/RemoteVstPlugin.cpp @@ -648,7 +648,7 @@ void RemoteVstPlugin::initEditor() wc.hInstance = hInst; wc.hIcon = LoadIcon( NULL, IDI_APPLICATION ); wc.hCursor = LoadCursor( NULL, IDC_ARROW ); - wc.hbrBackground = (HBRUSH) GetStockObject( BLACK_BRUSH ); + wc.hbrBackground = NULL; wc.lpszMenuName = NULL; wc.lpszClassName = "LVSL"; From 0e311ffd474d9ae23751dd3e004e5ff06212c20f Mon Sep 17 00:00:00 2001 From: Lukas W Date: Sat, 8 Apr 2017 23:14:23 +0200 Subject: [PATCH 04/12] X11Embed: Remove flag causing embedding to fail --- plugins/vst_base/VstPlugin.cpp | 6 +++--- src/gui/X11EmbedContainer.cpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/vst_base/VstPlugin.cpp b/plugins/vst_base/VstPlugin.cpp index 83d82e75e..514146c09 100644 --- a/plugins/vst_base/VstPlugin.cpp +++ b/plugins/vst_base/VstPlugin.cpp @@ -229,7 +229,9 @@ void VstPlugin::showEditor( QWidget * _parent, bool isEffect ) return; } - QX11EmbedContainer * xe = new QX11EmbedContainer; + vstSubWin * sw = new vstSubWin( gui->mainWindow()->workspace() ); + + QX11EmbedContainer * xe = new QX11EmbedContainer(sw); m_pluginWidget = xe; m_pluginWidget->setFixedSize( m_pluginGeometry ); m_pluginWidget->setWindowTitle( name() ); @@ -238,8 +240,6 @@ void VstPlugin::showEditor( QWidget * _parent, bool isEffect ) if( _parent == NULL ) { - vstSubWin * sw = new vstSubWin( - gui->mainWindow()->workspace() ); sw->setWidget( m_pluginWidget ); if( isEffect ) diff --git a/src/gui/X11EmbedContainer.cpp b/src/gui/X11EmbedContainer.cpp index f5a22eb87..99e0dc48d 100644 --- a/src/gui/X11EmbedContainer.cpp +++ b/src/gui/X11EmbedContainer.cpp @@ -356,8 +356,8 @@ QX11EmbedContainer::QX11EmbedContainer(QWidget *parent) Q_D(QX11EmbedContainer); //XSetErrorHandler(x11ErrorHandler); + //setAttribute(Qt::WA_DontCreateNativeAncestors); setAttribute(Qt::WA_NativeWindow); - setAttribute(Qt::WA_DontCreateNativeAncestors); createWinId(); setFocusPolicy(Qt::StrongFocus); @@ -369,8 +369,8 @@ QX11EmbedContainer::QX11EmbedContainer(QWidget *parent) // 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_DontCreateNativeAncestors); 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); From 8fdcc6ccac153c33c5cb6bc0e16bcc9a6a8b39dc Mon Sep 17 00:00:00 2001 From: Lukas W Date: Sat, 8 Apr 2017 23:38:45 +0200 Subject: [PATCH 05/12] Fix compile on Windows --- plugins/vst_base/VstPlugin.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/vst_base/VstPlugin.h b/plugins/vst_base/VstPlugin.h index 6d2317c85..1b6d62ff7 100644 --- a/plugins/vst_base/VstPlugin.h +++ b/plugins/vst_base/VstPlugin.h @@ -130,7 +130,7 @@ private: QString m_plugin; QPointer m_pluginWidget; - WId m_pluginWindowID; + int m_pluginWindowID; QSize m_pluginGeometry; bool m_badDllFormat; From 7ce60c247a0bf325482991566a035f5ba2229380 Mon Sep 17 00:00:00 2001 From: Lukas W Date: Sat, 8 Apr 2017 23:56:07 +0200 Subject: [PATCH 06/12] Travis: Add missing packages --- .travis/linux..install.sh | 6 ++++-- src/gui/CMakeLists.txt | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.travis/linux..install.sh b/.travis/linux..install.sh index f5988a83d..8174f9786 100644 --- a/.travis/linux..install.sh +++ b/.travis/linux..install.sh @@ -2,11 +2,13 @@ PACKAGES="cmake libsndfile-dev fftw3-dev libvorbis-dev libogg-dev libmp3lame-dev libasound2-dev libjack-dev libsdl-dev libsamplerate0-dev libstk0-dev - libfluidsynth-dev portaudio19-dev wine-dev g++-multilib libfltk1.3-dev + libfluidsynth-dev portaudio19-dev g++-multilib libfltk1.3-dev libgig-dev libsoundio-dev" +VST_PACKAGES="wine-dev libqt5x11extras5-dev qtbase5-private-dev libxcb-util-dev libxcb-keysyms1-dev" + # Help with unmet dependencies -PACKAGES="$PACKAGES libjack0" +PACKAGES="$PACKAGES $VST_PACKAGES libjack0" if [ $QT5 ]; then PACKAGES="$PACKAGES qtbase5-dev qttools5-dev-tools qttools5-dev" diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 58fb8e2dd..3373945ed 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -91,7 +91,7 @@ SET(LMMS_SRCS PARENT_SCOPE ) -IF(QT5) +IF(QT5 AND LMMS_BUILD_LINUX) add_library(x11embedcontainer STATIC X11EmbedContainer ) From dc4a3875a3a76f519cd5c4225bfe1244b1ae4d31 Mon Sep 17 00:00:00 2001 From: Lukas W Date: Sat, 15 Jul 2017 22:18:49 +0200 Subject: [PATCH 07/12] X11EmbedContainer: Don't grab mouse in acceptClient, more debugging msgs --- src/gui/X11EmbedContainer.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/gui/X11EmbedContainer.cpp b/src/gui/X11EmbedContainer.cpp index 99e0dc48d..a14735cee 100644 --- a/src/gui/X11EmbedContainer.cpp +++ b/src/gui/X11EmbedContainer.cpp @@ -274,6 +274,9 @@ static void sendXEmbedMessage(WId window, long message, c.data.l[3] = data1; c.data.l[4] = data2; +#ifdef QX11EMBED_DEBUG + qDebug() << "Sending XEMBED message" << message << detail << data1 << data2; +#endif XSendEvent(display, window, false, NoEventMask, (XEvent *) &c); } @@ -990,6 +993,9 @@ void QX11EmbedContainerPrivate::acceptClient(WId window) else sendXEmbedMessage(client, XEMBED_FOCUS_OUT); + // This is from the original Qt implementation. Disabled for now because it appears + // to cause the mouse being grabbed permanently in some environments + /* if (!clientIsXEmbed) { checkGrab(); if (q->hasFocus()) { @@ -999,6 +1005,7 @@ void QX11EmbedContainerPrivate::acceptClient(WId window) if (!isEmbedded()) moveInputToProxy(); } + */ emit q->clientIsEmbedded(); } From 000fe2da7be575aaa601e50f9a71e203b99e358e Mon Sep 17 00:00:00 2001 From: Lukas W Date: Sun, 16 Jul 2017 10:33:58 +0200 Subject: [PATCH 08/12] Travis: Fix xcb package name --- .travis/linux..install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis/linux..install.sh b/.travis/linux..install.sh index 8174f9786..cf446fb8c 100644 --- a/.travis/linux..install.sh +++ b/.travis/linux..install.sh @@ -5,7 +5,7 @@ PACKAGES="cmake libsndfile-dev fftw3-dev libvorbis-dev libogg-dev libmp3lame-dev libfluidsynth-dev portaudio19-dev g++-multilib libfltk1.3-dev libgig-dev libsoundio-dev" -VST_PACKAGES="wine-dev libqt5x11extras5-dev qtbase5-private-dev libxcb-util-dev libxcb-keysyms1-dev" +VST_PACKAGES="wine-dev libqt5x11extras5-dev qtbase5-private-dev libxcb-util0-dev libxcb-keysyms1-dev" # Help with unmet dependencies PACKAGES="$PACKAGES $VST_PACKAGES libjack0" From d9626e9e3b74c6fbe49552fb7ee673c96d1b23cc Mon Sep 17 00:00:00 2001 From: Lukas W Date: Wed, 19 Jul 2017 22:52:51 +0200 Subject: [PATCH 09/12] Move X11EmbedContainer.h to include --- {src/gui => include}/X11EmbedContainer.h | 0 src/CMakeLists.txt | 2 +- src/gui/CMakeLists.txt | 9 ++++++--- 3 files changed, 7 insertions(+), 4 deletions(-) rename {src/gui => include}/X11EmbedContainer.h (100%) diff --git a/src/gui/X11EmbedContainer.h b/include/X11EmbedContainer.h similarity index 100% rename from src/gui/X11EmbedContainer.h rename to include/X11EmbedContainer.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 154ff4f85..8523fc44b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -132,7 +132,7 @@ IF(LMMS_BUILD_HAIKU) SET(EXTRA_LIBRARIES "-lnetwork") ENDIF() -SET(LMMS_REQUIRED_LIBS +SET(LMMS_REQUIRED_LIBS ${LMMS_REQUIRED_LIBS} ${CMAKE_THREAD_LIBS_INIT} ${QT_LIBRARIES} ${ASOUND_LIBRARY} diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 3373945ed..345679297 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -92,8 +92,10 @@ SET(LMMS_SRCS ) IF(QT5 AND LMMS_BUILD_LINUX) + INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/include/") add_library(x11embedcontainer STATIC - X11EmbedContainer + "${CMAKE_SOURCE_DIR}/src/gui/X11EmbedContainer.cpp" + "${CMAKE_SOURCE_DIR}/include/X11EmbedContainer.h" ) FIND_PACKAGE(Qt5Core COMPONENTS Private) FIND_PACKAGE(Qt5Widgets COMPONENTS Private) @@ -102,10 +104,11 @@ IF(QT5 AND LMMS_BUILD_LINUX) include_directories(${Qt5Core_PRIVATE_INCLUDE_DIRS}) include_directories(${Qt5Widgets_PRIVATE_INCLUDE_DIRS}) - target_link_Libraries(x11embedcontainer - xcb xcb-util X11-xcb Qt5::X11Extras + xcb xcb-util X11-xcb Qt5::X11Extras X11 ) + + SET(LMMS_REQUIRED_LIBS ${LMMS_REQUIRED_LIBS} x11embedcontainer PARENT_SCOPE) ELSE() add_library(x11embedcontainer STATIC /dev/null) ENDIF() From def2b7d5098ad7a75169ff5f86d38dd9c50b8224 Mon Sep 17 00:00:00 2001 From: Lukas W Date: Sat, 5 Aug 2017 12:33:59 +0200 Subject: [PATCH 10/12] Move X11EmbedContainer to submodule --- .gitmodules | 3 + include/X11EmbedContainer.h | 98 --- plugins/vst_base/CMakeLists.txt | 2 +- src/3rdparty/CMakeLists.txt | 4 + src/3rdparty/qt5-x11embed | 1 + src/CMakeLists.txt | 1 + src/gui/CMakeLists.txt | 22 - src/gui/X11EmbedContainer.cpp | 1076 ------------------------------- 8 files changed, 10 insertions(+), 1197 deletions(-) create mode 100644 .gitmodules delete mode 100644 include/X11EmbedContainer.h create mode 100644 src/3rdparty/CMakeLists.txt create mode 160000 src/3rdparty/qt5-x11embed delete mode 100644 src/gui/X11EmbedContainer.cpp diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..4ca030cf5 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "src/3rdparty/qt5-x11embed"] + path = src/3rdparty/qt5-x11embed + url = git@github.com:Lukas-W/qt5-x11embed.git diff --git a/include/X11EmbedContainer.h b/include/X11EmbedContainer.h deleted file mode 100644 index d91258c28..000000000 --- a/include/X11EmbedContainer.h +++ /dev/null @@ -1,98 +0,0 @@ -/**************************************************************************** -** -** 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 -#include - -class QX11EmbedContainerPrivate; -class Q_GUI_EXPORT QX11EmbedContainer : public QWidget, public QAbstractNativeEventFilter -{ - 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; - -public: - bool nativeEventFilter(const QByteArray &eventType, void *message, long *result); - -Q_SIGNALS: - void clientIsEmbedded(); - void clientClosed(); - void error(QX11EmbedContainer::Error); - -protected: - bool eventFilter(QObject *, QEvent *) override; - void paintEvent(QPaintEvent *e) override; - void resizeEvent(QResizeEvent *); - bool event(QEvent *); - -private: - Q_DECLARE_PRIVATE(QX11EmbedContainer) - Q_DISABLE_COPY(QX11EmbedContainer) - bool x11Event(void *e, long *result); -}; - -#endif // X11EMBEDCONTAINER_H - -#endif diff --git a/plugins/vst_base/CMakeLists.txt b/plugins/vst_base/CMakeLists.txt index 0f5db8f59..19915faa6 100644 --- a/plugins/vst_base/CMakeLists.txt +++ b/plugins/vst_base/CMakeLists.txt @@ -28,7 +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) +TARGET_LINK_LIBRARIES(vstbase qx11embedcontainer) IF(LMMS_BUILD_LINUX AND NOT WANT_VST_NOWINE) diff --git a/src/3rdparty/CMakeLists.txt b/src/3rdparty/CMakeLists.txt new file mode 100644 index 000000000..644fccbb1 --- /dev/null +++ b/src/3rdparty/CMakeLists.txt @@ -0,0 +1,4 @@ +include(ExternalProject) + +set(BUILD_SHARED_LIBS OFF) +add_subdirectory(qt5-x11embed) diff --git a/src/3rdparty/qt5-x11embed b/src/3rdparty/qt5-x11embed new file mode 160000 index 000000000..dad35c07c --- /dev/null +++ b/src/3rdparty/qt5-x11embed @@ -0,0 +1 @@ +Subproject commit dad35c07cdb704f3e9306e6301f7eb4c098552a2 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8523fc44b..82dd14d7a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -14,6 +14,7 @@ IF(LMMS_BUILD_APPLE) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") ENDIF() +ADD_SUBDIRECTORY(3rdparty) ADD_SUBDIRECTORY(core) ADD_SUBDIRECTORY(gui) ADD_SUBDIRECTORY(tracks) diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 345679297..5b4050bca 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -91,28 +91,6 @@ SET(LMMS_SRCS PARENT_SCOPE ) -IF(QT5 AND LMMS_BUILD_LINUX) - INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/include/") - add_library(x11embedcontainer STATIC - "${CMAKE_SOURCE_DIR}/src/gui/X11EmbedContainer.cpp" - "${CMAKE_SOURCE_DIR}/include/X11EmbedContainer.h" - ) - 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 X11 - ) - - SET(LMMS_REQUIRED_LIBS ${LMMS_REQUIRED_LIBS} x11embedcontainer PARENT_SCOPE) -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 deleted file mode 100644 index a14735cee..000000000 --- a/src/gui/X11EmbedContainer.cpp +++ /dev/null @@ -1,1076 +0,0 @@ -/**************************************************************************** -** -** 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 -#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 - -enum ATOM_ID : int { - _XEMBED - ,_XEMBED_INFO - ,WM_PROTOCOLS - ,WM_DELETE_WINDOW - ,WM_STATE -}; - -static const std::vector> atom_list({ - {_XEMBED, "_XEMBED"}, - {_XEMBED_INFO, "_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() -{ - QMutexLocker locker(&atoms_lock); Q_UNUSED(locker); - - std::queue cookies; - - for (const auto& pair : atom_list) - { - cookies.push(xcb_intern_atom(QX11Info::connection(), false, pair.second.length(), pair.second.data())); - } - - for (const auto& pair : atom_list) - { - auto cookie = cookies.front(); - cookies.pop(); - - auto reply = xcb_intern_atom_reply(QX11Info::connection(), cookie, nullptr); - atoms[pair.first] = reply->atom; - - Q_ASSERT(pair.second == XGetAtomName(QX11Info::display(), reply->atom)); - -#ifdef QX11EMBED_DEBUG - qDebug() << "atom" << QString::fromStdString(pair.second) - << XGetAtomName(QX11Info::display(), reply->atom) << reply->atom; -#endif - free(reply); - } -} - -xcb_atom_t ATOM(int atomID) -{ - return atoms.value(atomID); -} - - -struct xembed_info -{ - uint32_t version; - uint32_t flags; -}; - -xembed_info* get_xembed_info(xcb_window_t window) -{ - auto cookie = xcb_get_property(QX11Info::connection(), 0, window, ATOM(_XEMBED_INFO), ATOM(_XEMBED_INFO), 0, 2); - if (auto reply = xcb_get_property_reply(QX11Info::connection(), cookie, nullptr)) { - auto val_len = xcb_get_property_value_length(reply); - if (val_len < 2) { -#ifdef QX11EMBED_DEBUG - qDebug() << "Client has malformed _XEMBED_INFO property, len is" << val_len; -#endif - free(reply); - return nullptr; - } - - void* result = malloc(sizeof(xembed_info)); - memcpy(result, xcb_get_property_value(reply), sizeof(xembed_info)); - return reinterpret_cast(result); - } - - return nullptr; -} - -// 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.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; - -#ifdef QX11EMBED_DEBUG - qDebug() << "Sending XEMBED message" << message << detail << data1 << data2; -#endif - 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; - - -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(); - void checkXembedInfo(); - - 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_DontCreateNativeAncestors); - setAttribute(Qt::WA_NativeWindow); - 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_DontCreateNativeAncestors); - d->focusProxy->setAttribute(Qt::WA_NativeWindow); - 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); - QCoreApplication::instance()->installNativeEventFilter(this); - - 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::nativeEventFilter(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; - } - -#ifdef QX11EMBED_DEBUG - qDebug() << "reparented client" << id << "into" << winId(); -#endif -} - -/*! \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 *message, long*) -{ - xcb_generic_event_t* e = reinterpret_cast(message); - Q_D(QX11EmbedContainer); - - switch (e->response_type & ~0x80) { - case XCB_CREATE_NOTIFY: -#ifdef QX11EMBED_DEBUG - qDebug() << "client created" << reinterpret_cast(e)->window; -#endif - // The client created an embedded window. - if (d->client) - d->rejectClient(reinterpret_cast(e)->window); - else - d->acceptClient(reinterpret_cast(e)->window); - break; - case XCB_DESTROY_NOTIFY: - if (reinterpret_cast(e)->window == d->client) { -#ifdef QX11EMBED_DEBUG - qDebug() << "client died"; -#endif - // 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; - - //TODO: Port to Qt5, if needed - //Time msgtime = (Time) event->data.data32[0]; - //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: - { - auto event = reinterpret_cast(e); - if (event->child == d->client && !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; - case XCB_PROPERTY_NOTIFY: - { - auto event = reinterpret_cast(e); - - if (event->atom == ATOM(_XEMBED_INFO) && event->window == d->client) { - if (auto info = get_xembed_info(d->client)) { - if (info->flags & XEMBED_MAPPED) { -#ifdef QX11EMBED_DEBUG - qDebug() << "mapping client per _xembed_info"; -#endif - XMapWindow(QX11Info::display(), d->client); - XRaiseWindow(QX11Info::display(), d->client); - } else { -#ifdef QX11EMBED_DEBUG - qDebug() << "unmapping client per _xembed_info"; -#endif - XUnmapWindow(QX11Info::display(), d->client); - } - - free(info); - } - } - break; - } - case XCB_CONFIGURE_NOTIFY: - return true; - default: - break; - } - - return false; -} - -/*! \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()); -} - -/*! - \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); - - XSelectInput(QX11Info::display(), client, PropertyChangeMask); - - // This tells Qt that we wish to forward DnD messages to - // our client. - if (!extra) - createExtra(); - //TODO - //extraData()->xDndProxy = client; - - unsigned int version = XEmbedVersion(); - 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 (auto info = get_xembed_info(client)) { - clientIsXEmbed = true; - clientversion = info->version; - free(info); - } - - // 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, 0, q->internalWinId(), minversion); - - // 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); - - // This is from the original Qt implementation. Disabled for now because it appears - // to cause the mouse being grabbed permanently in some environments - /* - 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 From 0d3aaf667b6623b57884eec07c2a51433fc1a6df Mon Sep 17 00:00:00 2001 From: Lukas W Date: Sat, 5 Aug 2017 12:42:13 +0200 Subject: [PATCH 11/12] .gitmodules: Use https URL --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 4ca030cf5..a81bc8174 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "src/3rdparty/qt5-x11embed"] path = src/3rdparty/qt5-x11embed - url = git@github.com:Lukas-W/qt5-x11embed.git + url = https://github.com/Lukas-W/qt5-x11embed.git From bd33475534cc85053450156132283e07b14caae9 Mon Sep 17 00:00:00 2001 From: Lukas W Date: Sat, 5 Aug 2017 13:15:15 +0200 Subject: [PATCH 12/12] Fix x11embed for non qt5+linux --- src/3rdparty/CMakeLists.txt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/3rdparty/CMakeLists.txt b/src/3rdparty/CMakeLists.txt index 644fccbb1..f1d7a9ba1 100644 --- a/src/3rdparty/CMakeLists.txt +++ b/src/3rdparty/CMakeLists.txt @@ -1,4 +1,8 @@ include(ExternalProject) -set(BUILD_SHARED_LIBS OFF) -add_subdirectory(qt5-x11embed) +IF(QT5 AND LMMS_BUILD_LINUX) + set(BUILD_SHARED_LIBS OFF) + add_subdirectory(qt5-x11embed) +ELSE() + add_library(qx11embedcontainer STATIC /dev/null) +ENDIF()