From 1d71292e13fc820c12996d28d693d6e02c23defe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Fl=C3=B6ser?= Date: Mon, 12 Feb 2018 21:40:05 +0100 Subject: [PATCH] Add a method to dbus interface to query information about a window Summary: This call is added for the window rules kcm which has a detect functionality. As that detect functionality cannot query any Wayland windows we need to have some functionality in KWin core. Furthermore this allows to simplify the code in the kcm as all the custom X11 interaction can be removed. KWin internally has the functionality to find a window at a given position. From a security perspective adding this dbus method is fine as the user stays in control of the functionality. It requires active click to select a window. The new dbus call is already used in the rules kcm replacing the X11 based detect functionality. That a detect is now able to get information for both X11 and Wayland windows. So far only X11 windows on X11 were supported. So this fills an important gap in the Wayland offerings. It should now be possible to create rules for Wayland windows (though may not be fully functional). Test Plan: Run the kwin_rules_dialog and it detected the window correctly Reviewers: #kwin, #plasma Subscribers: plasma-devel, kwin Tags: #plasma Differential Revision: https://phabricator.kde.org/D10490 --- dbusinterface.cpp | 41 +++++++++ dbusinterface.h | 5 +- kcmkwin/kwinrules/CMakeLists.txt | 1 - kcmkwin/kwinrules/detectwidget.cpp | 137 +++++++---------------------- kcmkwin/kwinrules/detectwidget.h | 23 ++--- kcmkwin/kwinrules/ruleswidget.cpp | 46 +++++++++- kcmkwin/kwinrules/ruleswidget.h | 1 + org.kde.KWin.xml | 4 + 8 files changed, 132 insertions(+), 126 deletions(-) diff --git a/dbusinterface.cpp b/dbusinterface.cpp index 8aaaa32ed8..ea5f5955da 100644 --- a/dbusinterface.cpp +++ b/dbusinterface.cpp @@ -23,6 +23,7 @@ along with this program. If not, see . #include "compositingadaptor.h" // kwin +#include "abstract_client.h" #include "atoms.h" #include "composite.h" #include "debug_console.h" @@ -187,6 +188,46 @@ void DBusInterface::showDebugConsole() console->show(); } +QVariantMap DBusInterface::queryWindowInfo() +{ + m_replyQueryWindowInfo = message(); + setDelayedReply(true); + kwinApp()->platform()->startInteractiveWindowSelection( + [this] (Toplevel *t) { + if (auto c = qobject_cast(t)) { + const QVariantMap ret{ + {QStringLiteral("resourceClass"), c->resourceClass()}, + {QStringLiteral("resourceName"), c->resourceName()}, + {QStringLiteral("role"), c->windowRole()}, + {QStringLiteral("caption"), c->captionNormal()}, + {QStringLiteral("clientMachine"), c->wmClientMachine(true)}, + {QStringLiteral("type"), c->windowType()}, + {QStringLiteral("x"), c->x()}, + {QStringLiteral("y"), c->y()}, + {QStringLiteral("width"), c->width()}, + {QStringLiteral("height"), c->height()}, + {QStringLiteral("x11DesktopNumber"), c->desktop()}, + {QStringLiteral("minimized"), c->isMinimized()}, + {QStringLiteral("shaded"), c->isShade()}, + {QStringLiteral("fullscreen"), c->isFullScreen()}, + {QStringLiteral("keepAbove"), c->keepAbove()}, + {QStringLiteral("keepBelow"), c->keepBelow()}, + {QStringLiteral("noBorder"), c->noBorder()}, + {QStringLiteral("skipTaskbar"), c->skipTaskbar()}, + {QStringLiteral("skipPager"), c->skipPager()}, + {QStringLiteral("skipSwitcher"), c->skipSwitcher()}, + {QStringLiteral("maximizeHorizontal"), c->maximizeMode() & MaximizeHorizontal}, + {QStringLiteral("maximizeVertical"), c->maximizeMode() & MaximizeVertical} + }; + QDBusConnection::sessionBus().send(m_replyQueryWindowInfo.createReply(ret)); + } else { + QDBusConnection::sessionBus().send(m_replyQueryWindowInfo.createErrorReply(QString(), QString())); + } + } + ); + return QVariantMap{}; +} + CompositorDBusInterface::CompositorDBusInterface(Compositor *parent) : QObject(parent) , m_compositor(parent) diff --git a/dbusinterface.h b/dbusinterface.h index 5781a28e89..489030acbf 100644 --- a/dbusinterface.h +++ b/dbusinterface.h @@ -43,7 +43,7 @@ class Compositor; * * @author Martin Gräßlin **/ -class DBusInterface: public QObject +class DBusInterface: public QObject, protected QDBusContext { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.KWin") @@ -66,12 +66,15 @@ public Q_SLOTS: // METHODS Q_NOREPLY void unclutterDesktop(); Q_NOREPLY void showDebugConsole(); + QVariantMap queryWindowInfo(); + private Q_SLOTS: void becomeKWinService(const QString &service); private: void announceService(); QString m_serviceName; + QDBusMessage m_replyQueryWindowInfo; }; class CompositorDBusInterface : public QObject diff --git a/kcmkwin/kwinrules/CMakeLists.txt b/kcmkwin/kwinrules/CMakeLists.txt index fbe3e6d21b..b53f7b5f51 100644 --- a/kcmkwin/kwinrules/CMakeLists.txt +++ b/kcmkwin/kwinrules/CMakeLists.txt @@ -29,7 +29,6 @@ set(kcm_libs KF5::Service KF5::WindowSystem KF5::XmlGui - ${X11_LIBRARIES} ) if(KWIN_BUILD_ACTIVITIES) diff --git a/kcmkwin/kwinrules/detectwidget.cpp b/kcmkwin/kwinrules/detectwidget.cpp index 2e0cfa3964..db00184e7b 100644 --- a/kcmkwin/kwinrules/detectwidget.cpp +++ b/kcmkwin/kwinrules/detectwidget.cpp @@ -31,11 +31,12 @@ #include #include -#include -#include -#include -#include -#include +#include +#include +#include +#include + +Q_DECLARE_METATYPE(NET::WindowType) namespace KWin { @@ -47,8 +48,7 @@ DetectWidget::DetectWidget(QWidget* parent) } DetectDialog::DetectDialog(QWidget* parent, const char* name) - : QDialog(parent), - grabber() + : QDialog(parent) { setObjectName(name); setModal(true); @@ -64,34 +64,9 @@ DetectDialog::DetectDialog(QWidget* parent, const char* name) connect(buttons, SIGNAL(rejected()), SLOT(reject())); } -void DetectDialog::detect(WId window, int secs) +void DetectDialog::detect(int secs) { - if (window == 0) - QTimer::singleShot(secs*1000, this, SLOT(selectWindow())); - else - readWindow(window); -} - -void DetectDialog::readWindow(WId w) -{ - if (w == 0) { - emit detectionDone(false); - return; - } - info.reset(new KWindowInfo(w, NET::WMAllProperties, NET::WM2AllProperties)); // read everything - if (!info->valid()) { - emit detectionDone(false); - return; - } - wmclass_class = info->windowClassClass(); - wmclass_name = info->windowClassName(); - role = info->windowRole(); - type = info->windowType(NET::NormalMask | NET::DesktopMask | NET::DockMask - | NET::ToolbarMask | NET::MenuMask | NET::DialogMask | NET::OverrideMask | NET::TopMenuMask - | NET::UtilityMask | NET::SplashMask); - title = info->name(); - machine = info->clientMachine(); - executeDialog(); + QTimer::singleShot(secs*1000, this, SLOT(selectWindow())); } void DetectDialog::executeDialog() @@ -170,79 +145,31 @@ QByteArray DetectDialog::selectedMachine() const void DetectDialog::selectWindow() { - if (!KWin::Cursor::self()) { - qApp->setProperty("x11Connection", QVariant::fromValue(QX11Info::connection())); - qApp->setProperty("x11RootWindow", QVariant::fromValue(QX11Info::appRootWindow())); - new X11Cursor(this); - } - // use a dialog, so that all user input is blocked - // use WX11BypassWM and moving away so that it's not actually visible - // grab only mouse, so that keyboard can be used e.g. for switching windows - grabber.reset(new QDialog(nullptr, Qt::X11BypassWindowManagerHint)); - grabber->move(-1000, -1000); - grabber->setModal(true); - grabber->show(); - // Qt uses QX11Info::appTime() to grab the pointer, what can silently fail (#318437) ... - XSync(QX11Info::display(), false); - if (XGrabPointer(QX11Info::display(), grabber->winId(), false, ButtonReleaseMask, - GrabModeAsync, GrabModeAsync, None, KWin::Cursor::x11Cursor(Qt::CrossCursor), - CurrentTime) == Success) { // ...so we use the far more convincing CurrentTime - QCoreApplication::instance()->installNativeEventFilter(this); - } else { - // ... and if we fail, cleanup, so we won't receive random events - grabber.reset(); - } -} + QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KWin"), + QStringLiteral("/KWin"), + QStringLiteral("org.kde.KWin"), + QStringLiteral("queryWindowInfo")); + QDBusPendingReply async = QDBusConnection::sessionBus().asyncCall(message); -bool DetectDialog::nativeEventFilter(const QByteArray &eventType, void *message, long int*) -{ - if (eventType != QByteArrayLiteral("xcb_generic_event_t")) { - return false; - } - auto *event = reinterpret_cast(message); - if ((event->response_type & ~0x80) != XCB_BUTTON_RELEASE) { - return false; - } - QCoreApplication::instance()->removeNativeEventFilter(this); - grabber.reset(); - auto *me = reinterpret_cast(event); - if (me->detail != XCB_BUTTON_INDEX_1) { - emit detectionDone(false); - return true; - } - readWindow(findWindow()); - return true; -} - -WId DetectDialog::findWindow() -{ - Window root; - Window child; - uint mask; - int rootX, rootY, x, y; - Window parent = QX11Info::appRootWindow(); - Atom wm_state = XInternAtom(QX11Info::display(), "WM_STATE", False); - for (int i = 0; - i < 10; - ++i) { - XQueryPointer(QX11Info::display(), parent, &root, &child, - &rootX, &rootY, &x, &y, &mask); - if (child == None) - return 0; - Atom type; - int format; - unsigned long nitems, after; - unsigned char* prop; - if (XGetWindowProperty(QX11Info::display(), child, wm_state, 0, 0, False, AnyPropertyType, - &type, &format, &nitems, &after, &prop) == Success) { - if (prop != nullptr) - XFree(prop); - if (type != None) - return child; + QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(async, this); + connect(callWatcher, &QDBusPendingCallWatcher::finished, this, + [this](QDBusPendingCallWatcher *self) { + QDBusPendingReply reply = *self; + self->deleteLater(); + if (!reply.isValid()) { + emit detectionDone(false); + return; + } + m_windowInfo = reply.value(); + wmclass_class = m_windowInfo.value("resourceClass").toByteArray(); + wmclass_name = m_windowInfo.value("resourceName").toByteArray(); + role = m_windowInfo.value("role").toByteArray(); + type = m_windowInfo.value("type").value(); + title = m_windowInfo.value("caption").toString(); + machine = m_windowInfo.value("clientMachine").toByteArray(); + executeDialog(); } - parent = child; - } - return 0; + ); } } // namespace diff --git a/kcmkwin/kwinrules/detectwidget.h b/kcmkwin/kwinrules/detectwidget.h index efc8980c41..c0a208f60a 100644 --- a/kcmkwin/kwinrules/detectwidget.h +++ b/kcmkwin/kwinrules/detectwidget.h @@ -22,7 +22,6 @@ #include #include -#include #include "../../rules.h" //Added by qt3to4: @@ -43,12 +42,12 @@ public: }; class DetectDialog - : public QDialog, public QAbstractNativeEventFilter + : public QDialog { Q_OBJECT public: explicit DetectDialog(QWidget* parent = nullptr, const char* name = nullptr); - void detect(WId window, int secs = 0); + void detect(int secs = 0); QByteArray selectedClass() const; bool selectedWholeClass() const; QByteArray selectedRole() const; @@ -57,17 +56,17 @@ public: QString selectedTitle() const; Rules::StringMatch titleMatch() const; QByteArray selectedMachine() const; - const KWindowInfo& windowInfo() const; - virtual bool nativeEventFilter(const QByteArray& eventType, void* message, long int* result) override; + const QVariantMap &windowInfo() const { + return m_windowInfo; + } + Q_SIGNALS: void detectionDone(bool); private Q_SLOTS: void selectWindow(); private: - void readWindow(WId window); void executeDialog(); - WId findWindow(); QByteArray wmclass_class; QByteArray wmclass_name; QByteArray role; @@ -76,17 +75,9 @@ private: QByteArray extrarole; QByteArray machine; DetectWidget* widget; - QScopedPointer grabber; - QScopedPointer info; + QVariantMap m_windowInfo; }; -inline -const KWindowInfo& DetectDialog::windowInfo() const -{ - Q_ASSERT(!info.isNull()); - return *(info.data()); -} - } // namespace #endif diff --git a/kcmkwin/kwinrules/ruleswidget.cpp b/kcmkwin/kwinrules/ruleswidget.cpp index b6765da9a8..240027f24d 100644 --- a/kcmkwin/kwinrules/ruleswidget.cpp +++ b/kcmkwin/kwinrules/ruleswidget.cpp @@ -41,6 +41,8 @@ #include "detectwidget.h" +Q_DECLARE_METATYPE(NET::WindowType) + namespace KWin { @@ -680,7 +682,7 @@ void RulesWidget::detectClicked() assert(detect_dlg == nullptr); detect_dlg = new DetectDialog; connect(detect_dlg, SIGNAL(detectionDone(bool)), this, SLOT(detected(bool))); - detect_dlg->detect(0, Ui::RulesWidgetBase::detection_delay->value()); + detect_dlg->detect(Ui::RulesWidgetBase::detection_delay->value()); } void RulesWidget::detected(bool ok) @@ -714,8 +716,7 @@ void RulesWidget::detected(bool ok) machine_match->setCurrentIndex(Rules::UnimportantMatch); machineMatchChanged(); // prefill values from to window to settings which already set - const KWindowInfo& info = detect_dlg->windowInfo(); - prefillUnusedValues(info); + prefillUnusedValues(detect_dlg->windowInfo()); } delete detect_dlg; detect_dlg = nullptr; @@ -771,6 +772,44 @@ void RulesWidget::prefillUnusedValues(const KWindowInfo& info) //CHECKBOX_PREFILL( blockcompositing, ); } +void RulesWidget::prefillUnusedValues(const QVariantMap& info) +{ + const QSize windowSize{info.value("width").toInt(), info.value("height").toInt()}; + LINEEDIT_PREFILL(position, positionToStr, QPoint(info.value("x").toInt(), info.value("y").toInt())); + LINEEDIT_PREFILL(size, sizeToStr, windowSize); + COMBOBOX_PREFILL(desktop, desktopToCombo, info.value("x11DesktopNumber").toInt()); + // COMBOBOX_PREFILL(activity, activityToCombo, info.activity()); // TODO: ivan + CHECKBOX_PREFILL(maximizehoriz, , info.value("maximizeHorizontal").toBool()); + CHECKBOX_PREFILL(maximizevert, , info.value("maximizeVertical").toBool()); + CHECKBOX_PREFILL(minimize, , info.value("minimized").toBool()); + CHECKBOX_PREFILL(shade, , info.value("shaded").toBool()); + CHECKBOX_PREFILL(fullscreen, , info.value("fullscreen").toBool()); + //COMBOBOX_PREFILL( placement, placementToCombo ); + CHECKBOX_PREFILL(above, , info.value("keepAbove").toBool()); + CHECKBOX_PREFILL(below, , info.value("keepBelow").toBool()); + CHECKBOX_PREFILL(noborder, , info.value("noBorder").toBool()); + CHECKBOX_PREFILL(skiptaskbar, , info.value("skipTaskbar").toBool()); + CHECKBOX_PREFILL(skippager, , info.value("skipPager").toBool()); + CHECKBOX_PREFILL(skipswitcher, , info.value("skipSwitcher").toBool()); + //CHECKBOX_PREFILL( acceptfocus, ); + //CHECKBOX_PREFILL( closeable, ); + //CHECKBOX_PREFILL( autogroup, ); + //CHECKBOX_PREFILL( autogroupfg, ); + //LINEEDIT_PREFILL( autogroupid, ); + SPINBOX_PREFILL(opacityactive, , 100 /*get the actual opacity somehow*/); + SPINBOX_PREFILL(opacityinactive, , 100 /*get the actual opacity somehow*/); + //LINEEDIT_PREFILL( shortcut, ); + //COMBOBOX_PREFILL( fsplevel, ); + //COMBOBOX_PREFILL( fpplevel, ); + COMBOBOX_PREFILL(type, typeToCombo, info.value("type").value()); + //CHECKBOX_PREFILL( ignoregeometry, ); + LINEEDIT_PREFILL(minsize, sizeToStr, windowSize); + LINEEDIT_PREFILL(maxsize, sizeToStr, windowSize); + //CHECKBOX_PREFILL( strictgeometry, ); + //CHECKBOX_PREFILL( disableglobalshortcuts, ); + //CHECKBOX_PREFILL( blockcompositing, ); +} + #undef GENERIC_PREFILL #undef CHECKBOX_PREFILL #undef LINEEDIT_PREFILL @@ -804,6 +843,7 @@ bool RulesWidget::finalCheck() void RulesWidget::prepareWindowSpecific(WId window) { + // TODO: adjust for Wayland tabs->setCurrentIndex(1); // geometry tab, skip tab for window identification KWindowInfo info(window, NET::WMAllProperties, NET::WM2AllProperties); // read everything prefillUnusedValues(info); diff --git a/kcmkwin/kwinrules/ruleswidget.h b/kcmkwin/kwinrules/ruleswidget.h index 7a4e3f27f7..714ed885c9 100644 --- a/kcmkwin/kwinrules/ruleswidget.h +++ b/kcmkwin/kwinrules/ruleswidget.h @@ -117,6 +117,7 @@ private: int inc(int i) const { return i+1; } int dec(int i) const { return i-1; } void prefillUnusedValues(const KWindowInfo& info); + void prefillUnusedValues(const QVariantMap& info); DetectDialog* detect_dlg; bool detect_dlg_ok; }; diff --git a/org.kde.KWin.xml b/org.kde.KWin.xml index 7f57f0c1f3..4794335f5b 100644 --- a/org.kde.KWin.xml +++ b/org.kde.KWin.xml @@ -36,5 +36,9 @@ + + + +