Port window specific rules dialog to DBus

Summary:
The dialog invoked through user actions menu takes the internal uuid as
command line argument which allows to query the required information
from KWin instead of using X11.

This allows to enable the system for Wayland windows.

In order to replace the usage of ClientMachine in the rules dialog the
dbus interface is extended by a value whether the window is on the
localhost. This is exposed through a virtual method on toplevel which is
overridden in ShellClient and there always returning true.

Test Plan: Run a nested Wayland and opened the dialog on a wayland window

Reviewers: #kwin

Subscribers: kwin

Tags: #kwin

Differential Revision: https://phabricator.kde.org/D17750
This commit is contained in:
Martin Flöser 2018-12-23 08:56:15 +01:00
parent feb1d443f1
commit 0b28abeb01
13 changed files with 82 additions and 104 deletions

View file

@ -151,7 +151,7 @@ void TestDbusInterface::testGetWindowInfoShellClient()
QVERIFY(!reply.isError());
auto windowData = reply.value();
QVERIFY(!windowData.isEmpty());
QCOMPARE(windowData.size(), 23);
QCOMPARE(windowData.size(), 24);
QCOMPARE(windowData.value(QStringLiteral("type")).toInt(), NET::Normal);
QCOMPARE(windowData.value(QStringLiteral("x")).toInt(), client->x());
QCOMPARE(windowData.value(QStringLiteral("y")).toInt(), client->y());
@ -170,6 +170,7 @@ void TestDbusInterface::testGetWindowInfoShellClient()
QCOMPARE(windowData.value(QStringLiteral("maximizeVertical")).toBool(), false);
QCOMPARE(windowData.value(QStringLiteral("noBorder")).toBool(), true);
QCOMPARE(windowData.value(QStringLiteral("clientMachine")).toString(), QString());
QCOMPARE(windowData.value(QStringLiteral("localhost")).toBool(), true);
QCOMPARE(windowData.value(QStringLiteral("role")).toString(), QString());
QCOMPARE(windowData.value(QStringLiteral("resourceName")).toString(), QStringLiteral("testDbusInterface"));
if (type == Test::ShellSurfaceType::WlShell) {
@ -298,7 +299,7 @@ void TestDbusInterface::testGetWindowInfoX11Client()
QVERIFY(!reply.isError());
auto windowData = reply.value();
QVERIFY(!windowData.isEmpty());
QCOMPARE(windowData.size(), 23);
QCOMPARE(windowData.size(), 24);
QCOMPARE(windowData.value(QStringLiteral("type")).toInt(), NET::Normal);
QCOMPARE(windowData.value(QStringLiteral("x")).toInt(), client->x());
QCOMPARE(windowData.value(QStringLiteral("y")).toInt(), client->y());
@ -322,6 +323,7 @@ void TestDbusInterface::testGetWindowInfoX11Client()
QCOMPARE(windowData.value(QStringLiteral("desktopFile")).toString(), QStringLiteral("org.kde.foo"));
QCOMPARE(windowData.value(QStringLiteral("caption")).toString(), QStringLiteral("Some caption"));
// not testing clientmachine as that is system dependent
// due to that also not testing localhost
auto verifyProperty = [client] (const QString &name) {
QDBusPendingReply<QVariantMap> reply{getWindowInfo(client->internalId())};

View file

@ -199,6 +199,7 @@ QVariantMap clientToVariantMap(const AbstractClient *c)
{QStringLiteral("role"), c->windowRole()},
{QStringLiteral("caption"), c->captionNormal()},
{QStringLiteral("clientMachine"), c->wmClientMachine(true)},
{QStringLiteral("localhost"), c->isLocalhost()},
{QStringLiteral("type"), c->windowType()},
{QStringLiteral("x"), c->x()},
{QStringLiteral("y"), c->y()},

View file

@ -4,7 +4,7 @@ add_definitions(-DKCMRULES)
########### next target ###############
include_directories(../../)
set (kwinrules_MOC_HDRS yesnobox.h ../../client_machine.h ../../cursor.h ../../plugins/platforms/x11/standalone/x11cursor.h)
set (kwinrules_MOC_HDRS yesnobox.h ../../cursor.h ../../plugins/platforms/x11/standalone/x11cursor.h)
qt5_wrap_cpp(kwinrules_MOC_SRCS ${kwinrules_MOC_HDRS})
set(kwinrules_SRCS ruleswidget.cpp ruleslist.cpp kwinsrc.cpp detectwidget.cpp ${kwinrules_MOC_SRCS})

View file

@ -26,7 +26,6 @@
#include "../../placement.cpp"
#include "../../options.cpp"
#include "../../utils.cpp"
#include "../../client_machine.cpp"
KWin::InputRedirection *KWin::InputRedirection::s_self = nullptr;

View file

@ -21,16 +21,19 @@
#include <kconfig.h>
#include <KLocalizedString>
#include <kwindowsystem.h>
#include <QtDBus>
#include <QX11Info>
#include <X11/Xlib.h>
#include <fixx11h.h>
#include "ruleswidget.h"
#include "../../rules.h"
#include "../../client_machine.h"
#include <QByteArray>
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusPendingCallWatcher>
#include <QDBusPendingReply>
#include <QUuid>
Q_DECLARE_METATYPE(NET::WindowType)
namespace KWin
{
@ -67,29 +70,14 @@ static void saveRules(const QList< Rules* >& rules)
}
}
static Rules* findRule(const QList< Rules* >& rules, Window wid, bool whole_app)
static Rules* findRule(const QList< Rules* >& rules, const QVariantMap &data, bool whole_app)
{
// ClientMachine::resolve calls NETWinInfo::update() which requires properties
// bug #348472 ./. bug #346748
if (QX11Info::isPlatformX11()) {
qApp->setProperty("x11Connection", QVariant::fromValue<void*>(QX11Info::connection()));
qApp->setProperty("x11RootWindow", QVariant::fromValue(QX11Info::appRootWindow()));
}
KWindowInfo info = KWindowInfo(wid,
NET::WMName | NET::WMWindowType,
NET::WM2WindowClass | NET::WM2WindowRole | NET::WM2ClientMachine);
if (!info.valid()) // shouldn't really happen
return nullptr;
ClientMachine clientMachine;
clientMachine.resolve(info.win(), info.groupLeader());
QByteArray wmclass_class = info.windowClassClass().toLower();
QByteArray wmclass_name = info.windowClassName().toLower();
QByteArray role = info.windowRole().toLower();
NET::WindowType type = info.windowType(NET::NormalMask | NET::DesktopMask | NET::DockMask
| NET::ToolbarMask | NET::MenuMask | NET::DialogMask | NET::OverrideMask | NET::TopMenuMask
| NET::UtilityMask | NET::SplashMask);
QString title = info.name();
QByteArray machine = clientMachine.hostName();
QByteArray wmclass_class = data.value("resourceClass").toByteArray().toLower();
QByteArray wmclass_name = data.value("resourceName").toByteArray().toLower();
QByteArray role = data.value("role").toByteArray().toLower();
NET::WindowType type = data.value("type").value<NET::WindowType>();
QString title = data.value("caption").toString();
QByteArray machine = data.value("clientMachine").toByteArray();
Rules* best_match = nullptr;
int match_quality = 0;
for (QList< Rules* >::ConstIterator it = rules.constBegin();
@ -136,7 +124,7 @@ static Rules* findRule(const QList< Rules* >& rules, Window wid, bool whole_app)
if (!rule->matchType(type)
|| !rule->matchRole(role)
|| !rule->matchTitle(title)
|| !rule->matchClientMachine(machine, clientMachine.isLocal()))
|| !rule->matchClientMachine(machine, data.value("localhost").toBool()))
continue;
if (quality > match_quality) {
best_match = rule;
@ -212,16 +200,16 @@ static Rules* findRule(const QList< Rules* >& rules, Window wid, bool whole_app)
return ret;
}
static int edit(Window wid, bool whole_app)
static void edit(const QVariantMap &data, bool whole_app)
{
QList< Rules* > rules;
loadRules(rules);
Rules* orig_rule = findRule(rules, wid, whole_app);
Rules* orig_rule = findRule(rules, data, whole_app);
RulesDialog dlg;
if (whole_app)
dlg.setWindowTitle(i18nc("Window caption for the application wide rules dialog", "Edit Application-Specific Settings"));
// dlg.edit() creates new Rules instance if edited
Rules* edited_rule = dlg.edit(orig_rule, wid, true);
Rules* edited_rule = dlg.edit(orig_rule, data, true);
if (edited_rule == nullptr || edited_rule->isEmpty()) {
rules.removeAll(orig_rule);
delete orig_rule;
@ -240,7 +228,7 @@ static int edit(Window wid, bool whole_app)
QDBusMessage message =
QDBusMessage::createSignal("/KWin", "org.kde.KWin", "reloadConfig");
QDBusConnection::sessionBus().send(message);
return 0;
qApp->quit();
}
} // namespace
@ -248,28 +236,49 @@ static int edit(Window wid, bool whole_app)
extern "C"
KWIN_EXPORT int kdemain(int argc, char* argv[])
{
qputenv("QT_QPA_PLATFORM", "xcb");
QApplication app(argc, argv);
app.setApplicationDisplayName(i18n("KWin"));
app.setApplicationName("kwin_rules_dialog");
app.setApplicationVersion("1.0");
bool whole_app = false;
bool id_ok = false;
Window id = None;
QUuid uuid;
{
QCommandLineParser parser;
parser.setApplicationDescription(i18n("KWin helper utility"));
parser.addOption(QCommandLineOption("wid", i18n("WId of the window for special window settings."), "wid"));
parser.addOption(QCommandLineOption("uuid", i18n("KWin id of the window for special window settings."), "uuid"));
parser.addOption(QCommandLineOption("whole-app", i18n("Whether the settings should affect all windows of the application.")));
parser.process(app);
id = parser.value("wid").toULongLong(&id_ok);
uuid = QUuid::fromString(parser.value("uuid"));
whole_app = parser.isSet("whole-app");
}
if (!id_ok || id == None) {
if (uuid.isNull()) {
printf("%s\n", qPrintable(i18n("This helper utility is not supposed to be called directly.")));
return 1;
}
return KWin::edit(id, whole_app);
QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KWin"),
QStringLiteral("/KWin"),
QStringLiteral("org.kde.KWin"),
QStringLiteral("getWindowInfo"));
message.setArguments({uuid.toString()});
QDBusPendingReply<QVariantMap> async = QDBusConnection::sessionBus().asyncCall(message);
QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(async, &app);
QObject::connect(callWatcher, &QDBusPendingCallWatcher::finished, &app,
[&whole_app] (QDBusPendingCallWatcher *self) {
QDBusPendingReply<QVariantMap> reply = *self;
self->deleteLater();
if (!reply.isValid() || reply.value().isEmpty()) {
qApp->quit();
return;
}
KWin::edit(reply.value(), whole_app);
}
);
return app.exec();
}

View file

@ -82,7 +82,7 @@ void KCMRulesList::activeChanged()
void KCMRulesList::newClicked()
{
RulesDialog dlg(this);
Rules* rule = dlg.edit(nullptr, 0, false);
Rules* rule = dlg.edit(nullptr, {}, false);
if (rule == nullptr)
return;
int pos = rules_listbox->currentRow() + 1;
@ -98,7 +98,7 @@ void KCMRulesList::modifyClicked()
if (pos == -1)
return;
RulesDialog dlg(this);
Rules* rule = dlg.edit(rules[ pos ], 0, false);
Rules* rule = dlg.edit(rules[ pos ], {}, false);
if (rule == rules[ pos ])
return;
delete rules[ pos ];

View file

@ -24,7 +24,6 @@
#include <QCheckBox>
#include <QFileInfo>
#include <QLabel>
#include <kwindowsystem.h>
#include <KLocalizedString>
#include <QRegExp>
@ -740,45 +739,6 @@ void RulesWidget::detected(bool ok)
#define COMBOBOX_PREFILL( var, func, info ) GENERIC_PREFILL( var, func, info, setCurrentIndex )
#define SPINBOX_PREFILL( var, func, info ) GENERIC_PREFILL( var, func, info, setValue )
void RulesWidget::prefillUnusedValues(const KWindowInfo& info)
{
LINEEDIT_PREFILL(position, positionToStr, info.frameGeometry().topLeft());
LINEEDIT_PREFILL(size, sizeToStr, info.frameGeometry().size());
COMBOBOX_PREFILL(desktop, desktopToCombo, info.desktop());
// COMBOBOX_PREFILL(activity, activityToCombo, info.activity()); // TODO: ivan
CHECKBOX_PREFILL(maximizehoriz, , info.state() & NET::MaxHoriz);
CHECKBOX_PREFILL(maximizevert, , info.state() & NET::MaxVert);
CHECKBOX_PREFILL(minimize, , info.isMinimized());
CHECKBOX_PREFILL(shade, , info.state() & NET::Shaded);
CHECKBOX_PREFILL(fullscreen, , info.state() & NET::FullScreen);
//COMBOBOX_PREFILL( placement, placementToCombo );
CHECKBOX_PREFILL(above, , info.state() & NET::KeepAbove);
CHECKBOX_PREFILL(below, , info.state() & NET::KeepBelow);
// noborder is only internal KWin information, so let's guess
CHECKBOX_PREFILL(noborder, , info.frameGeometry() == info.geometry());
CHECKBOX_PREFILL(skiptaskbar, , info.state() & NET::SkipTaskbar);
CHECKBOX_PREFILL(skippager, , info.state() & NET::SkipPager);
CHECKBOX_PREFILL(skipswitcher, , info.state() & NET::SkipSwitcher);
//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.windowType(SUPPORTED_MANAGED_WINDOW_TYPES_MASK));
//CHECKBOX_PREFILL( ignoregeometry, );
LINEEDIT_PREFILL(minsize, sizeToStr, info.frameGeometry().size());
LINEEDIT_PREFILL(maxsize, sizeToStr, info.frameGeometry().size());
//CHECKBOX_PREFILL( strictgeometry, );
//CHECKBOX_PREFILL( disableglobalshortcuts, );
//CHECKBOX_PREFILL( blockcompositing, );
LINEEDIT_PREFILL(desktopfile, , info.desktopFileName());
}
void RulesWidget::prefillUnusedValues(const QVariantMap& info)
{
const QSize windowSize{info.value("width").toInt(), info.value("height").toInt()};
@ -849,11 +809,9 @@ bool RulesWidget::finalCheck()
return true;
}
void RulesWidget::prepareWindowSpecific(WId window)
void RulesWidget::prepareWindowSpecific(const QVariantMap &info)
{
// 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);
}
@ -886,12 +844,14 @@ RulesDialog::RulesDialog(QWidget* parent, const char* name)
// window is set only for Alt+F3/Window-specific settings, because the dialog
// is then related to one specific window
Rules* RulesDialog::edit(Rules* r, WId window, bool show_hints)
Rules* RulesDialog::edit(Rules* r, const QVariantMap& info, bool show_hints)
{
rules = r;
widget->setRules(rules);
if (window != 0)
widget->prepareWindowSpecific(window);
if (!info.isEmpty())
{
widget->prepareWindowSpecific(info);
}
if (show_hints)
QTimer::singleShot(0, this, SLOT(displayHints()));
exec();

View file

@ -23,7 +23,6 @@
#include <config-kwin.h>
#include <QDialog>
#include <kwindowsystem.h>
#include <kkeysequencewidget.h>
#include "ui_ruleswidgetbase.h"
@ -50,7 +49,7 @@ public:
void setRules(Rules* r);
Rules* rules() const;
bool finalCheck();
void prepareWindowSpecific(WId window);
void prepareWindowSpecific(const QVariantMap &info);
Q_SIGNALS:
void changed(bool state);
protected Q_SLOTS:
@ -117,7 +116,6 @@ private:
int comboToTiling(int val) const;
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;
@ -129,7 +127,7 @@ class RulesDialog
Q_OBJECT
public:
explicit RulesDialog(QWidget* parent = nullptr, const char* name = nullptr);
Rules* edit(Rules* r, WId window, bool show_hints);
Rules* edit(Rules* r, const QVariantMap& info, bool show_hints);
protected:
virtual void accept();
private Q_SLOTS:

View file

@ -1042,7 +1042,7 @@ void RuleBook::edit(AbstractClient* c, bool whole_app)
{
save();
QStringList args;
args << QStringLiteral("--wid") << QString::number(c->window());
args << QStringLiteral("--uuid") << c->internalId().toString();
if (whole_app)
args << QStringLiteral("--whole-app");
QProcess *p = new Process(this);
@ -1050,6 +1050,7 @@ void RuleBook::edit(AbstractClient* c, bool whole_app)
p->setProcessEnvironment(kwinApp()->processStartupEnvironment());
const QFileInfo buildDirBinary{QDir{QCoreApplication::applicationDirPath()}, QStringLiteral("kwin_rules_dialog")};
p->setProgram(buildDirBinary.exists() ? buildDirBinary.absoluteFilePath() : QStringLiteral(KWIN_RULES_DIALOG_BIN));
p->setProcessChannelMode(QProcess::MergedChannels);
connect(p, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), p, &QProcess::deleteLater);
connect(p, static_cast<void (QProcess::*)(QProcess::ProcessError)>(&QProcess::error), this,
[p] (QProcess::ProcessError e) {

View file

@ -168,6 +168,11 @@ public:
bool isPopupWindow() const override;
bool isLocalhost() const override
{
return true;
}
protected:
void addDamage(const QRegion &damage) override;
bool belongsToSameApplication(const AbstractClient *other, SameApplicationChecks checks) const override;

View file

@ -558,5 +558,13 @@ QRect Toplevel::inputGeometry() const
return geometry();
}
bool Toplevel::isLocalhost() const
{
if (!m_clientMachine) {
return true;
}
return m_clientMachine->isLocal();
}
} // namespace

View file

@ -307,6 +307,7 @@ public:
QByteArray wmCommand();
QByteArray wmClientMachine(bool use_localhost) const;
const ClientMachine *clientMachine() const;
virtual bool isLocalhost() const;
Window wmClientLeader() const;
virtual pid_t pid() const;
static bool resourceMatch(const Toplevel* c1, const Toplevel* c2);

View file

@ -471,14 +471,8 @@ void UserActionsMenu::menuAboutToShow()
action->setText(i18n("&Extensions"));
}
// disable rules for Wayland windows - dialog is X11 only
if (qobject_cast<ShellClient*>(m_client.data())) {
m_rulesOperation->setEnabled(false);
m_applicationRulesOperation->setEnabled(false);
} else {
m_rulesOperation->setEnabled(true);
m_applicationRulesOperation->setEnabled(true);
}
m_rulesOperation->setEnabled(true);
m_applicationRulesOperation->setEnabled(true);
showHideActivityMenu();
}