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:
parent
119c64839d
commit
1d71292e13
8 changed files with 132 additions and 126 deletions
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -29,7 +29,6 @@ set(kcm_libs
|
|||
KF5::Service
|
||||
KF5::WindowSystem
|
||||
KF5::XmlGui
|
||||
${X11_LIBRARIES}
|
||||
)
|
||||
|
||||
if(KWIN_BUILD_ACTIVITIES)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue