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
icc-effect-5.14.5
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"
// 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<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)
: QObject(parent)
, m_compositor(parent)

View File

@ -43,7 +43,7 @@ class Compositor;
*
* @author Martin Gräßlin <mgraesslin@kde.org>
**/
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

View File

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

View File

@ -31,11 +31,12 @@
#include <QByteArray>
#include <QTimer>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#include <fixx11h.h>
#include <QX11Info>
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusPendingCallWatcher>
#include <QDBusPendingReply>
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<void*>(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<QVariantMap> 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<xcb_generic_event_t *>(message);
if ((event->response_type & ~0x80) != XCB_BUTTON_RELEASE) {
return false;
}
QCoreApplication::instance()->removeNativeEventFilter(this);
grabber.reset();
auto *me = reinterpret_cast<xcb_button_press_event_t*>(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<QVariantMap> 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<NET::WindowType>();
title = m_windowInfo.value("caption").toString();
machine = m_windowInfo.value("clientMachine").toByteArray();
executeDialog();
}
parent = child;
}
return 0;
);
}
} // namespace

View File

@ -22,7 +22,6 @@
#include <QDialog>
#include <kwindowsystem.h>
#include <QAbstractNativeEventFilter>
#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<QDialog> grabber;
QScopedPointer<KWindowInfo> info;
QVariantMap m_windowInfo;
};
inline
const KWindowInfo& DetectDialog::windowInfo() const
{
Q_ASSERT(!info.isNull());
return *(info.data());
}
} // namespace
#endif

View File

@ -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<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 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);

View File

@ -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;
};

View File

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