diff --git a/dbusinterface.h b/dbusinterface.h --- a/dbusinterface.h +++ b/dbusinterface.h @@ -43,7 +43,7 @@ * * @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 @@ 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/dbusinterface.cpp b/dbusinterface.cpp --- a/dbusinterface.cpp +++ b/dbusinterface.cpp @@ -23,6 +23,7 @@ #include "compositingadaptor.h" // kwin +#include "abstract_client.h" #include "atoms.h" #include "composite.h" #include "debug_console.h" @@ -187,6 +188,46 @@ 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/kcmkwin/kwinrules/CMakeLists.txt b/kcmkwin/kwinrules/CMakeLists.txt --- a/kcmkwin/kwinrules/CMakeLists.txt +++ b/kcmkwin/kwinrules/CMakeLists.txt @@ -29,7 +29,6 @@ KF5::Service KF5::WindowSystem KF5::XmlGui - ${X11_LIBRARIES} ) if(KWIN_BUILD_ACTIVITIES) diff --git a/kcmkwin/kwinrules/detectwidget.h b/kcmkwin/kwinrules/detectwidget.h --- a/kcmkwin/kwinrules/detectwidget.h +++ b/kcmkwin/kwinrules/detectwidget.h @@ -22,7 +22,6 @@ #include #include -#include #include "../../rules.h" //Added by qt3to4: @@ -43,50 +42,42 @@ }; 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; bool selectedWholeApp() const; NET::WindowType selectedType() const; 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; NET::WindowType type; QString title; 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/detectwidget.cpp b/kcmkwin/kwinrules/detectwidget.cpp --- 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 @@ } DetectDialog::DetectDialog(QWidget* parent, const char* name) - : QDialog(parent), - grabber() + : QDialog(parent) { setObjectName(name); setModal(true); @@ -64,34 +64,9 @@ connect(buttons, SIGNAL(rejected()), SLOT(reject())); } -void DetectDialog::detect(WId window, int secs) -{ - if (window == 0) - QTimer::singleShot(secs*1000, this, SLOT(selectWindow())); - else - readWindow(window); -} - -void DetectDialog::readWindow(WId w) +void DetectDialog::detect(int secs) { - 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 @@ 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(); - } -} - -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; + QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KWin"), + QStringLiteral("/KWin"), + QStringLiteral("org.kde.KWin"), + QStringLiteral("queryWindowInfo")); + QDBusPendingReply async = QDBusConnection::sessionBus().asyncCall(message); + + 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/ruleswidget.h b/kcmkwin/kwinrules/ruleswidget.h --- a/kcmkwin/kwinrules/ruleswidget.h +++ b/kcmkwin/kwinrules/ruleswidget.h @@ -117,6 +117,7 @@ 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/kcmkwin/kwinrules/ruleswidget.cpp b/kcmkwin/kwinrules/ruleswidget.cpp --- 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 @@ 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 @@ 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 @@ //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 @@ 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/org.kde.KWin.xml b/org.kde.KWin.xml --- a/org.kde.KWin.xml +++ b/org.kde.KWin.xml @@ -36,5 +36,9 @@ + + + +