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
This commit is contained in:
Martin Flöser 2018-02-12 21:40:05 +01:00
parent 119c64839d
commit 1d71292e13
8 changed files with 132 additions and 126 deletions

View file

@ -23,6 +23,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "compositingadaptor.h" #include "compositingadaptor.h"
// kwin // kwin
#include "abstract_client.h"
#include "atoms.h" #include "atoms.h"
#include "composite.h" #include "composite.h"
#include "debug_console.h" #include "debug_console.h"
@ -187,6 +188,46 @@ void DBusInterface::showDebugConsole()
console->show(); console->show();
} }
QVariantMap DBusInterface::queryWindowInfo()
{
m_replyQueryWindowInfo = message();
setDelayedReply(true);
kwinApp()->platform()->startInteractiveWindowSelection(
[this] (Toplevel *t) {
if (auto c = qobject_cast<AbstractClient*>(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) CompositorDBusInterface::CompositorDBusInterface(Compositor *parent)
: QObject(parent) : QObject(parent)
, m_compositor(parent) , m_compositor(parent)

View file

@ -43,7 +43,7 @@ class Compositor;
* *
* @author Martin Gräßlin <mgraesslin@kde.org> * @author Martin Gräßlin <mgraesslin@kde.org>
**/ **/
class DBusInterface: public QObject class DBusInterface: public QObject, protected QDBusContext
{ {
Q_OBJECT Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.kde.KWin") Q_CLASSINFO("D-Bus Interface", "org.kde.KWin")
@ -66,12 +66,15 @@ public Q_SLOTS: // METHODS
Q_NOREPLY void unclutterDesktop(); Q_NOREPLY void unclutterDesktop();
Q_NOREPLY void showDebugConsole(); Q_NOREPLY void showDebugConsole();
QVariantMap queryWindowInfo();
private Q_SLOTS: private Q_SLOTS:
void becomeKWinService(const QString &service); void becomeKWinService(const QString &service);
private: private:
void announceService(); void announceService();
QString m_serviceName; QString m_serviceName;
QDBusMessage m_replyQueryWindowInfo;
}; };
class CompositorDBusInterface : public QObject class CompositorDBusInterface : public QObject

View file

@ -29,7 +29,6 @@ set(kcm_libs
KF5::Service KF5::Service
KF5::WindowSystem KF5::WindowSystem
KF5::XmlGui KF5::XmlGui
${X11_LIBRARIES}
) )
if(KWIN_BUILD_ACTIVITIES) if(KWIN_BUILD_ACTIVITIES)

View file

@ -31,11 +31,12 @@
#include <QByteArray> #include <QByteArray>
#include <QTimer> #include <QTimer>
#include <X11/Xlib.h> #include <QDBusConnection>
#include <X11/Xatom.h> #include <QDBusMessage>
#include <X11/Xutil.h> #include <QDBusPendingCallWatcher>
#include <fixx11h.h> #include <QDBusPendingReply>
#include <QX11Info>
Q_DECLARE_METATYPE(NET::WindowType)
namespace KWin namespace KWin
{ {
@ -47,8 +48,7 @@ DetectWidget::DetectWidget(QWidget* parent)
} }
DetectDialog::DetectDialog(QWidget* parent, const char* name) DetectDialog::DetectDialog(QWidget* parent, const char* name)
: QDialog(parent), : QDialog(parent)
grabber()
{ {
setObjectName(name); setObjectName(name);
setModal(true); setModal(true);
@ -64,34 +64,9 @@ DetectDialog::DetectDialog(QWidget* parent, const char* name)
connect(buttons, SIGNAL(rejected()), SLOT(reject())); 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()));
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();
} }
void DetectDialog::executeDialog() void DetectDialog::executeDialog()
@ -170,79 +145,31 @@ QByteArray DetectDialog::selectedMachine() const
void DetectDialog::selectWindow() void DetectDialog::selectWindow()
{ {
if (!KWin::Cursor::self()) { QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KWin"),
qApp->setProperty("x11Connection", QVariant::fromValue<void*>(QX11Info::connection())); QStringLiteral("/KWin"),
qApp->setProperty("x11RootWindow", QVariant::fromValue(QX11Info::appRootWindow())); QStringLiteral("org.kde.KWin"),
new X11Cursor(this); QStringLiteral("queryWindowInfo"));
} QDBusPendingReply<QVariantMap> async = QDBusConnection::sessionBus().asyncCall(message);
// 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*) QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(async, this);
{ connect(callWatcher, &QDBusPendingCallWatcher::finished, this,
if (eventType != QByteArrayLiteral("xcb_generic_event_t")) { [this](QDBusPendingCallWatcher *self) {
return false; QDBusPendingReply<QVariantMap> reply = *self;
} self->deleteLater();
auto *event = reinterpret_cast<xcb_generic_event_t *>(message); if (!reply.isValid()) {
if ((event->response_type & ~0x80) != XCB_BUTTON_RELEASE) { emit detectionDone(false);
return false; return;
} }
QCoreApplication::instance()->removeNativeEventFilter(this); m_windowInfo = reply.value();
grabber.reset(); wmclass_class = m_windowInfo.value("resourceClass").toByteArray();
auto *me = reinterpret_cast<xcb_button_press_event_t*>(event); wmclass_name = m_windowInfo.value("resourceName").toByteArray();
if (me->detail != XCB_BUTTON_INDEX_1) { role = m_windowInfo.value("role").toByteArray();
emit detectionDone(false); type = m_windowInfo.value("type").value<NET::WindowType>();
return true; title = m_windowInfo.value("caption").toString();
} machine = m_windowInfo.value("clientMachine").toByteArray();
readWindow(findWindow()); executeDialog();
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;
} }
parent = child; );
}
return 0;
} }
} // namespace } // namespace

View file

@ -22,7 +22,6 @@
#include <QDialog> #include <QDialog>
#include <kwindowsystem.h> #include <kwindowsystem.h>
#include <QAbstractNativeEventFilter>
#include "../../rules.h" #include "../../rules.h"
//Added by qt3to4: //Added by qt3to4:
@ -43,12 +42,12 @@ public:
}; };
class DetectDialog class DetectDialog
: public QDialog, public QAbstractNativeEventFilter : public QDialog
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit DetectDialog(QWidget* parent = nullptr, const char* name = nullptr); explicit DetectDialog(QWidget* parent = nullptr, const char* name = nullptr);
void detect(WId window, int secs = 0); void detect(int secs = 0);
QByteArray selectedClass() const; QByteArray selectedClass() const;
bool selectedWholeClass() const; bool selectedWholeClass() const;
QByteArray selectedRole() const; QByteArray selectedRole() const;
@ -57,17 +56,17 @@ public:
QString selectedTitle() const; QString selectedTitle() const;
Rules::StringMatch titleMatch() const; Rules::StringMatch titleMatch() const;
QByteArray selectedMachine() 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: Q_SIGNALS:
void detectionDone(bool); void detectionDone(bool);
private Q_SLOTS: private Q_SLOTS:
void selectWindow(); void selectWindow();
private: private:
void readWindow(WId window);
void executeDialog(); void executeDialog();
WId findWindow();
QByteArray wmclass_class; QByteArray wmclass_class;
QByteArray wmclass_name; QByteArray wmclass_name;
QByteArray role; QByteArray role;
@ -76,17 +75,9 @@ private:
QByteArray extrarole; QByteArray extrarole;
QByteArray machine; QByteArray machine;
DetectWidget* widget; DetectWidget* widget;
QScopedPointer<QDialog> grabber; QVariantMap m_windowInfo;
QScopedPointer<KWindowInfo> info;
}; };
inline
const KWindowInfo& DetectDialog::windowInfo() const
{
Q_ASSERT(!info.isNull());
return *(info.data());
}
} // namespace } // namespace
#endif #endif

View file

@ -41,6 +41,8 @@
#include "detectwidget.h" #include "detectwidget.h"
Q_DECLARE_METATYPE(NET::WindowType)
namespace KWin namespace KWin
{ {
@ -680,7 +682,7 @@ void RulesWidget::detectClicked()
assert(detect_dlg == nullptr); assert(detect_dlg == nullptr);
detect_dlg = new DetectDialog; detect_dlg = new DetectDialog;
connect(detect_dlg, SIGNAL(detectionDone(bool)), this, SLOT(detected(bool))); 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) void RulesWidget::detected(bool ok)
@ -714,8 +716,7 @@ void RulesWidget::detected(bool ok)
machine_match->setCurrentIndex(Rules::UnimportantMatch); machine_match->setCurrentIndex(Rules::UnimportantMatch);
machineMatchChanged(); machineMatchChanged();
// prefill values from to window to settings which already set // prefill values from to window to settings which already set
const KWindowInfo& info = detect_dlg->windowInfo(); prefillUnusedValues(detect_dlg->windowInfo());
prefillUnusedValues(info);
} }
delete detect_dlg; delete detect_dlg;
detect_dlg = nullptr; detect_dlg = nullptr;
@ -771,6 +772,44 @@ void RulesWidget::prefillUnusedValues(const KWindowInfo& info)
//CHECKBOX_PREFILL( blockcompositing, ); //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<NET::WindowType>());
//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 GENERIC_PREFILL
#undef CHECKBOX_PREFILL #undef CHECKBOX_PREFILL
#undef LINEEDIT_PREFILL #undef LINEEDIT_PREFILL
@ -804,6 +843,7 @@ bool RulesWidget::finalCheck()
void RulesWidget::prepareWindowSpecific(WId window) void RulesWidget::prepareWindowSpecific(WId window)
{ {
// TODO: adjust for Wayland
tabs->setCurrentIndex(1); // geometry tab, skip tab for window identification tabs->setCurrentIndex(1); // geometry tab, skip tab for window identification
KWindowInfo info(window, NET::WMAllProperties, NET::WM2AllProperties); // read everything KWindowInfo info(window, NET::WMAllProperties, NET::WM2AllProperties); // read everything
prefillUnusedValues(info); prefillUnusedValues(info);

View file

@ -117,6 +117,7 @@ private:
int inc(int i) const { return i+1; } int inc(int i) const { return i+1; }
int dec(int i) const { return i-1; } int dec(int i) const { return i-1; }
void prefillUnusedValues(const KWindowInfo& info); void prefillUnusedValues(const KWindowInfo& info);
void prefillUnusedValues(const QVariantMap& info);
DetectDialog* detect_dlg; DetectDialog* detect_dlg;
bool detect_dlg_ok; bool detect_dlg_ok;
}; };

View file

@ -36,5 +36,9 @@
<arg type="s" direction="out"/> <arg type="s" direction="out"/>
</method> </method>
<method name="showDebugConsole"/> <method name="showDebugConsole"/>
<method name="queryWindowInfo">
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QVariantMap"/>
<arg type="a{sv}" direction="out"/>
</method>
</interface> </interface>
</node> </node>