kwinrules: Use a list of desktop UUIDs to set and store the rules setting

Port the RuleSettings and KCM to store and use a list of virtual desktop
 UUIDs, instead of the previous x11 positional id, continuing the work on
This allows to set a rule with several desktops on Wayland.

On X11 it has no visible change for the user, but internally it uses the
more modern concept, helping to simplify the related code.

The relevant key on kwinrulesrc changes from `desktop` to `desktops`.
A kconf_update script handles the migration.
icc-effect-5.26.4
Ismael Asensio 2021-08-13 21:49:10 +02:00
parent 5da8df39a5
commit 9995f984c3
12 changed files with 174 additions and 78 deletions

View File

@ -1783,12 +1783,18 @@ void TestXdgShellClientRules::testMaximizeForceTemporarily()
void TestXdgShellClientRules::testDesktopDontAffect()
{
// We need at least two virtual desktop for this test.
VirtualDesktopManager::self()->setCount(2);
QCOMPARE(VirtualDesktopManager::self()->count(), 2u);
VirtualDesktopManager::self()->setCurrent(1);
QCOMPARE(VirtualDesktopManager::self()->current(), 1);
// Initialize RuleBook with the test rule.
auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig);
config->group("General").writeEntry("count", 1);
KConfigGroup group = config->group("1");
group.writeEntry("desktop", 2);
group.writeEntry("desktoprule", int(Rules::DontAffect));
group.writeEntry("desktops", {VirtualDesktopManager::self()->desktopForX11Id(2)->id()});
group.writeEntry("desktopsrule", int(Rules::DontAffect));
group.writeEntry("wmclass", "org.kde.foo");
group.writeEntry("wmclasscomplete", false);
group.writeEntry("wmclassmatch", int(Rules::ExactMatch));
@ -1796,12 +1802,6 @@ void TestXdgShellClientRules::testDesktopDontAffect()
RuleBook::self()->setConfig(config);
workspace()->slotReconfigure();
// We need at least two virtual desktop for this test.
VirtualDesktopManager::self()->setCount(2);
QCOMPARE(VirtualDesktopManager::self()->count(), 2u);
VirtualDesktopManager::self()->setCurrent(1);
QCOMPARE(VirtualDesktopManager::self()->current(), 1);
// Create the test client.
AbstractClient *client;
Surface *surface;
@ -1821,12 +1821,18 @@ void TestXdgShellClientRules::testDesktopDontAffect()
void TestXdgShellClientRules::testDesktopApply()
{
// We need at least two virtual desktop for this test.
VirtualDesktopManager::self()->setCount(2);
QCOMPARE(VirtualDesktopManager::self()->count(), 2u);
VirtualDesktopManager::self()->setCurrent(1);
QCOMPARE(VirtualDesktopManager::self()->current(), 1);
// Initialize RuleBook with the test rule.
auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig);
config->group("General").writeEntry("count", 1);
KConfigGroup group = config->group("1");
group.writeEntry("desktop", 2);
group.writeEntry("desktoprule", int(Rules::Apply));
group.writeEntry("desktops", {VirtualDesktopManager::self()->desktopForX11Id(2)->id()});
group.writeEntry("desktopsrule", int(Rules::Apply));
group.writeEntry("wmclass", "org.kde.foo");
group.writeEntry("wmclasscomplete", false);
group.writeEntry("wmclassmatch", int(Rules::ExactMatch));
@ -1834,12 +1840,6 @@ void TestXdgShellClientRules::testDesktopApply()
RuleBook::self()->setConfig(config);
workspace()->slotReconfigure();
// We need at least two virtual desktop for this test.
VirtualDesktopManager::self()->setCount(2);
QCOMPARE(VirtualDesktopManager::self()->count(), 2u);
VirtualDesktopManager::self()->setCurrent(1);
QCOMPARE(VirtualDesktopManager::self()->current(), 1);
// Create the test client.
AbstractClient *client;
Surface *surface;
@ -1875,12 +1875,18 @@ void TestXdgShellClientRules::testDesktopApply()
void TestXdgShellClientRules::testDesktopRemember()
{
// We need at least two virtual desktop for this test.
VirtualDesktopManager::self()->setCount(2);
QCOMPARE(VirtualDesktopManager::self()->count(), 2u);
VirtualDesktopManager::self()->setCurrent(1);
QCOMPARE(VirtualDesktopManager::self()->current(), 1);
// Initialize RuleBook with the test rule.
auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig);
config->group("General").writeEntry("count", 1);
KConfigGroup group = config->group("1");
group.writeEntry("desktop", 2);
group.writeEntry("desktoprule", int(Rules::Remember));
group.writeEntry("desktops", {VirtualDesktopManager::self()->desktopForX11Id(2)->id()});
group.writeEntry("desktopsrule", int(Rules::Remember));
group.writeEntry("wmclass", "org.kde.foo");
group.writeEntry("wmclasscomplete", false);
group.writeEntry("wmclassmatch", int(Rules::ExactMatch));
@ -1888,12 +1894,6 @@ void TestXdgShellClientRules::testDesktopRemember()
RuleBook::self()->setConfig(config);
workspace()->slotReconfigure();
// We need at least two virtual desktop for this test.
VirtualDesktopManager::self()->setCount(2);
QCOMPARE(VirtualDesktopManager::self()->count(), 2u);
VirtualDesktopManager::self()->setCurrent(1);
QCOMPARE(VirtualDesktopManager::self()->current(), 1);
// Create the test client.
AbstractClient *client;
Surface *surface;
@ -1925,12 +1925,18 @@ void TestXdgShellClientRules::testDesktopRemember()
void TestXdgShellClientRules::testDesktopForce()
{
// We need at least two virtual desktop for this test.
VirtualDesktopManager::self()->setCount(2);
QCOMPARE(VirtualDesktopManager::self()->count(), 2u);
VirtualDesktopManager::self()->setCurrent(1);
QCOMPARE(VirtualDesktopManager::self()->current(), 1);
// Initialize RuleBook with the test rule.
auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig);
config->group("General").writeEntry("count", 1);
KConfigGroup group = config->group("1");
group.writeEntry("desktop", 2);
group.writeEntry("desktoprule", int(Rules::Force));
group.writeEntry("desktops", {VirtualDesktopManager::self()->desktopForX11Id(2)->id()});
group.writeEntry("desktopsrule", int(Rules::Force));
group.writeEntry("wmclass", "org.kde.foo");
group.writeEntry("wmclasscomplete", false);
group.writeEntry("wmclassmatch", int(Rules::ExactMatch));
@ -1938,11 +1944,6 @@ void TestXdgShellClientRules::testDesktopForce()
RuleBook::self()->setConfig(config);
workspace()->slotReconfigure();
// We need at least two virtual desktop for this test.
VirtualDesktopManager::self()->setCount(2);
QCOMPARE(VirtualDesktopManager::self()->count(), 2u);
VirtualDesktopManager::self()->setCurrent(1);
QCOMPARE(VirtualDesktopManager::self()->current(), 1);
// Create the test client.
AbstractClient *client;
@ -1998,8 +1999,8 @@ void TestXdgShellClientRules::testDesktopApplyNow()
auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig);
config->group("General").writeEntry("count", 1);
KConfigGroup group = config->group("1");
group.writeEntry("desktop", 2);
group.writeEntry("desktoprule", int(Rules::ApplyNow));
group.writeEntry("desktops", {VirtualDesktopManager::self()->desktopForX11Id(2)->id()});
group.writeEntry("desktopsrule", int(Rules::ApplyNow));
group.writeEntry("wmclass", "org.kde.foo");
group.writeEntry("wmclasscomplete", false);
group.writeEntry("wmclassmatch", int(Rules::ExactMatch));
@ -2029,12 +2030,18 @@ void TestXdgShellClientRules::testDesktopApplyNow()
void TestXdgShellClientRules::testDesktopForceTemporarily()
{
// We need at least two virtual desktop for this test.
VirtualDesktopManager::self()->setCount(2);
QCOMPARE(VirtualDesktopManager::self()->count(), 2u);
VirtualDesktopManager::self()->setCurrent(1);
QCOMPARE(VirtualDesktopManager::self()->current(), 1);
// Initialize RuleBook with the test rule.
auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig);
config->group("General").writeEntry("count", 1);
KConfigGroup group = config->group("1");
group.writeEntry("desktop", 2);
group.writeEntry("desktoprule", int(Rules::ForceTemporarily));
group.writeEntry("desktops", {VirtualDesktopManager::self()->desktopForX11Id(2)->id()});
group.writeEntry("desktopsrule", int(Rules::ForceTemporarily));
group.writeEntry("wmclass", "org.kde.foo");
group.writeEntry("wmclasscomplete", false);
group.writeEntry("wmclassmatch", int(Rules::ExactMatch));
@ -2042,12 +2049,6 @@ void TestXdgShellClientRules::testDesktopForceTemporarily()
RuleBook::self()->setConfig(config);
workspace()->slotReconfigure();
// We need at least two virtual desktop for this test.
VirtualDesktopManager::self()->setCount(2);
QCOMPARE(VirtualDesktopManager::self()->count(), 2u);
VirtualDesktopManager::self()->setCurrent(1);
QCOMPARE(VirtualDesktopManager::self()->current(), 1);
// Create the test client.
AbstractClient *client;
Surface *surface;

View File

@ -23,3 +23,5 @@ install(FILES kwinrules.upd
DESTINATION ${KDE_INSTALL_KCONFUPDATEDIR})
install(PROGRAMS kwinrules-5.19-placement.pl
DESTINATION ${KDE_INSTALL_KCONFUPDATEDIR})
install(PROGRAMS kwinrules-5.23-virtual-desktop-ids.py
DESTINATION ${KDE_INSTALL_KCONFUPDATEDIR})

View File

@ -0,0 +1,47 @@
#!/usr/bin/env python3
import fileinput
import configparser
import os.path
import subprocess
# Get the config standard locations
config_locations = subprocess.check_output(['qtpaths', '--paths', 'ConfigLocation']).decode('utf-8').strip().split(':')
config_paths = [os.path.join(folder, 'kwinrc') for folder in config_locations]
# Get the desktops information from `kwinrc` config file
kwinrc = configparser.ConfigParser(strict=False, allow_no_value=True)
kwinrc.read(config_paths)
num_desktops = int(kwinrc.get('Desktops', 'Number', fallback=''))
# Generete the map from x11ids (ennumeration) to UUIDs
desktopUUIDs = { -1: "" } # NET::OnAllDesktops -> Empty string
for i in range(1, num_desktops + 1):
uuid = kwinrc.get('Desktops', f'Id_{i}', fallback='')
desktopUUIDs[i] = str(uuid)
# Apply the conversion to `kwinrulesrc`
for line in fileinput.input():
# Rename key `desktoprule` to `desktopsrule`
if line.startswith("desktoprule="):
value = line[len("desktoprule="):].strip()
print("desktopsrule=" + value)
print("# DELETE desktoprule")
continue
if not line.startswith("desktop="):
print(line.strip())
continue
# Convert key `desktop` (x11id) to `desktops` (list of UUIDs)
try:
value = line[len("desktop="):].strip()
x11id = int(value)
uuid = desktopUUIDs[x11id]
except:
print(line.strip()) # If there is some error, print back the line
continue
print("desktops=" + uuid)
print("# DELETE desktop")

View File

@ -5,3 +5,7 @@ Id=replace-placement-string-to-enum
File=kwinrulesrc
Script=kwinrules-5.19-placement.pl,perl
# Replace the `desktop` x11id number to `desktops` list of desktop UUIDs
Id=use-virtual-desktop-ids
File=kwinrulesrc
Script=kwinrules-5.23-virtual-desktop-ids.py,python3

View File

@ -497,7 +497,7 @@ void AbstractClient::setDesktops(QVector<VirtualDesktop*> desktops)
doSetDesktop();
FocusChain::self()->update(this, FocusChain::MakeFirst);
updateWindowRules(Rules::Desktop);
updateWindowRules(Rules::Desktops);
Q_EMIT desktopChanged();
if (wasOnCurrentDesktop != isOnCurrentDesktop())
@ -572,6 +572,20 @@ QVector<uint> AbstractClient::x11DesktopIds() const
return x11Ids;
}
QStringList AbstractClient::desktopIds() const
{
const auto desks = desktops();
QStringList ids;
ids.reserve(desks.count());
std::transform(desks.constBegin(), desks.constEnd(),
std::back_inserter(ids),
[] (const VirtualDesktop *vd) {
return vd->id();
}
);
return ids;
};
ShadeMode AbstractClient::shadeMode() const
{
return m_shadeMode;

View File

@ -491,6 +491,7 @@ public:
return m_desktops;
}
QVector<uint> x11DesktopIds() const;
QStringList desktopIds() const;
void setMinimized(bool set);
/**

View File

@ -33,6 +33,7 @@ set(kwin_kcm_rules_XCB_LIBS
set(kcm_libs
Qt::Quick
Qt::QuickWidgets
Qt::X11Extras
KF5::KCMUtils
KF5::I18n

View File

@ -212,6 +212,9 @@ QVariant RuleItem::typedValue(const QVariant &value) const
case Size:
return value.toSize();
case String:
if (value.type() == QVariant::StringList && !value.toStringList().isEmpty()) {
return value.toStringList().at(0).trimmed();
}
return value.toString().trimmed();
case Shortcut:
return value.toString();

View File

@ -14,6 +14,7 @@
#include <QIcon>
#include <QQmlEngine>
#include <QtDBus>
#include <QX11Info>
#include <KColorSchemeManager>
#include <KConfig>
@ -433,14 +434,26 @@ void RulesModel::populateRuleList()
i18n("Maximized vertically"), i18n("Size & Position"),
QIcon::fromTheme("resizerow")));
auto desktop = addRule(new RuleItem(QLatin1String("desktop"),
RulePolicy::SetRule, RuleItem::Option,
i18n("Virtual Desktop"), i18n("Size & Position"),
QIcon::fromTheme("virtual-desktops")));
desktop->setOptionsData(virtualDesktopsModelData());
RuleItem *desktops;
if (QX11Info::isPlatformX11()) {
// Single selection of Virtual Desktop on X11
desktops = new RuleItem(QLatin1String("desktops"),
RulePolicy::SetRule, RuleItem::Option,
i18n("Virtual Desktop"), i18n("Size & Position"),
QIcon::fromTheme("virtual-desktops"));
} else {
// Multiple selection on Wayland
desktops = new RuleItem(QLatin1String("desktops"),
RulePolicy::SetRule, RuleItem::OptionList,
i18n("Virtual Desktops"), i18n("Size & Position"),
QIcon::fromTheme("virtual-desktops"));
}
addRule(desktops);
desktops->setOptionsData(virtualDesktopsModelData());
connect(this, &RulesModel::virtualDesktopsUpdated,
this, [this] { m_rules["desktop"]->setOptionsData(virtualDesktopsModelData()); });
this, [this] { m_rules["desktops"]->setOptionsData(virtualDesktopsModelData()); });
updateVirtualDesktops();
#ifdef KWIN_BUILD_ACTIVITIES
@ -645,7 +658,6 @@ const QHash<QString, QString> RulesModel::x11PropertyHash()
{ "caption", "title" },
{ "role", "windowrole" },
{ "clientMachine", "clientmachine" },
{ "x11DesktopNumber", "desktop" },
{ "maximizeHorizontal", "maximizehoriz" },
{ "maximizeVertical", "maximizevert" },
{ "minimized", "minimize" },
@ -687,6 +699,18 @@ void RulesModel::setSuggestedProperties(const QVariantMap &info)
m_rules["wmclass"]->setSuggestedValue(wmsimpleclass);
m_rules["wmclasshelper"]->setSuggestedValue(wmcompleteclass);
//TODO: Make the DBus method `queryWindowInfo` return the list of desktop IDs and use them directly
if (info.value("x11DesktopNumber").toInt() == NET::OnAllDesktops) {
m_rules["desktops"]->setSuggestedValue(QStringList());
} else {
for (const auto vd : qAsConst(m_virtualDesktops)) {
if (info.value("x11DesktopNumber").toUInt() == vd.position + 1) {
m_rules["desktops"]->setSuggestedValue(QStringList{ vd.id });
break;
}
}
}
#ifdef KWIN_BUILD_ACTIVITIES
const QStringList activities = info.value("activities").toStringList();
m_rules["activity"]->setSuggestedValue(activities.isEmpty() ? QStringList{ Activities::nullUuid() }
@ -729,15 +753,14 @@ QList<OptionsModel::Data> RulesModel::windowTypesModelData() const
QList<OptionsModel::Data> RulesModel::virtualDesktopsModelData() const
{
QList<OptionsModel::Data> modelData;
QList<OptionsModel::Data> modelData = { {QString(), i18n("All Desktops"), QIcon::fromTheme("window-pin")} };
for (const DBusDesktopDataStruct &desktop : m_virtualDesktops) {
modelData << OptionsModel::Data{
desktop.position + 1, // "desktop" setting uses the desktop position (int) starting at 1
desktop.id,
QString::number(desktop.position + 1).rightJustified(2) + QStringLiteral(": ") + desktop.name,
QIcon::fromTheme("virtual-desktops")
};
}
modelData << OptionsModel::Data{ NET::OnAllDesktops, i18n("All Desktops"), QIcon::fromTheme("window-pin") };
return modelData;
}

View File

@ -50,7 +50,7 @@ Rules::Rules()
, opacityactiverule(UnusedForceRule)
, opacityinactiverule(UnusedForceRule)
, ignoregeometryrule(UnusedSetRule)
, desktoprule(UnusedSetRule)
, desktopsrule(UnusedSetRule)
, screenrule(UnusedSetRule)
, activityrule(UnusedSetRule)
, typerule(UnusedForceRule)
@ -142,7 +142,7 @@ void Rules::readFromSettings(const RuleSettings *settings)
READ_FORCE_RULE(opacityactive,);
READ_FORCE_RULE(opacityinactive,);
READ_SET_RULE(ignoregeometry);
READ_SET_RULE(desktop);
READ_SET_RULE(desktops);
READ_SET_RULE(screen);
READ_SET_RULE(activity);
READ_FORCE_RULE(type, static_cast<NET::WindowType>);
@ -222,7 +222,7 @@ void Rules::write(RuleSettings *settings) const
WRITE_FORCE_RULE(opacityactive, Opacityactive,);
WRITE_FORCE_RULE(opacityinactive, Opacityinactive,);
WRITE_SET_RULE(ignoregeometry, Ignoregeometry,);
WRITE_SET_RULE(desktop, Desktop,);
WRITE_SET_RULE(desktops, Desktops,);
WRITE_SET_RULE(screen, Screen,);
WRITE_SET_RULE(activity, Activity,);
WRITE_FORCE_RULE(type, Type,);
@ -274,7 +274,7 @@ bool Rules::isEmpty() const
&& opacityactiverule == UnusedForceRule
&& opacityinactiverule == UnusedForceRule
&& ignoregeometryrule == UnusedSetRule
&& desktoprule == UnusedSetRule
&& desktopsrule == UnusedSetRule
&& screenrule == UnusedSetRule
&& activityrule == UnusedSetRule
&& typerule == UnusedForceRule
@ -445,9 +445,9 @@ bool Rules::update(AbstractClient* c, int selection)
size = new_size;
}
}
if NOW_REMEMBER(Desktop, desktop) {
updated = updated || desktop != c->desktop();
desktop = c->desktop();
if NOW_REMEMBER(Desktops, desktops) {
updated = updated || desktops != c->desktopIds();
desktops = c->desktopIds();
}
if NOW_REMEMBER(Screen, screen) {
updated = updated || screen != c->screen();
@ -568,20 +568,17 @@ APPLY_RULE(screen, Screen, int)
APPLY_RULE(activity, Activity, QStringList)
APPLY_FORCE_RULE(type, Type, NET::WindowType)
bool Rules::applyDesktops(QVector<VirtualDesktop *> &desktops, bool init) const
bool Rules::applyDesktops(QVector<VirtualDesktop *> &vds, bool init) const
{
if (checkSetRule(desktoprule, init)) {
if (desktop == NET::OnAllDesktops) {
desktops = {};
} else {
if (auto vd = VirtualDesktopManager::self()->desktopForX11Id(desktop)) {
desktops = {vd};
} else {
desktops = {VirtualDesktopManager::self()->currentDesktop()};
if (checkSetRule(desktopsrule, init)) {
vds.clear();
for (auto id : desktops) {
if (auto vd = VirtualDesktopManager::self()->desktopForId(id)) {
vds << vd;
}
}
}
return checkSetStop(desktoprule);
return checkSetStop(desktopsrule);
}
bool Rules::applyMaximizeHoriz(MaximizeMode& mode, bool init) const
@ -678,7 +675,7 @@ bool Rules::discardUsed(bool withdrawn)
DISCARD_USED_FORCE_RULE(opacityactive);
DISCARD_USED_FORCE_RULE(opacityinactive);
DISCARD_USED_SET_RULE(ignoregeometry);
DISCARD_USED_SET_RULE(desktop);
DISCARD_USED_SET_RULE(desktops);
DISCARD_USED_SET_RULE(screen);
DISCARD_USED_SET_RULE(activity);
DISCARD_USED_FORCE_RULE(type);

View File

@ -95,7 +95,7 @@ public:
explicit Rules(const RuleSettings*);
Rules(const QString&, bool temporary);
enum Type {
Position = 1<<0, Size = 1<<1, Desktop = 1<<2,
Position = 1<<0, Size = 1<<1, Desktops = 1<<2,
MaximizeVert = 1<<3, MaximizeHoriz = 1<<4, Minimize = 1<<5,
Shade = 1<<6, SkipTaskbar = 1<<7, SkipPager = 1<<8,
SkipSwitcher = 1<<9, Above = 1<<10, Below = 1<<11, Fullscreen = 1<<12,
@ -182,6 +182,9 @@ private:
bool matchRole(const QByteArray& match_role) const;
bool matchTitle(const QString& match_title) const;
bool matchClientMachine(const QByteArray& match_machine, bool local) const;
#ifdef KCMRULES
private:
#endif
void readFromSettings(const RuleSettings *settings);
static ForceRule convertForceRule(int v);
static QString getDecoColor(const QString &themeName);
@ -219,8 +222,8 @@ private:
ForceRule opacityinactiverule;
bool ignoregeometry;
SetRule ignoregeometryrule;
int desktop;
SetRule desktoprule;
QStringList desktops;
SetRule desktopsrule;
int screen;
SetRule screenrule;
QStringList activity;

View File

@ -147,12 +147,12 @@
<default code="true">Rules::UnusedSetRule</default>
</entry>
<entry name="desktop" type="Int">
<label>Desktop number</label>
<default>0</default>
<entry name="desktops" type="StringList">
<label>List of Desktop Ids</label>
<default>{}</default>
</entry>
<entry name="desktoprule" type="Int">
<label>Desktop number rule type</label>
<entry name="desktopsrule" type="Int">
<label>Desktop Ids rule type</label>
<min code="true">Rules::UnusedSetRule</min>
<max code="true">static_cast&lt;Rules::SetRule&gt;(Rules::ForceTemporarily)</max>
<default code="true">Rules::UnusedSetRule</default>