Introduce layer window rule

The new window rule allows to overwrite the stack layer. It can be
useful on wayland to force picture-in-picture surfaces (which are
xdg-toplevels at the moment) to be placed above fullscreen windows.
Keep above flag is unsuitable because fullscreen windows are placed
higher "above" windows.

CCBUG: 466016
This commit is contained in:
Vlad Zahorodnii 2024-01-16 23:54:17 +02:00
parent 74a193d383
commit 5c4acbdddd
9 changed files with 107 additions and 4 deletions

View file

@ -140,6 +140,7 @@ void TestDbusInterface::testGetWindowInfoXdgShellClient()
#if KWIN_BUILD_ACTIVITIES
{QStringLiteral("activities"), QStringList()},
#endif
{QStringLiteral("layer"), NormalLayer},
};
// let's get the window info
@ -276,6 +277,7 @@ void TestDbusInterface::testGetWindowInfoX11Client()
#if KWIN_BUILD_ACTIVITIES
{QStringLiteral("activities"), QStringList()},
#endif
{QStringLiteral("layer"), NormalLayer},
};
// let's get the window info

View file

@ -151,6 +151,10 @@ private Q_SLOTS:
void testScreenApplyNow();
void testScreenForceTemporarily();
void testLayerDontAffect();
void testLayerForce();
void testLayerForceTemporarily();
void testMatchAfterNameChange();
private:
@ -2980,5 +2984,42 @@ void TestXdgShellWindowRules::testMatchAfterNameChange()
QCOMPARE(window->keepAbove(), true);
}
void TestXdgShellWindowRules::testLayerDontAffect()
{
setWindowRule("layer", QStringLiteral("overlay"), int(Rules::DontAffect));
createTestWindow();
// The layer should not be affected by the rule.
QCOMPARE(m_window->layer(), NormalLayer);
destroyTestWindow();
}
void TestXdgShellWindowRules::testLayerForce()
{
setWindowRule("layer", QStringLiteral("overlay"), int(Rules::Force));
createTestWindow();
QCOMPARE(m_window->layer(), UnmanagedLayer);
destroyTestWindow();
}
void TestXdgShellWindowRules::testLayerForceTemporarily()
{
setWindowRule("layer", QStringLiteral("overlay"), int(Rules::ForceTemporarily));
createTestWindow();
QCOMPARE(m_window->layer(), UnmanagedLayer);
// The rule should be discarded when the window is closed.
destroyTestWindow();
createTestWindow();
QCOMPARE(m_window->layer(), NormalLayer);
destroyTestWindow();
}
WAYLANDTEST_MAIN(TestXdgShellWindowRules)
#include "xdgshellwindow_rules_test.moc"

View file

@ -180,6 +180,7 @@ QVariantMap clientToVariantMap(const Window *c)
#if KWIN_BUILD_ACTIVITIES
{QStringLiteral("activities"), c->activities()},
#endif
{QStringLiteral("layer"), c->layer()},
};
}
}

View file

@ -719,6 +719,12 @@ void RulesModel::populateRuleList()
RulePolicy::ForceRule, RuleItem::Boolean,
i18n("Block compositing"), i18n("Appearance & Fixes"),
QIcon::fromTheme("composite-track-on")));
auto layer = addRule(new RuleItem(QLatin1String("layer"),
RulePolicy::ForceRule, RuleItem::Option,
i18n("Layer"), i18n("Appearance & Fixes"),
QIcon::fromTheme("view-sort")));
layer->setOptionsData(layerModelData());
}
const QHash<QString, QString> RulesModel::x11PropertyHash()
@ -741,6 +747,7 @@ const QHash<QString, QString> RulesModel::x11PropertyHash()
{"type", "type"},
{"desktopFile", "desktopfile"},
{"desktops", "desktops"},
{"layer", "layer"},
};
return propertyToRule;
};
@ -911,6 +918,23 @@ QList<OptionsModel::Data> RulesModel::colorSchemesModelData() const
return modelData;
}
QList<OptionsModel::Data> RulesModel::layerModelData() const
{
static const auto modelData = QList<OptionsModel::Data>{
{DesktopLayer, i18n("Desktop")},
{BelowLayer, i18n("Below")},
{NormalLayer, i18n("Normal")},
{AboveLayer, i18n("Above")},
{NotificationLayer, i18n("Notification")},
{ActiveLayer, i18n("Fullscreen")},
{PopupLayer, i18n("Popup")},
{CriticalNotificationLayer, i18n("Critical Notification")},
{OnScreenDisplayLayer, i18n("OSD")},
{UnmanagedLayer, i18n("Overlay")},
};
return modelData;
}
void RulesModel::detectWindowProperties(int miliseconds)
{
QTimer::singleShot(miliseconds, this, &RulesModel::selectX11Window);

View file

@ -102,6 +102,7 @@ private:
QList<OptionsModel::Data> placementModelData() const;
QList<OptionsModel::Data> focusModelData() const;
QList<OptionsModel::Data> colorSchemesModelData() const;
QList<OptionsModel::Data> layerModelData() const;
private Q_SLOTS:
void selectX11Window();

View file

@ -33,7 +33,8 @@ namespace KWin
{
Rules::Rules()
: wmclassmatch(UnimportantMatch)
: layerrule(UnusedForceRule)
, wmclassmatch(UnimportantMatch)
, wmclasscomplete(UnimportantMatch)
, windowrolematch(UnimportantMatch)
, titlematch(UnimportantMatch)
@ -160,6 +161,7 @@ void Rules::readFromSettings(const RuleSettings *settings)
READ_SET_RULE(shortcut);
READ_FORCE_RULE(disableglobalshortcuts, );
READ_SET_RULE(desktopfile);
READ_FORCE_RULE(layer, );
}
#undef READ_MATCH_STRING
@ -238,6 +240,7 @@ void Rules::write(RuleSettings *settings) const
WRITE_SET_RULE(shortcut, Shortcut, );
WRITE_FORCE_RULE(disableglobalshortcuts, Disableglobalshortcuts, );
WRITE_SET_RULE(desktopfile, Desktopfile, );
WRITE_FORCE_RULE(layer, Layer, );
}
#undef WRITE_MATCH_STRING
@ -282,7 +285,8 @@ bool Rules::isEmpty() const
&& strictgeometryrule == UnusedForceRule
&& shortcutrule == UnusedSetRule
&& disableglobalshortcutsrule == UnusedForceRule
&& desktopfilerule == UnusedSetRule);
&& desktopfilerule == UnusedSetRule
&& layerrule == UnusedForceRule);
}
Rules::ForceRule Rules::convertForceRule(int v)
@ -575,6 +579,7 @@ APPLY_RULE(ignoregeometry, IgnoreGeometry, bool)
APPLY_RULE(screen, Screen, int)
APPLY_RULE(activity, Activity, QStringList)
APPLY_FORCE_RULE(type, Type, NET::WindowType)
APPLY_FORCE_RULE(layer, Layer, enum Layer)
bool Rules::applyDesktops(QList<VirtualDesktop *> &vds, bool init) const
{
@ -698,6 +703,7 @@ bool Rules::discardUsed(bool withdrawn)
DISCARD_USED_SET_RULE(shortcut);
DISCARD_USED_FORCE_RULE(disableglobalshortcuts);
DISCARD_USED_SET_RULE(desktopfile);
DISCARD_USED_FORCE_RULE(layer);
return changed;
}
@ -845,6 +851,7 @@ CHECK_FORCE_RULE(StrictGeometry, bool)
CHECK_RULE(Shortcut, QString)
CHECK_FORCE_RULE(DisableGlobalShortcuts, bool)
CHECK_RULE(DesktopFile, QString)
CHECK_FORCE_RULE(Layer, Layer)
#undef CHECK_RULE
#undef CHECK_FORCE_RULE

View file

@ -77,6 +77,7 @@ public:
QString checkShortcut(QString s, bool init = false) const;
bool checkDisableGlobalShortcuts(bool disable) const;
QString checkDesktopFile(QString desktopFile, bool init = false) const;
Layer checkLayer(Layer layer) const;
private:
MaximizeMode checkMaximizeVert(MaximizeMode mode, bool init) const;
@ -111,6 +112,7 @@ public:
Activity = 1 << 16,
Screen = 1 << 17,
DesktopFile = 1 << 18,
Layer = 1 << 19,
All = 0xffffffff
};
Q_DECLARE_FLAGS(Types, Type)
@ -184,6 +186,7 @@ public:
bool applyShortcut(QString &shortcut, bool init) const;
bool applyDisableGlobalShortcuts(bool &disable) const;
bool applyDesktopFile(QString &desktopFile, bool init) const;
bool applyLayer(enum Layer &layer) const;
private:
#endif
@ -204,6 +207,8 @@ private:
static bool checkSetStop(SetRule rule);
static bool checkForceStop(ForceRule rule);
#endif
enum Layer layer;
ForceRule layerrule;
QString description;
QString wmclass;
StringMatch wmclassmatch;

View file

@ -431,5 +431,26 @@
<max code="true">static_cast&lt;Rules::SetRule&gt;(Rules::ForceTemporarily)</max>
<default code="true">Rules::UnusedSetRule</default>
</entry>
<entry name="layer" type="Enum">
<label>Layer</label>
<choices name="KWin::Layer">
<choice name="DesktopLayer" value="desktop" />
<choice name="BelowLayer" value="below" />
<choice name="NormalLayer" value="normal" />
<choice name="AboveLayer" value="above" />
<choice name="NotificationLayer" value="notification" />
<choice name="ActiveLayer" value="fullscreen" />
<choice name="PopupLayer" value="popup" />
<choice name="CriticalNotificationLayer" value="critical-notification" />
<choice name="OnScreenDisplayLayer" value="osd" />
<choice name="UnmanagedLayer" value="overlay" />
</choices>
<default code="true">NormalLayer</default>
</entry>
<entry name="layerrule" type="Int">
<label>Layer rule type</label>
<default code="true">Rules::UnusedForceRule</default>
</entry>
</group>
</kcfg>

View file

@ -552,7 +552,7 @@ void Window::markAsDeleted()
Layer Window::layer() const
{
if (m_layer == UnknownLayer) {
const_cast<Window *>(this)->m_layer = belongsToLayer();
const_cast<Window *>(this)->m_layer = rules()->checkLayer(belongsToLayer());
}
return m_layer;
}
@ -562,7 +562,7 @@ void Window::updateLayer()
if (isDeleted()) {
return;
}
if (layer() == belongsToLayer()) {
if (layer() == rules()->checkLayer(belongsToLayer())) {
return;
}
StackingUpdatesBlocker blocker(workspace());
@ -4139,6 +4139,7 @@ void Window::applyWindowRules()
setFullScreen(isRequestedFullScreen());
setNoBorder(noBorder());
updateColorScheme();
updateLayer();
// FSP
// AcceptFocus :
if (workspace()->mostRecentlyActivatedWindow() == this