You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
920 lines
38 KiB
920 lines
38 KiB
/* |
|
SPDX-FileCopyrightText: 2004 Lubos Lunak <l.lunak@kde.org> |
|
SPDX-FileCopyrightText: 2020 Ismael Asensio <isma.af@gmail.com> |
|
|
|
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL |
|
*/ |
|
|
|
#include "rulesmodel.h" |
|
|
|
#if KWIN_BUILD_ACTIVITIES |
|
#include "activities.h" |
|
#endif |
|
|
|
#include <QIcon> |
|
#include <QQmlEngine> |
|
#include <QtDBus> |
|
|
|
#include <KColorSchemeManager> |
|
#include <KConfig> |
|
#include <KLocalizedString> |
|
#include <KWindowSystem> |
|
|
|
namespace KWin |
|
{ |
|
|
|
RulesModel::RulesModel(QObject *parent) |
|
: QAbstractListModel(parent) |
|
{ |
|
qmlRegisterUncreatableType<RuleItem>("org.kde.kcms.kwinrules", 1, 0, "RuleItem", |
|
QStringLiteral("Do not create objects of type RuleItem")); |
|
qmlRegisterUncreatableType<RulesModel>("org.kde.kcms.kwinrules", 1, 0, "RulesModel", |
|
QStringLiteral("Do not create objects of type RulesModel")); |
|
qmlRegisterUncreatableType<OptionsModel>("org.kde.kcms.kwinrules", 1, 0, "OptionsModel", |
|
QStringLiteral("Do not create objects of type OptionsModel")); |
|
|
|
qDBusRegisterMetaType<KWin::DBusDesktopDataStruct>(); |
|
qDBusRegisterMetaType<KWin::DBusDesktopDataVector>(); |
|
|
|
populateRuleList(); |
|
} |
|
|
|
RulesModel::~RulesModel() |
|
{ |
|
} |
|
|
|
QHash<int, QByteArray> RulesModel::roleNames() const |
|
{ |
|
return { |
|
{KeyRole, QByteArrayLiteral("key")}, |
|
{NameRole, QByteArrayLiteral("name")}, |
|
{IconRole, QByteArrayLiteral("icon")}, |
|
{IconNameRole, QByteArrayLiteral("iconName")}, |
|
{SectionRole, QByteArrayLiteral("section")}, |
|
{DescriptionRole, QByteArrayLiteral("description")}, |
|
{EnabledRole, QByteArrayLiteral("enabled")}, |
|
{SelectableRole, QByteArrayLiteral("selectable")}, |
|
{ValueRole, QByteArrayLiteral("value")}, |
|
{TypeRole, QByteArrayLiteral("type")}, |
|
{PolicyRole, QByteArrayLiteral("policy")}, |
|
{PolicyModelRole, QByteArrayLiteral("policyModel")}, |
|
{OptionsModelRole, QByteArrayLiteral("options")}, |
|
{SuggestedValueRole, QByteArrayLiteral("suggested")}, |
|
}; |
|
} |
|
|
|
int RulesModel::rowCount(const QModelIndex &parent) const |
|
{ |
|
if (parent.isValid()) { |
|
return 0; |
|
} |
|
return m_ruleList.size(); |
|
} |
|
|
|
QVariant RulesModel::data(const QModelIndex &index, int role) const |
|
{ |
|
if (!checkIndex(index, CheckIndexOption::IndexIsValid | CheckIndexOption::ParentIsInvalid)) { |
|
return QVariant(); |
|
} |
|
|
|
const RuleItem *rule = m_ruleList.at(index.row()); |
|
|
|
switch (role) { |
|
case KeyRole: |
|
return rule->key(); |
|
case NameRole: |
|
return rule->name(); |
|
case IconRole: |
|
return rule->icon(); |
|
case IconNameRole: |
|
return rule->iconName(); |
|
case DescriptionRole: |
|
return rule->description(); |
|
case SectionRole: |
|
return rule->section(); |
|
case EnabledRole: |
|
return rule->isEnabled(); |
|
case SelectableRole: |
|
return !rule->hasFlag(RuleItem::AlwaysEnabled) && !rule->hasFlag(RuleItem::SuggestionOnly); |
|
case ValueRole: |
|
return rule->value(); |
|
case TypeRole: |
|
return rule->type(); |
|
case PolicyRole: |
|
return rule->policy(); |
|
case PolicyModelRole: |
|
return rule->policyModel(); |
|
case OptionsModelRole: |
|
return rule->options(); |
|
case SuggestedValueRole: |
|
return rule->suggestedValue(); |
|
} |
|
return QVariant(); |
|
} |
|
|
|
bool RulesModel::setData(const QModelIndex &index, const QVariant &value, int role) |
|
{ |
|
if (!checkIndex(index, CheckIndexOption::IndexIsValid | CheckIndexOption::ParentIsInvalid)) { |
|
return false; |
|
} |
|
|
|
RuleItem *rule = m_ruleList.at(index.row()); |
|
|
|
switch (role) { |
|
case EnabledRole: |
|
if (value.toBool() == rule->isEnabled()) { |
|
return true; |
|
} |
|
rule->setEnabled(value.toBool()); |
|
break; |
|
case ValueRole: |
|
if (rule->hasFlag(RuleItem::SuggestionOnly)) { |
|
processSuggestion(rule->key(), value); |
|
} |
|
if (value == rule->value()) { |
|
return true; |
|
} |
|
rule->setValue(value); |
|
break; |
|
case PolicyRole: |
|
if (value.toInt() == rule->policy()) { |
|
return true; |
|
} |
|
rule->setPolicy(value.toInt()); |
|
break; |
|
case SuggestedValueRole: |
|
if (value == rule->suggestedValue()) { |
|
return true; |
|
} |
|
rule->setSuggestedValue(value); |
|
break; |
|
default: |
|
return false; |
|
} |
|
|
|
writeToSettings(rule); |
|
|
|
Q_EMIT dataChanged(index, index, QVector<int>{role}); |
|
if (rule->hasFlag(RuleItem::AffectsDescription)) { |
|
Q_EMIT descriptionChanged(); |
|
} |
|
if (rule->hasFlag(RuleItem::AffectsWarning)) { |
|
Q_EMIT warningMessagesChanged(); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
QModelIndex RulesModel::indexOf(const QString &key) const |
|
{ |
|
const QModelIndexList indexes = match(index(0), RulesModel::KeyRole, key, 1, Qt::MatchFixedString); |
|
if (indexes.isEmpty()) { |
|
return QModelIndex(); |
|
} |
|
return indexes.at(0); |
|
} |
|
|
|
RuleItem *RulesModel::addRule(RuleItem *rule) |
|
{ |
|
m_ruleList << rule; |
|
m_rules.insert(rule->key(), rule); |
|
|
|
return rule; |
|
} |
|
|
|
bool RulesModel::hasRule(const QString &key) const |
|
{ |
|
return m_rules.contains(key); |
|
} |
|
|
|
RuleItem *RulesModel::ruleItem(const QString &key) const |
|
{ |
|
return m_rules.value(key); |
|
} |
|
|
|
QString RulesModel::description() const |
|
{ |
|
const QString desc = m_rules["description"]->value().toString(); |
|
if (!desc.isEmpty()) { |
|
return desc; |
|
} |
|
return defaultDescription(); |
|
} |
|
|
|
void RulesModel::setDescription(const QString &description) |
|
{ |
|
setData(indexOf("description"), description, RulesModel::ValueRole); |
|
} |
|
|
|
QString RulesModel::defaultDescription() const |
|
{ |
|
const QString wmclass = m_rules["wmclass"]->value().toString(); |
|
const QString title = m_rules["title"]->isEnabled() ? m_rules["title"]->value().toString() : QString(); |
|
|
|
if (!title.isEmpty()) { |
|
return i18n("Window settings for %1", title); |
|
} |
|
if (!wmclass.isEmpty()) { |
|
return i18n("Settings for %1", wmclass); |
|
} |
|
|
|
return i18n("New window settings"); |
|
} |
|
|
|
void RulesModel::processSuggestion(const QString &key, const QVariant &value) |
|
{ |
|
if (key == QLatin1String("wmclasshelper")) { |
|
setData(indexOf("wmclass"), value, RulesModel::ValueRole); |
|
setData(indexOf("wmclasscomplete"), true, RulesModel::ValueRole); |
|
} |
|
} |
|
|
|
QStringList RulesModel::warningMessages() const |
|
{ |
|
QStringList messages; |
|
|
|
if (wmclassWarning()) { |
|
messages << i18n("You have specified the window class as unimportant.\n" |
|
"This means the settings will possibly apply to windows from all applications." |
|
" If you really want to create a generic setting, it is recommended" |
|
" you at least limit the window types to avoid special window types."); |
|
} |
|
|
|
if (geometryWarning()) { |
|
messages << i18n("Some applications set their own geometry after starting," |
|
" overriding your initial settings for size and position. " |
|
"To enforce these settings, also force the property \"%1\" to \"Yes\".", |
|
m_rules["ignoregeometry"]->name()); |
|
} |
|
|
|
if (opacityWarning()) { |
|
messages << i18n("Readability may be impaired with extremely low opacity values. At 0%, the window becomes invisible."); |
|
} |
|
|
|
return messages; |
|
} |
|
|
|
bool RulesModel::wmclassWarning() const |
|
{ |
|
const bool no_wmclass = !m_rules["wmclass"]->isEnabled() |
|
|| m_rules["wmclass"]->policy() == Rules::UnimportantMatch; |
|
const bool alltypes = !m_rules["types"]->isEnabled() |
|
|| (m_rules["types"]->value() == 0) |
|
|| (m_rules["types"]->value() == NET::AllTypesMask) |
|
|| ((m_rules["types"]->value().toInt() | (1 << NET::Override)) == 0x3FF); |
|
|
|
return (no_wmclass && alltypes); |
|
} |
|
|
|
bool RulesModel::geometryWarning() const |
|
{ |
|
const bool ignoregeometry = m_rules["ignoregeometry"]->isEnabled() |
|
&& m_rules["ignoregeometry"]->policy() == Rules::Force |
|
&& m_rules["ignoregeometry"]->value() == true; |
|
|
|
const bool initialPos = m_rules["position"]->isEnabled() |
|
&& (m_rules["position"]->policy() == Rules::Apply |
|
|| m_rules["position"]->policy() == Rules::Remember); |
|
|
|
const bool initialSize = m_rules["size"]->isEnabled() |
|
&& (m_rules["size"]->policy() == Rules::Apply |
|
|| m_rules["size"]->policy() == Rules::Remember); |
|
|
|
const bool initialPlacement = m_rules["placement"]->isEnabled() |
|
&& m_rules["placement"]->policy() == Rules::Force; |
|
|
|
return (!ignoregeometry && (initialPos || initialSize || initialPlacement)); |
|
} |
|
|
|
bool RulesModel::opacityWarning() const |
|
{ |
|
auto opacityActive = m_rules["opacityactive"]; |
|
const bool lowOpacityActive = opacityActive->isEnabled() |
|
&& opacityActive->policy() != Rules::Unused && opacityActive->policy() != Rules::DontAffect |
|
&& opacityActive->value().toInt() < 25; |
|
|
|
auto opacityInactive = m_rules["opacityinactive"]; |
|
const bool lowOpacityInactive = opacityInactive->isEnabled() |
|
&& opacityActive->policy() != Rules::Unused && opacityActive->policy() != Rules::DontAffect |
|
&& opacityInactive->value().toInt() < 25; |
|
|
|
return lowOpacityActive || lowOpacityInactive; |
|
} |
|
|
|
RuleSettings *RulesModel::settings() const |
|
{ |
|
return m_settings; |
|
} |
|
|
|
void RulesModel::setSettings(RuleSettings *settings) |
|
{ |
|
if (m_settings == settings) { |
|
return; |
|
} |
|
|
|
beginResetModel(); |
|
|
|
m_settings = settings; |
|
|
|
for (RuleItem *rule : std::as_const(m_ruleList)) { |
|
const KConfigSkeletonItem *configItem = m_settings->findItem(rule->key()); |
|
const KConfigSkeletonItem *configPolicyItem = m_settings->findItem(rule->policyKey()); |
|
|
|
rule->reset(); |
|
|
|
if (!configItem) { |
|
continue; |
|
} |
|
|
|
const bool isEnabled = configPolicyItem ? configPolicyItem->property() != Rules::Unused |
|
: !configItem->property().toString().isEmpty(); |
|
rule->setEnabled(isEnabled); |
|
|
|
const QVariant value = configItem->property(); |
|
rule->setValue(value); |
|
|
|
if (configPolicyItem) { |
|
const int policy = configPolicyItem->property().toInt(); |
|
rule->setPolicy(policy); |
|
} |
|
} |
|
|
|
endResetModel(); |
|
|
|
Q_EMIT descriptionChanged(); |
|
Q_EMIT warningMessagesChanged(); |
|
} |
|
|
|
void RulesModel::writeToSettings(RuleItem *rule) |
|
{ |
|
KConfigSkeletonItem *configItem = m_settings->findItem(rule->key()); |
|
KConfigSkeletonItem *configPolicyItem = m_settings->findItem(rule->policyKey()); |
|
|
|
if (!configItem) { |
|
return; |
|
} |
|
|
|
if (rule->isEnabled()) { |
|
configItem->setProperty(rule->value()); |
|
if (configPolicyItem) { |
|
configPolicyItem->setProperty(rule->policy()); |
|
} |
|
} else { |
|
configItem->setDefault(); |
|
if (configPolicyItem) { |
|
configPolicyItem->setDefault(); |
|
} |
|
} |
|
} |
|
|
|
void RulesModel::populateRuleList() |
|
{ |
|
qDeleteAll(m_ruleList); |
|
m_ruleList.clear(); |
|
|
|
// Rule description |
|
auto description = addRule(new RuleItem(QLatin1String("description"), |
|
RulePolicy::NoPolicy, RuleItem::String, |
|
i18n("Description"), i18n("Window matching"), |
|
QIcon::fromTheme("entry-edit"))); |
|
description->setFlag(RuleItem::AlwaysEnabled); |
|
description->setFlag(RuleItem::AffectsDescription); |
|
|
|
// Window matching |
|
auto wmclass = addRule(new RuleItem(QLatin1String("wmclass"), |
|
RulePolicy::StringMatch, RuleItem::String, |
|
i18n("Window class (application)"), i18n("Window matching"), |
|
QIcon::fromTheme("window"))); |
|
wmclass->setFlag(RuleItem::AlwaysEnabled); |
|
wmclass->setFlag(RuleItem::AffectsDescription); |
|
wmclass->setFlag(RuleItem::AffectsWarning); |
|
|
|
auto wmclasscomplete = addRule(new RuleItem(QLatin1String("wmclasscomplete"), |
|
RulePolicy::NoPolicy, RuleItem::Boolean, |
|
i18n("Match whole window class"), i18n("Window matching"), |
|
QIcon::fromTheme("window"))); |
|
wmclasscomplete->setFlag(RuleItem::AlwaysEnabled); |
|
|
|
// Helper item to store the detected whole window class when detecting properties |
|
auto wmclasshelper = addRule(new RuleItem(QLatin1String("wmclasshelper"), |
|
RulePolicy::NoPolicy, RuleItem::String, |
|
i18n("Whole window class"), i18n("Window matching"), |
|
QIcon::fromTheme("window"))); |
|
wmclasshelper->setFlag(RuleItem::SuggestionOnly); |
|
|
|
auto types = addRule(new RuleItem(QLatin1String("types"), |
|
RulePolicy::NoPolicy, RuleItem::NetTypes, |
|
i18n("Window types"), i18n("Window matching"), |
|
QIcon::fromTheme("window-duplicate"))); |
|
types->setOptionsData(windowTypesModelData()); |
|
types->setFlag(RuleItem::AlwaysEnabled); |
|
types->setFlag(RuleItem::AffectsWarning); |
|
|
|
addRule(new RuleItem(QLatin1String("windowrole"), |
|
RulePolicy::StringMatch, RuleItem::String, |
|
i18n("Window role"), i18n("Window matching"), |
|
QIcon::fromTheme("dialog-object-properties"))); |
|
|
|
auto title = addRule(new RuleItem(QLatin1String("title"), |
|
RulePolicy::StringMatch, RuleItem::String, |
|
i18n("Window title"), i18n("Window matching"), |
|
QIcon::fromTheme("edit-comment"))); |
|
title->setFlag(RuleItem::AffectsDescription); |
|
|
|
addRule(new RuleItem(QLatin1String("clientmachine"), |
|
RulePolicy::StringMatch, RuleItem::String, |
|
i18n("Machine (hostname)"), i18n("Window matching"), |
|
QIcon::fromTheme("computer"))); |
|
|
|
// Size & Position |
|
auto position = addRule(new RuleItem(QLatin1String("position"), |
|
RulePolicy::SetRule, RuleItem::Point, |
|
i18n("Position"), i18n("Size & Position"), |
|
QIcon::fromTheme("transform-move"))); |
|
position->setFlag(RuleItem::AffectsWarning); |
|
|
|
auto size = addRule(new RuleItem(QLatin1String("size"), |
|
RulePolicy::SetRule, RuleItem::Size, |
|
i18n("Size"), i18n("Size & Position"), |
|
QIcon::fromTheme("transform-scale"))); |
|
size->setFlag(RuleItem::AffectsWarning); |
|
|
|
addRule(new RuleItem(QLatin1String("maximizehoriz"), |
|
RulePolicy::SetRule, RuleItem::Boolean, |
|
i18n("Maximized horizontally"), i18n("Size & Position"), |
|
QIcon::fromTheme("resizecol"))); |
|
|
|
addRule(new RuleItem(QLatin1String("maximizevert"), |
|
RulePolicy::SetRule, RuleItem::Boolean, |
|
i18n("Maximized vertically"), i18n("Size & Position"), |
|
QIcon::fromTheme("resizerow"))); |
|
|
|
RuleItem *desktops; |
|
if (KWindowSystem::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["desktops"]->setOptionsData(virtualDesktopsModelData()); |
|
const QModelIndex index = indexOf("desktops"); |
|
Q_EMIT dataChanged(index, index, {OptionsModelRole}); |
|
}); |
|
|
|
updateVirtualDesktops(); |
|
|
|
#if KWIN_BUILD_ACTIVITIES |
|
m_activities = new KActivities::Consumer(this); |
|
|
|
auto activity = addRule(new RuleItem(QLatin1String("activity"), |
|
RulePolicy::SetRule, RuleItem::OptionList, |
|
i18n("Activities"), i18n("Size & Position"), |
|
QIcon::fromTheme("activities"))); |
|
activity->setOptionsData(activitiesModelData()); |
|
|
|
// Activites consumer may update the available activities later |
|
auto updateActivities = [this]() { |
|
m_rules["activity"]->setOptionsData(activitiesModelData()); |
|
const QModelIndex index = indexOf("activity"); |
|
Q_EMIT dataChanged(index, index, {OptionsModelRole}); |
|
}; |
|
connect(m_activities, &KActivities::Consumer::activitiesChanged, this, updateActivities); |
|
connect(m_activities, &KActivities::Consumer::serviceStatusChanged, this, updateActivities); |
|
#endif |
|
|
|
addRule(new RuleItem(QLatin1String("screen"), |
|
RulePolicy::SetRule, RuleItem::Integer, |
|
i18n("Screen"), i18n("Size & Position"), |
|
QIcon::fromTheme("osd-shutd-screen"))); |
|
|
|
addRule(new RuleItem(QLatin1String("fullscreen"), |
|
RulePolicy::SetRule, RuleItem::Boolean, |
|
i18n("Fullscreen"), i18n("Size & Position"), |
|
QIcon::fromTheme("view-fullscreen"))); |
|
|
|
addRule(new RuleItem(QLatin1String("minimize"), |
|
RulePolicy::SetRule, RuleItem::Boolean, |
|
i18n("Minimized"), i18n("Size & Position"), |
|
QIcon::fromTheme("window-minimize"))); |
|
|
|
addRule(new RuleItem(QLatin1String("shade"), |
|
RulePolicy::SetRule, RuleItem::Boolean, |
|
i18n("Shaded"), i18n("Size & Position"), |
|
QIcon::fromTheme("window-shade"))); |
|
|
|
auto placement = addRule(new RuleItem(QLatin1String("placement"), |
|
RulePolicy::ForceRule, RuleItem::Option, |
|
i18n("Initial placement"), i18n("Size & Position"), |
|
QIcon::fromTheme("region"))); |
|
placement->setOptionsData(placementModelData()); |
|
placement->setFlag(RuleItem::AffectsWarning); |
|
|
|
auto ignoregeometry = addRule(new RuleItem(QLatin1String("ignoregeometry"), |
|
RulePolicy::SetRule, RuleItem::Boolean, |
|
i18n("Ignore requested geometry"), i18n("Size & Position"), |
|
QIcon::fromTheme("view-time-schedule-baselined-remove"), |
|
i18n("Windows can ask to appear in a certain position. " |
|
"By default this overrides the placement strategy " |
|
"what might be nasty if the client abuses the feature " |
|
"to unconditionally popup in the middle of your screen."))); |
|
ignoregeometry->setFlag(RuleItem::AffectsWarning); |
|
|
|
addRule(new RuleItem(QLatin1String("minsize"), |
|
RulePolicy::ForceRule, RuleItem::Size, |
|
i18n("Minimum Size"), i18n("Size & Position"), |
|
QIcon::fromTheme("transform-scale"))); |
|
|
|
addRule(new RuleItem(QLatin1String("maxsize"), |
|
RulePolicy::ForceRule, RuleItem::Size, |
|
i18n("Maximum Size"), i18n("Size & Position"), |
|
QIcon::fromTheme("transform-scale"))); |
|
|
|
addRule(new RuleItem(QLatin1String("strictgeometry"), |
|
RulePolicy::ForceRule, RuleItem::Boolean, |
|
i18n("Obey geometry restrictions"), i18n("Size & Position"), |
|
QIcon::fromTheme("transform-crop-and-resize"), |
|
i18n("Eg. terminals or video players can ask to keep a certain aspect ratio " |
|
"or only grow by values larger than one " |
|
"(eg. by the dimensions of one character). " |
|
"This may be pointless and the restriction prevents arbitrary dimensions " |
|
"like your complete screen area."))); |
|
|
|
// Arrangement & Access |
|
addRule(new RuleItem(QLatin1String("above"), |
|
RulePolicy::SetRule, RuleItem::Boolean, |
|
i18n("Keep above other windows"), i18n("Arrangement & Access"), |
|
QIcon::fromTheme("window-keep-above"))); |
|
|
|
addRule(new RuleItem(QLatin1String("below"), |
|
RulePolicy::SetRule, RuleItem::Boolean, |
|
i18n("Keep below other windows"), i18n("Arrangement & Access"), |
|
QIcon::fromTheme("window-keep-below"))); |
|
|
|
addRule(new RuleItem(QLatin1String("skiptaskbar"), |
|
RulePolicy::SetRule, RuleItem::Boolean, |
|
i18n("Skip taskbar"), i18n("Arrangement & Access"), |
|
QIcon::fromTheme("kt-show-statusbar"), |
|
i18n("Window shall (not) appear in the taskbar."))); |
|
|
|
addRule(new RuleItem(QLatin1String("skippager"), |
|
RulePolicy::SetRule, RuleItem::Boolean, |
|
i18n("Skip pager"), i18n("Arrangement & Access"), |
|
QIcon::fromTheme("org.kde.plasma.pager"), |
|
i18n("Window shall (not) appear in the manager for virtual desktops"))); |
|
|
|
addRule(new RuleItem(QLatin1String("skipswitcher"), |
|
RulePolicy::SetRule, RuleItem::Boolean, |
|
i18n("Skip switcher"), i18n("Arrangement & Access"), |
|
QIcon::fromTheme("preferences-system-windows-effect-flipswitch"), |
|
i18n("Window shall (not) appear in the Alt+Tab list"))); |
|
|
|
addRule(new RuleItem(QLatin1String("shortcut"), |
|
RulePolicy::SetRule, RuleItem::Shortcut, |
|
i18n("Shortcut"), i18n("Arrangement & Access"), |
|
QIcon::fromTheme("configure-shortcuts"))); |
|
|
|
// Appearance & Fixes |
|
addRule(new RuleItem(QLatin1String("noborder"), |
|
RulePolicy::SetRule, RuleItem::Boolean, |
|
i18n("No titlebar and frame"), i18n("Appearance & Fixes"), |
|
QIcon::fromTheme("dialog-cancel"))); |
|
|
|
auto decocolor = addRule(new RuleItem(QLatin1String("decocolor"), |
|
RulePolicy::ForceRule, RuleItem::Option, |
|
i18n("Titlebar color scheme"), i18n("Appearance & Fixes"), |
|
QIcon::fromTheme("preferences-desktop-theme"))); |
|
decocolor->setOptionsData(colorSchemesModelData()); |
|
|
|
auto opacityactive = addRule(new RuleItem(QLatin1String("opacityactive"), |
|
RulePolicy::ForceRule, RuleItem::Percentage, |
|
i18n("Active opacity"), i18n("Appearance & Fixes"), |
|
QIcon::fromTheme("edit-opacity"))); |
|
opacityactive->setFlag(RuleItem::AffectsWarning); |
|
auto opacityinactive = addRule(new RuleItem(QLatin1String("opacityinactive"), |
|
RulePolicy::ForceRule, RuleItem::Percentage, |
|
i18n("Inactive opacity"), i18n("Appearance & Fixes"), |
|
QIcon::fromTheme("edit-opacity"))); |
|
opacityinactive->setFlag(RuleItem::AffectsWarning); |
|
|
|
auto fsplevel = addRule(new RuleItem(QLatin1String("fsplevel"), |
|
RulePolicy::ForceRule, RuleItem::Option, |
|
i18n("Focus stealing prevention"), i18n("Appearance & Fixes"), |
|
QIcon::fromTheme("preferences-system-windows-effect-glide"), |
|
i18n("KWin tries to prevent windows from taking the focus " |
|
"(\"activate\") while you're working in another window, " |
|
"but this may sometimes fail or superact. " |
|
"\"None\" will unconditionally allow this window to get the focus while " |
|
"\"Extreme\" will completely prevent it from taking the focus."))); |
|
fsplevel->setOptionsData(focusModelData()); |
|
|
|
auto fpplevel = addRule(new RuleItem(QLatin1String("fpplevel"), |
|
RulePolicy::ForceRule, RuleItem::Option, |
|
i18n("Focus protection"), i18n("Appearance & Fixes"), |
|
QIcon::fromTheme("preferences-system-windows-effect-minimize"), |
|
i18n("This controls the focus protection of the currently active window. " |
|
"None will always give the focus away, " |
|
"Extreme will keep it. " |
|
"Otherwise it's interleaved with the stealing prevention " |
|
"assigned to the window that wants the focus."))); |
|
fpplevel->setOptionsData(focusModelData()); |
|
|
|
addRule(new RuleItem(QLatin1String("acceptfocus"), |
|
RulePolicy::ForceRule, RuleItem::Boolean, |
|
i18n("Accept focus"), i18n("Appearance & Fixes"), |
|
QIcon::fromTheme("preferences-desktop-cursors"), |
|
i18n("Windows may prevent to get the focus (activate) when being clicked. " |
|
"On the other hand you might wish to prevent a window " |
|
"from getting focused on a mouse click."))); |
|
|
|
addRule(new RuleItem(QLatin1String("disableglobalshortcuts"), |
|
RulePolicy::ForceRule, RuleItem::Boolean, |
|
i18n("Ignore global shortcuts"), i18n("Appearance & Fixes"), |
|
QIcon::fromTheme("input-keyboard-virtual-off"), |
|
i18n("When used, a window will receive " |
|
"all keyboard inputs while it is active, including Alt+Tab etc. " |
|
"This is especially interesting for emulators or virtual machines. " |
|
"\n" |
|
"Be warned: " |
|
"you won't be able to Alt+Tab out of the window " |
|
"nor use any other global shortcut (such as Alt+F2 to show KRunner) " |
|
"while it's active!"))); |
|
|
|
addRule(new RuleItem(QLatin1String("closeable"), |
|
RulePolicy::ForceRule, RuleItem::Boolean, |
|
i18n("Closeable"), i18n("Appearance & Fixes"), |
|
QIcon::fromTheme("dialog-close"))); |
|
|
|
auto type = addRule(new RuleItem(QLatin1String("type"), |
|
RulePolicy::ForceRule, RuleItem::Option, |
|
i18n("Set window type"), i18n("Appearance & Fixes"), |
|
QIcon::fromTheme("window-duplicate"))); |
|
type->setOptionsData(windowTypesModelData()); |
|
|
|
addRule(new RuleItem(QLatin1String("desktopfile"), |
|
RulePolicy::SetRule, RuleItem::String, |
|
i18n("Desktop file name"), i18n("Appearance & Fixes"), |
|
QIcon::fromTheme("application-x-desktop"))); |
|
|
|
addRule(new RuleItem(QLatin1String("blockcompositing"), |
|
RulePolicy::ForceRule, RuleItem::Boolean, |
|
i18n("Block compositing"), i18n("Appearance & Fixes"), |
|
QIcon::fromTheme("composite-track-on"))); |
|
} |
|
|
|
const QHash<QString, QString> RulesModel::x11PropertyHash() |
|
{ |
|
static const auto propertyToRule = QHash<QString, QString>{ |
|
{"caption", "title"}, |
|
{"role", "windowrole"}, |
|
{"clientMachine", "clientmachine"}, |
|
{"maximizeHorizontal", "maximizehoriz"}, |
|
{"maximizeVertical", "maximizevert"}, |
|
{"minimized", "minimize"}, |
|
{"shaded", "shade"}, |
|
{"fullscreen", "fullscreen"}, |
|
{"keepAbove", "above"}, |
|
{"keepBelow", "below"}, |
|
{"noBorder", "noborder"}, |
|
{"skipTaskbar", "skiptaskbar"}, |
|
{"skipPager", "skippager"}, |
|
{"skipSwitcher", "skipswitcher"}, |
|
{"type", "type"}, |
|
{"desktopFile", "desktopfile"}, |
|
{"desktops", "desktops"}, |
|
}; |
|
return propertyToRule; |
|
}; |
|
|
|
void RulesModel::setSuggestedProperties(const QVariantMap &info) |
|
{ |
|
// Properties that cannot be directly applied via x11PropertyHash |
|
const QPoint position = QPoint(info.value("x").toInt(), info.value("y").toInt()); |
|
const QSize size = QSize(info.value("width").toInt(), info.value("height").toInt()); |
|
|
|
m_rules["position"]->setSuggestedValue(position); |
|
m_rules["size"]->setSuggestedValue(size); |
|
m_rules["minsize"]->setSuggestedValue(size); |
|
m_rules["maxsize"]->setSuggestedValue(size); |
|
|
|
NET::WindowType window_type = static_cast<NET::WindowType>(info.value("type", 0).toInt()); |
|
if (window_type == NET::Unknown) { |
|
window_type = NET::Normal; |
|
} |
|
m_rules["types"]->setSuggestedValue(1 << window_type); |
|
|
|
const QString wmsimpleclass = info.value("resourceClass").toString(); |
|
const QString wmcompleteclass = QStringLiteral("%1 %2").arg(info.value("resourceName").toString(), |
|
info.value("resourceClass").toString()); |
|
|
|
// This window is not providing the class according to spec (WM_CLASS on X11, appId on Wayland) |
|
// Notify the user that this is a bug within the application, so there's nothing we can do |
|
if (wmsimpleclass.isEmpty()) { |
|
Q_EMIT showErrorMessage(i18n("Window class not available"), |
|
xi18nc("@info", "This application is not providing a class for the window, " |
|
"so KWin cannot use it to match and apply any rules. " |
|
"If you still want to apply some rules to it, " |
|
"try to match other properties like the window title instead.<nl/><nl/>" |
|
"Please consider reporting this bug to the application's developers.")); |
|
} |
|
|
|
m_rules["wmclass"]->setSuggestedValue(wmsimpleclass); |
|
m_rules["wmclasshelper"]->setSuggestedValue(wmcompleteclass); |
|
|
|
#if KWIN_BUILD_ACTIVITIES |
|
const QStringList activities = info.value("activities").toStringList(); |
|
m_rules["activity"]->setSuggestedValue(activities.isEmpty() ? QStringList{Activities::nullUuid()} |
|
: activities); |
|
#endif |
|
|
|
const auto ruleForProperty = x11PropertyHash(); |
|
for (QString &property : info.keys()) { |
|
if (!ruleForProperty.contains(property)) { |
|
continue; |
|
} |
|
const QString ruleKey = ruleForProperty.value(property, QString()); |
|
Q_ASSERT(hasRule(ruleKey)); |
|
|
|
m_rules[ruleKey]->setSuggestedValue(info.value(property)); |
|
} |
|
|
|
Q_EMIT dataChanged(index(0), index(rowCount() - 1), {RulesModel::SuggestedValueRole}); |
|
} |
|
|
|
QList<OptionsModel::Data> RulesModel::windowTypesModelData() const |
|
{ |
|
static const auto modelData = QList<OptionsModel::Data>{ |
|
// TODO: Find/create better icons |
|
{0, i18n("All Window Types"), {}, {}, OptionsModel::SelectAllOption}, |
|
{1 << NET::Normal, i18n("Normal Window"), QIcon::fromTheme("window")}, |
|
{1 << NET::Dialog, i18n("Dialog Window"), QIcon::fromTheme("window-duplicate")}, |
|
{1 << NET::Utility, i18n("Utility Window"), QIcon::fromTheme("dialog-object-properties")}, |
|
{1 << NET::Dock, i18n("Dock (panel)"), QIcon::fromTheme("list-remove")}, |
|
{1 << NET::Toolbar, i18n("Toolbar"), QIcon::fromTheme("tools")}, |
|
{1 << NET::Menu, i18n("Torn-Off Menu"), QIcon::fromTheme("overflow-menu-left")}, |
|
{1 << NET::Splash, i18n("Splash Screen"), QIcon::fromTheme("embosstool")}, |
|
{1 << NET::Desktop, i18n("Desktop"), QIcon::fromTheme("desktop")}, |
|
// {1 << NET::Override, i18n("Unmanaged Window")}, deprecated |
|
{1 << NET::TopMenu, i18n("Standalone Menubar"), QIcon::fromTheme("application-menu")}, |
|
{1 << NET::OnScreenDisplay, i18n("On Screen Display"), QIcon::fromTheme("osd-duplicate")}}; |
|
|
|
return modelData; |
|
} |
|
|
|
QList<OptionsModel::Data> RulesModel::virtualDesktopsModelData() const |
|
{ |
|
QList<OptionsModel::Data> modelData; |
|
modelData << OptionsModel::Data{ |
|
QString(), |
|
i18n("All Desktops"), |
|
QIcon::fromTheme("window-pin"), |
|
i18nc("@info:tooltip in the virtual desktop list", "Make the window available on all desktops"), |
|
OptionsModel::ExclusiveOption, |
|
}; |
|
for (const DBusDesktopDataStruct &desktop : m_virtualDesktops) { |
|
modelData << OptionsModel::Data{ |
|
desktop.id, |
|
QString::number(desktop.position + 1).rightJustified(2) + QStringLiteral(": ") + desktop.name, |
|
QIcon::fromTheme("virtual-desktops")}; |
|
} |
|
return modelData; |
|
} |
|
|
|
QList<OptionsModel::Data> RulesModel::activitiesModelData() const |
|
{ |
|
#if KWIN_BUILD_ACTIVITIES |
|
QList<OptionsModel::Data> modelData; |
|
|
|
modelData << OptionsModel::Data{ |
|
Activities::nullUuid(), |
|
i18n("All Activities"), |
|
QIcon::fromTheme("activities"), |
|
i18nc("@info:tooltip in the activity list", "Make the window available on all activities"), |
|
OptionsModel::ExclusiveOption, |
|
}; |
|
|
|
const auto activities = m_activities->activities(KActivities::Info::Running); |
|
if (m_activities->serviceStatus() == KActivities::Consumer::Running) { |
|
for (const QString &activityId : activities) { |
|
const KActivities::Info info(activityId); |
|
modelData << OptionsModel::Data{activityId, info.name(), QIcon::fromTheme(info.icon())}; |
|
} |
|
} |
|
|
|
return modelData; |
|
#else |
|
return {}; |
|
#endif |
|
} |
|
|
|
QList<OptionsModel::Data> RulesModel::placementModelData() const |
|
{ |
|
static const auto modelData = QList<OptionsModel::Data>{ |
|
{PlacementDefault, i18n("Default")}, |
|
{PlacementNone, i18n("No Placement")}, |
|
{PlacementSmart, i18n("Minimal Overlapping")}, |
|
{PlacementMaximizing, i18n("Maximized")}, |
|
{PlacementCentered, i18n("Centered")}, |
|
{PlacementRandom, i18n("Random")}, |
|
{PlacementZeroCornered, i18n("In Top-Left Corner")}, |
|
{PlacementUnderMouse, i18n("Under Mouse")}, |
|
{PlacementOnMainWindow, i18n("On Main Window")}}; |
|
return modelData; |
|
} |
|
|
|
QList<OptionsModel::Data> RulesModel::focusModelData() const |
|
{ |
|
static const auto modelData = QList<OptionsModel::Data>{ |
|
{0, i18n("None")}, |
|
{1, i18n("Low")}, |
|
{2, i18n("Normal")}, |
|
{3, i18n("High")}, |
|
{4, i18n("Extreme")}}; |
|
return modelData; |
|
} |
|
|
|
QList<OptionsModel::Data> RulesModel::colorSchemesModelData() const |
|
{ |
|
QList<OptionsModel::Data> modelData; |
|
|
|
KColorSchemeManager schemes; |
|
QAbstractItemModel *schemesModel = schemes.model(); |
|
|
|
// Skip row 0, which is Default scheme |
|
for (int r = 1; r < schemesModel->rowCount(); r++) { |
|
const QModelIndex index = schemesModel->index(r, 0); |
|
modelData << OptionsModel::Data{ |
|
QFileInfo(index.data(Qt::UserRole).toString()).baseName(), |
|
index.data(Qt::DisplayRole).toString(), |
|
index.data(Qt::DecorationRole).value<QIcon>()}; |
|
} |
|
|
|
return modelData; |
|
} |
|
|
|
void RulesModel::detectWindowProperties(int miliseconds) |
|
{ |
|
QTimer::singleShot(miliseconds, this, &RulesModel::selectX11Window); |
|
} |
|
|
|
void RulesModel::selectX11Window() |
|
{ |
|
QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KWin"), |
|
QStringLiteral("/KWin"), |
|
QStringLiteral("org.kde.KWin"), |
|
QStringLiteral("queryWindowInfo")); |
|
|
|
QDBusPendingReply<QVariantMap> async = QDBusConnection::sessionBus().asyncCall(message); |
|
|
|
QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(async, this); |
|
connect(callWatcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *self) { |
|
QDBusPendingReply<QVariantMap> reply = *self; |
|
self->deleteLater(); |
|
if (!reply.isValid()) { |
|
if (reply.error().name() == QLatin1String("org.kde.KWin.Error.InvalidWindow")) { |
|
Q_EMIT showErrorMessage(i18n("Unmanaged window"), |
|
i18n("Could not detect window properties. The window is not managed by KWin.")); |
|
} |
|
return; |
|
} |
|
const QVariantMap windowInfo = reply.value(); |
|
setSuggestedProperties(windowInfo); |
|
Q_EMIT showSuggestions(); |
|
}); |
|
} |
|
|
|
void RulesModel::updateVirtualDesktops() |
|
{ |
|
QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KWin"), |
|
QStringLiteral("/VirtualDesktopManager"), |
|
QStringLiteral("org.freedesktop.DBus.Properties"), |
|
QStringLiteral("Get")); |
|
message.setArguments(QVariantList{ |
|
QStringLiteral("org.kde.KWin.VirtualDesktopManager"), |
|
QStringLiteral("desktops")}); |
|
|
|
QDBusPendingReply<QVariant> async = QDBusConnection::sessionBus().asyncCall(message); |
|
|
|
QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(async, this); |
|
connect(callWatcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *self) { |
|
QDBusPendingReply<QVariant> reply = *self; |
|
self->deleteLater(); |
|
if (!reply.isValid()) { |
|
return; |
|
} |
|
m_virtualDesktops = qdbus_cast<KWin::DBusDesktopDataVector>(reply.value()); |
|
Q_EMIT virtualDesktopsUpdated(); |
|
}); |
|
} |
|
|
|
} // namespace
|
|
|