KWin
Loading...
Searching...
No Matches
rulesmodel.cpp
Go to the documentation of this file.
1/*
2 SPDX-FileCopyrightText: 2004 Lubos Lunak <l.lunak@kde.org>
3 SPDX-FileCopyrightText: 2020 Ismael Asensio <isma.af@gmail.com>
4
5 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
6*/
7
8#include "rulesmodel.h"
9
10#if KWIN_BUILD_ACTIVITIES
11#include "activities.h"
12#endif
13
14#include <QDBusConnection>
15#include <QDBusMessage>
16#include <QDBusMetaType>
17#include <QDBusPendingCallWatcher>
18#include <QDBusPendingReply>
19#include <QFileInfo>
20#include <QIcon>
21#include <QQmlEngine>
22#include <QTimer>
23
24#include <KColorSchemeManager>
25#include <KConfig>
26#include <KLocalizedString>
27#include <KWindowSystem>
28
29namespace KWin
30{
31
32RulesModel::RulesModel(QObject *parent)
33 : QAbstractListModel(parent)
34{
35 qmlRegisterUncreatableType<RuleItem>("org.kde.kcms.kwinrules", 1, 0, "RuleItem",
36 QStringLiteral("Do not create objects of type RuleItem"));
37 qmlRegisterUncreatableType<RulesModel>("org.kde.kcms.kwinrules", 1, 0, "RulesModel",
38 QStringLiteral("Do not create objects of type RulesModel"));
39 qmlRegisterUncreatableType<OptionsModel>("org.kde.kcms.kwinrules", 1, 0, "OptionsModel",
40 QStringLiteral("Do not create objects of type OptionsModel"));
41
42 qDBusRegisterMetaType<KWin::DBusDesktopDataStruct>();
43 qDBusRegisterMetaType<KWin::DBusDesktopDataVector>();
44
45 populateRuleList();
46}
47
51
52QHash<int, QByteArray> RulesModel::roleNames() const
53{
54 return {
55 {KeyRole, QByteArrayLiteral("key")},
56 {NameRole, QByteArrayLiteral("name")},
57 {IconRole, QByteArrayLiteral("icon")},
58 {IconNameRole, QByteArrayLiteral("iconName")},
59 {SectionRole, QByteArrayLiteral("section")},
60 {DescriptionRole, QByteArrayLiteral("description")},
61 {EnabledRole, QByteArrayLiteral("enabled")},
62 {SelectableRole, QByteArrayLiteral("selectable")},
63 {ValueRole, QByteArrayLiteral("value")},
64 {TypeRole, QByteArrayLiteral("type")},
65 {PolicyRole, QByteArrayLiteral("policy")},
66 {PolicyModelRole, QByteArrayLiteral("policyModel")},
67 {OptionsModelRole, QByteArrayLiteral("options")},
68 {SuggestedValueRole, QByteArrayLiteral("suggested")},
69 };
70}
71
72int RulesModel::rowCount(const QModelIndex &parent) const
73{
74 if (parent.isValid()) {
75 return 0;
76 }
77 return m_ruleList.size();
78}
79
80QVariant RulesModel::data(const QModelIndex &index, int role) const
81{
82 if (!checkIndex(index, CheckIndexOption::IndexIsValid | CheckIndexOption::ParentIsInvalid)) {
83 return QVariant();
84 }
85
86 const RuleItem *rule = m_ruleList.at(index.row());
87
88 switch (role) {
89 case KeyRole:
90 return rule->key();
91 case NameRole:
92 return rule->name();
93 case IconRole:
94 return rule->icon();
95 case IconNameRole:
96 return rule->iconName();
97 case DescriptionRole:
98 return rule->description();
99 case SectionRole:
100 return rule->section();
101 case EnabledRole:
102 return rule->isEnabled();
103 case SelectableRole:
105 case ValueRole:
106 return rule->value();
107 case TypeRole:
108 return rule->type();
109 case PolicyRole:
110 return rule->policy();
111 case PolicyModelRole:
112 return rule->policyModel();
113 case OptionsModelRole:
114 return rule->options();
116 return rule->suggestedValue();
117 }
118 return QVariant();
119}
120
121bool RulesModel::setData(const QModelIndex &index, const QVariant &value, int role)
122{
123 if (!checkIndex(index, CheckIndexOption::IndexIsValid | CheckIndexOption::ParentIsInvalid)) {
124 return false;
125 }
126
127 RuleItem *rule = m_ruleList.at(index.row());
128
129 switch (role) {
130 case EnabledRole:
131 if (value.toBool() == rule->isEnabled()) {
132 return true;
133 }
134 rule->setEnabled(value.toBool());
135 break;
136 case ValueRole:
138 processSuggestion(rule->key(), value);
139 }
140 if (value == rule->value()) {
141 return true;
142 }
143 rule->setValue(value);
144 break;
145 case PolicyRole:
146 if (value.toInt() == rule->policy()) {
147 return true;
148 }
149 rule->setPolicy(value.toInt());
150 break;
152 if (value == rule->suggestedValue()) {
153 return true;
154 }
155 rule->setSuggestedValue(value);
156 break;
157 default:
158 return false;
159 }
160
161 writeToSettings(rule);
162
163 Q_EMIT dataChanged(index, index, QList<int>{role});
165 Q_EMIT descriptionChanged();
166 }
168 Q_EMIT warningMessagesChanged();
169 }
170
171 return true;
172}
173
174QModelIndex RulesModel::indexOf(const QString &key) const
175{
176 const QModelIndexList indexes = match(index(0), RulesModel::KeyRole, key, 1, Qt::MatchFixedString);
177 if (indexes.isEmpty()) {
178 return QModelIndex();
179 }
180 return indexes.at(0);
181}
182
183RuleItem *RulesModel::addRule(RuleItem *rule)
184{
185 m_ruleList << rule;
186 m_rules.insert(rule->key(), rule);
187
188 return rule;
189}
190
191bool RulesModel::hasRule(const QString &key) const
192{
193 return m_rules.contains(key);
194}
195
196RuleItem *RulesModel::ruleItem(const QString &key) const
197{
198 return m_rules.value(key);
199}
200
202{
203 const QString desc = m_rules["description"]->value().toString();
204 if (!desc.isEmpty()) {
205 return desc;
206 }
207 return defaultDescription();
208}
209
210void RulesModel::setDescription(const QString &description)
211{
213}
214
215QString RulesModel::defaultDescription() const
216{
217 const QString wmclass = m_rules["wmclass"]->value().toString();
218 const QString title = m_rules["title"]->isEnabled() ? m_rules["title"]->value().toString() : QString();
219
220 if (!title.isEmpty()) {
221 return i18n("Window settings for %1", title);
222 }
223 if (!wmclass.isEmpty()) {
224 return i18n("Settings for %1", wmclass);
225 }
226
227 return i18n("New window settings");
228}
229
230void RulesModel::processSuggestion(const QString &key, const QVariant &value)
231{
232 if (key == QLatin1String("wmclasshelper")) {
233 setData(indexOf("wmclass"), value, RulesModel::ValueRole);
234 setData(indexOf("wmclasscomplete"), true, RulesModel::ValueRole);
235 }
236}
237
239{
240 QStringList messages;
241
242 if (wmclassWarning()) {
243 messages << i18n("You have specified the window class as unimportant.\n"
244 "This means the settings will possibly apply to windows from all applications."
245 " If you really want to create a generic setting, it is recommended"
246 " you at least limit the window types to avoid special window types.");
247 }
248
249 if (geometryWarning()) {
250 messages << i18n("Some applications set their own geometry after starting,"
251 " overriding your initial settings for size and position. "
252 "To enforce these settings, also force the property \"%1\" to \"Yes\".",
253 m_rules["ignoregeometry"]->name());
254 }
255
256 if (opacityWarning()) {
257 messages << i18n("Readability may be impaired with extremely low opacity values. At 0%, the window becomes invisible.");
258 }
259
260 return messages;
261}
262
263bool RulesModel::wmclassWarning() const
264{
265 const bool no_wmclass = !m_rules["wmclass"]->isEnabled()
266 || m_rules["wmclass"]->policy() == Rules::UnimportantMatch;
267 const bool alltypes = !m_rules["types"]->isEnabled()
268 || (m_rules["types"]->value() == 0)
269 || (m_rules["types"]->value() == NET::AllTypesMask)
270 || ((m_rules["types"]->value().toInt() | (1 << NET::Override)) == 0x3FF);
271
272 return (no_wmclass && alltypes);
273}
274
275bool RulesModel::geometryWarning() const
276{
277 if (!KWindowSystem::isPlatformX11()) {
278 return false;
279 }
280
281 const bool ignoregeometry = m_rules["ignoregeometry"]->isEnabled()
282 && m_rules["ignoregeometry"]->policy() == Rules::Force
283 && m_rules["ignoregeometry"]->value() == true;
284
285 const bool initialPos = m_rules["position"]->isEnabled()
286 && (m_rules["position"]->policy() == Rules::Apply
287 || m_rules["position"]->policy() == Rules::Remember);
288
289 const bool initialSize = m_rules["size"]->isEnabled()
290 && (m_rules["size"]->policy() == Rules::Apply
291 || m_rules["size"]->policy() == Rules::Remember);
292
293 const bool initialPlacement = m_rules["placement"]->isEnabled()
294 && m_rules["placement"]->policy() == Rules::Force;
295
296 return (!ignoregeometry && (initialPos || initialSize || initialPlacement));
297}
298
299bool RulesModel::opacityWarning() const
300{
301 auto opacityActive = m_rules["opacityactive"];
302 const bool lowOpacityActive = opacityActive->isEnabled()
303 && opacityActive->policy() != Rules::Unused && opacityActive->policy() != Rules::DontAffect
304 && opacityActive->value().toInt() < 25;
305
306 auto opacityInactive = m_rules["opacityinactive"];
307 const bool lowOpacityInactive = opacityInactive->isEnabled()
308 && opacityActive->policy() != Rules::Unused && opacityActive->policy() != Rules::DontAffect
309 && opacityInactive->value().toInt() < 25;
310
311 return lowOpacityActive || lowOpacityInactive;
312}
313
314RuleSettings *RulesModel::settings() const
315{
316 return m_settings;
317}
318
319void RulesModel::setSettings(RuleSettings *settings)
320{
321 if (m_settings == settings) {
322 return;
323 }
324
325 beginResetModel();
326
327 m_settings = settings;
328
329 for (RuleItem *rule : std::as_const(m_ruleList)) {
330 const KConfigSkeletonItem *configItem = m_settings->findItem(rule->key());
331 const KConfigSkeletonItem *configPolicyItem = m_settings->findItem(rule->policyKey());
332
333 rule->reset();
334
335 if (!configItem) {
336 continue;
337 }
338
339 const bool isEnabled = configPolicyItem ? configPolicyItem->property() != Rules::Unused
340 : !configItem->property().toString().isEmpty();
341 rule->setEnabled(isEnabled);
342
343 const QVariant value = configItem->property();
344 rule->setValue(value);
345
346 if (configPolicyItem) {
347 const int policy = configPolicyItem->property().toInt();
348 rule->setPolicy(policy);
349 }
350 }
351
352 endResetModel();
353
354 Q_EMIT descriptionChanged();
355 Q_EMIT warningMessagesChanged();
356}
357
358void RulesModel::writeToSettings(RuleItem *rule)
359{
360 KConfigSkeletonItem *configItem = m_settings->findItem(rule->key());
361 KConfigSkeletonItem *configPolicyItem = m_settings->findItem(rule->policyKey());
362
363 if (!configItem) {
364 return;
365 }
366
367 if (rule->isEnabled()) {
368 configItem->setProperty(rule->value());
369 if (configPolicyItem) {
370 configPolicyItem->setProperty(rule->policy());
371 }
372 } else {
373 configItem->setDefault();
374 if (configPolicyItem) {
375 configPolicyItem->setDefault();
376 }
377 }
378}
379
380void RulesModel::populateRuleList()
381{
382 qDeleteAll(m_ruleList);
383 m_ruleList.clear();
384
385 // Rule description
386 auto description = addRule(new RuleItem(QLatin1String("description"),
388 i18n("Description"), i18n("Window matching"),
389 QIcon::fromTheme("entry-edit")));
392
393 // Window matching
394 auto wmclass = addRule(new RuleItem(QLatin1String("wmclass"),
396 i18n("Window class (application)"), i18n("Window matching"),
397 QIcon::fromTheme("window")));
398 wmclass->setFlag(RuleItem::AlwaysEnabled);
399 wmclass->setFlag(RuleItem::AffectsDescription);
400 wmclass->setFlag(RuleItem::AffectsWarning);
401
402 auto wmclasscomplete = addRule(new RuleItem(QLatin1String("wmclasscomplete"),
404 i18n("Match whole window class"), i18n("Window matching"),
405 QIcon::fromTheme("window")));
406 wmclasscomplete->setFlag(RuleItem::AlwaysEnabled);
407
408 // Helper item to store the detected whole window class when detecting properties
409 auto wmclasshelper = addRule(new RuleItem(QLatin1String("wmclasshelper"),
411 i18n("Whole window class"), i18n("Window matching"),
412 QIcon::fromTheme("window")));
413 wmclasshelper->setFlag(RuleItem::SuggestionOnly);
414
415 auto types = addRule(new RuleItem(QLatin1String("types"),
417 i18n("Window types"), i18n("Window matching"),
418 QIcon::fromTheme("window-duplicate")));
419 types->setOptionsData(windowTypesModelData());
420 types->setFlag(RuleItem::AlwaysEnabled);
421 types->setFlag(RuleItem::AffectsWarning);
422
423 addRule(new RuleItem(QLatin1String("windowrole"),
425 i18n("Window role"), i18n("Window matching"),
426 QIcon::fromTheme("dialog-object-properties")));
427
428 auto title = addRule(new RuleItem(QLatin1String("title"),
430 i18n("Window title"), i18n("Window matching"),
431 QIcon::fromTheme("edit-comment")));
432 title->setFlag(RuleItem::AffectsDescription);
433
434 addRule(new RuleItem(QLatin1String("clientmachine"),
436 i18n("Machine (hostname)"), i18n("Window matching"),
437 QIcon::fromTheme("computer")));
438
439 // Size & Position
440 auto position = addRule(new RuleItem(QLatin1String("position"),
442 i18n("Position"), i18n("Size & Position"),
443 QIcon::fromTheme("transform-move")));
444 position->setFlag(RuleItem::AffectsWarning);
445
446 auto size = addRule(new RuleItem(QLatin1String("size"),
448 i18n("Size"), i18n("Size & Position"),
449 QIcon::fromTheme("transform-scale")));
450 size->setFlag(RuleItem::AffectsWarning);
451
452 addRule(new RuleItem(QLatin1String("maximizehoriz"),
454 i18n("Maximized horizontally"), i18n("Size & Position"),
455 QIcon::fromTheme("resizecol")));
456
457 addRule(new RuleItem(QLatin1String("maximizevert"),
459 i18n("Maximized vertically"), i18n("Size & Position"),
460 QIcon::fromTheme("resizerow")));
461
462 RuleItem *desktops;
463 if (KWindowSystem::isPlatformX11()) {
464 // Single selection of Virtual Desktop on X11
465 desktops = new RuleItem(QLatin1String("desktops"),
467 i18n("Virtual Desktop"), i18n("Size & Position"),
468 QIcon::fromTheme("virtual-desktops"));
469 } else {
470 // Multiple selection on Wayland
471 desktops = new RuleItem(QLatin1String("desktops"),
473 i18n("Virtual Desktops"), i18n("Size & Position"),
474 QIcon::fromTheme("virtual-desktops"));
475 }
476 addRule(desktops);
477 desktops->setOptionsData(virtualDesktopsModelData());
478
479 connect(this, &RulesModel::virtualDesktopsUpdated, this, [this]() {
480 m_rules["desktops"]->setOptionsData(virtualDesktopsModelData());
481 const QModelIndex index = indexOf("desktops");
482 Q_EMIT dataChanged(index, index, {OptionsModelRole});
483 });
484
485 updateVirtualDesktops();
486
487#if KWIN_BUILD_ACTIVITIES
488 m_activities = new KActivities::Consumer(this);
489
490 auto activity = addRule(new RuleItem(QLatin1String("activity"),
492 i18n("Activities"), i18n("Size & Position"),
493 QIcon::fromTheme("activities")));
494 activity->setOptionsData(activitiesModelData());
495
496 // Activites consumer may update the available activities later
497 auto updateActivities = [this]() {
498 m_rules["activity"]->setOptionsData(activitiesModelData());
499 const QModelIndex index = indexOf("activity");
500 Q_EMIT dataChanged(index, index, {OptionsModelRole});
501 };
502 connect(m_activities, &KActivities::Consumer::activitiesChanged, this, updateActivities);
503 connect(m_activities, &KActivities::Consumer::serviceStatusChanged, this, updateActivities);
504#endif
505
506 addRule(new RuleItem(QLatin1String("screen"),
508 i18n("Screen"), i18n("Size & Position"),
509 QIcon::fromTheme("osd-shutd-screen")));
510
511 addRule(new RuleItem(QLatin1String("fullscreen"),
513 i18n("Fullscreen"), i18n("Size & Position"),
514 QIcon::fromTheme("view-fullscreen")));
515
516 addRule(new RuleItem(QLatin1String("minimize"),
518 i18n("Minimized"), i18n("Size & Position"),
519 QIcon::fromTheme("window-minimize")));
520
521 addRule(new RuleItem(QLatin1String("shade"),
523 i18n("Shaded"), i18n("Size & Position"),
524 QIcon::fromTheme("window-shade")));
525
526 auto placement = addRule(new RuleItem(QLatin1String("placement"),
528 i18n("Initial placement"), i18n("Size & Position"),
529 QIcon::fromTheme("region")));
530 placement->setOptionsData(placementModelData());
531 placement->setFlag(RuleItem::AffectsWarning);
532
533 if (KWindowSystem::isPlatformX11()) {
534 // On Wayland windows cannot set their own geometry
535 auto ignoregeometry = addRule(new RuleItem(QLatin1String("ignoregeometry"),
537 i18n("Ignore requested geometry"), i18n("Size & Position"),
538 QIcon::fromTheme("view-time-schedule-baselined-remove"),
539 xi18nc("@info:tooltip",
540 "Some applications can set their own geometry, overriding the window manager preferences. "
541 "Setting this property overrides their placement requests."
542 "<nl/><nl/>"
543 "This affects <interface>Size</interface> and <interface>Position</interface> "
544 "but not <interface>Maximized</interface> or <interface>Fullscreen</interface> states."
545 "<nl/><nl/>"
546 "Note that the position can also be used to map to a different <interface>Screen</interface>")));
547 ignoregeometry->setFlag(RuleItem::AffectsWarning);
548 }
549
550 addRule(new RuleItem(QLatin1String("minsize"),
552 i18n("Minimum Size"), i18n("Size & Position"),
553 QIcon::fromTheme("transform-scale")));
554
555 addRule(new RuleItem(QLatin1String("maxsize"),
557 i18n("Maximum Size"), i18n("Size & Position"),
558 QIcon::fromTheme("transform-scale")));
559
560 addRule(new RuleItem(QLatin1String("strictgeometry"),
562 i18n("Obey geometry restrictions"), i18n("Size & Position"),
563 QIcon::fromTheme("transform-crop-and-resize"),
564 xi18nc("@info:tooltip", "Some apps like video players or terminals can ask KWin to constrain them to "
565 "certain aspect ratios or only grow by values larger than the dimensions of one "
566 "character. Use this property to ignore such restrictions and allow those windows "
567 "to be resized to arbitrary sizes."
568 "<nl/><nl/>"
569 "This can be helpful for windows that can't quite fit the full screen area when "
570 "maximized.")));
571
572 // Arrangement & Access
573 addRule(new RuleItem(QLatin1String("above"),
575 i18n("Keep above other windows"), i18n("Arrangement & Access"),
576 QIcon::fromTheme("window-keep-above")));
577
578 addRule(new RuleItem(QLatin1String("below"),
580 i18n("Keep below other windows"), i18n("Arrangement & Access"),
581 QIcon::fromTheme("window-keep-below")));
582
583 addRule(new RuleItem(QLatin1String("skiptaskbar"),
585 i18n("Skip taskbar"), i18n("Arrangement & Access"),
586 QIcon::fromTheme("kt-show-statusbar"),
587 i18nc("@info:tooltip", "Controls whether or not the window appears in the Task Manager.")));
588
589 addRule(new RuleItem(QLatin1String("skippager"),
591 i18n("Skip pager"), i18n("Arrangement & Access"),
592 QIcon::fromTheme("org.kde.plasma.pager"),
593 i18nc("@info:tooltip", "Controls whether or not the window appears in the Virtual Desktop manager.")));
594
595 addRule(new RuleItem(QLatin1String("skipswitcher"),
597 i18n("Skip switcher"), i18n("Arrangement & Access"),
598 QIcon::fromTheme("preferences-system-windows-effect-flipswitch"),
599 xi18nc("@info:tooltip", "Controls whether or not the window appears in the <shortcut>Alt+Tab</shortcut> window list.")));
600
601 addRule(new RuleItem(QLatin1String("shortcut"),
603 i18n("Shortcut"), i18n("Arrangement & Access"),
604 QIcon::fromTheme("configure-shortcuts")));
605
606 // Appearance & Fixes
607 addRule(new RuleItem(QLatin1String("noborder"),
609 i18n("No titlebar and frame"), i18n("Appearance & Fixes"),
610 QIcon::fromTheme("dialog-cancel")));
611
612 auto decocolor = addRule(new RuleItem(QLatin1String("decocolor"),
614 i18n("Titlebar color scheme"), i18n("Appearance & Fixes"),
615 QIcon::fromTheme("preferences-desktop-theme")));
616 decocolor->setOptionsData(colorSchemesModelData());
617
618 auto opacityactive = addRule(new RuleItem(QLatin1String("opacityactive"),
620 i18n("Active opacity"), i18n("Appearance & Fixes"),
621 QIcon::fromTheme("edit-opacity")));
622 opacityactive->setFlag(RuleItem::AffectsWarning);
623 auto opacityinactive = addRule(new RuleItem(QLatin1String("opacityinactive"),
625 i18n("Inactive opacity"), i18n("Appearance & Fixes"),
626 QIcon::fromTheme("edit-opacity")));
627 opacityinactive->setFlag(RuleItem::AffectsWarning);
628
629 auto fsplevel = addRule(new RuleItem(QLatin1String("fsplevel"),
631 i18n("Focus stealing prevention"), i18n("Appearance & Fixes"),
632 QIcon::fromTheme("preferences-system-windows-effect-glide"),
633 xi18nc("@info:tooltip", "KWin tries to prevent windows that were opened without direct user action from raising "
634 "themselves and taking focus while you're currently interacting with another window. This "
635 "property can be used to change the level of focus stealing prevention applied to "
636 "individual windows and apps."
637 "<nl/><nl/>"
638 "Here's what will happen to a window opened without your direct action at each level of "
639 "focus stealing prevention:"
640 "<nl/>"
641 "<list>"
642 "<item><emphasis strong='true'>None:</emphasis> The window will be raised and focused.</item>"
643 "<item><emphasis strong='true'>Low:</emphasis> Focus stealing prevention will be applied, "
644 "but in the case of a situation KWin considers ambiguous, the window will be raised and "
645 "focused.</item>"
646 "<item><emphasis strong='true'>Normal:</emphasis> Focus stealing prevention will be "
647 "applied, but in the case of a situation KWin considers ambiguous, the window will "
648 "<emphasis>not</emphasis> be raised and focused.</item>"
649 "<item><emphasis strong='true'>High:</emphasis> The window will only be raised and focused "
650 "if it belongs to the same app as the currently-focused window.</item>"
651 "<item><emphasis strong='true'>Extreme:</emphasis> The window will never be raised and "
652 "focused.</item>"
653 "</list>")));
654 fsplevel->setOptionsData(focusModelData());
655
656 auto fpplevel = addRule(new RuleItem(QLatin1String("fpplevel"),
658 i18n("Focus protection"), i18n("Appearance & Fixes"),
659 QIcon::fromTheme("preferences-system-windows-effect-minimize"),
660 xi18nc("@info:tooltip", "This property controls the focus protection level of the currently active "
661 "window. It is used to override the focus stealing prevention applied to new windows that "
662 "are opened without your direct action."
663 "<nl/><nl/>"
664 "Here's what happens to new windows that are opened without your direct action at each "
665 "level of focus protection while the window with this property applied to it has focus:"
666 "<nl/>"
667 "<list>"
668 "<item><emphasis strong='true'>None</emphasis>: Newly-opened windows always raise "
669 "themselves and take focus.</item>"
670 "<item><emphasis strong='true'>Low:</emphasis> Focus stealing prevention will be applied "
671 "to the newly-opened window, but in the case of a situation KWin considers ambiguous, the "
672 "window will be raised and focused.</item>"
673 "<item><emphasis strong='true'>Normal:</emphasis> Focus stealing prevention will be applied "
674 "to the newly-opened window, but in the case of a situation KWin considers ambiguous, the "
675 "window will <emphasis>not</emphasis> be raised and focused.</item>"
676 "<item><emphasis strong='true'>High:</emphasis> Newly-opened windows will only raise "
677 "themselves and take focus if they belongs to the same app as the currently-focused "
678 "window.</item>"
679 "<item><emphasis strong='true'>Extreme:</emphasis> Newly-opened windows never raise "
680 "themselves and take focus.</item>"
681 "</list>")));
682 fpplevel->setOptionsData(focusModelData());
683
684 addRule(new RuleItem(QLatin1String("acceptfocus"),
686 i18n("Accept focus"), i18n("Appearance & Fixes"),
687 QIcon::fromTheme("preferences-desktop-cursors"),
688 i18n("Controls whether or not the window becomes focused when clicked.")));
689
690 addRule(new RuleItem(QLatin1String("disableglobalshortcuts"),
692 i18n("Ignore global shortcuts"), i18n("Appearance & Fixes"),
693 QIcon::fromTheme("input-keyboard-virtual-off"),
694 xi18nc("@info:tooltip", "Use this property to prevent global keyboard shortcuts from working while "
695 "the window is focused. This can be useful for apps like emulators or virtual "
696 "machines that handle some of the same shortcuts themselves."
697 "<nl/><nl/>"
698 "Note that you won't be able to <shortcut>Alt+Tab</shortcut> out of the window "
699 "or use any other global shortcuts such as <shortcut>Alt+Space</shortcut> to "
700 "activate KRunner.")));
701
702 addRule(new RuleItem(QLatin1String("closeable"),
704 i18n("Closeable"), i18n("Appearance & Fixes"),
705 QIcon::fromTheme("dialog-close")));
706
707 addRule(new RuleItem(QLatin1String("desktopfile"),
709 i18n("Desktop file name"), i18n("Appearance & Fixes"),
710 QIcon::fromTheme("application-x-desktop")));
711
712 addRule(new RuleItem(QLatin1String("blockcompositing"),
714 i18n("Block compositing"), i18n("Appearance & Fixes"),
715 QIcon::fromTheme("composite-track-on")));
716
717 auto layer = addRule(new RuleItem(QLatin1String("layer"),
719 i18n("Layer"), i18n("Appearance & Fixes"),
720 QIcon::fromTheme("view-sort")));
721 layer->setOptionsData(layerModelData());
722
723 addRule(new RuleItem(QLatin1String("adaptivesync"),
725 i18n("Adaptive Sync"), i18n("Appearance & Fixes"),
726 QIcon::fromTheme("monitor-symbolic")));
727}
728
729const QHash<QString, QString> RulesModel::x11PropertyHash()
730{
731 static const auto propertyToRule = QHash<QString, QString>{
732 {"caption", "title"},
733 {"role", "windowrole"},
734 {"clientMachine", "clientmachine"},
735 {"maximizeHorizontal", "maximizehoriz"},
736 {"maximizeVertical", "maximizevert"},
737 {"minimized", "minimize"},
738 {"shaded", "shade"},
739 {"fullscreen", "fullscreen"},
740 {"keepAbove", "above"},
741 {"keepBelow", "below"},
742 {"noBorder", "noborder"},
743 {"skipTaskbar", "skiptaskbar"},
744 {"skipPager", "skippager"},
745 {"skipSwitcher", "skipswitcher"},
746 {"desktopFile", "desktopfile"},
747 {"desktops", "desktops"},
748 {"layer", "layer"},
749 };
750 return propertyToRule;
751};
752
753void RulesModel::setSuggestedProperties(const QVariantMap &info)
754{
755 // Properties that cannot be directly applied via x11PropertyHash
756 const QPoint position = QPoint(info.value("x").toInt(), info.value("y").toInt());
757 const QSize size = QSize(info.value("width").toInt(), info.value("height").toInt());
758
759 m_rules["position"]->setSuggestedValue(position);
760 m_rules["size"]->setSuggestedValue(size);
761 m_rules["minsize"]->setSuggestedValue(size);
762 m_rules["maxsize"]->setSuggestedValue(size);
763
764 NET::WindowType window_type = static_cast<NET::WindowType>(info.value("type", 0).toInt());
765 if (window_type == NET::Unknown) {
766 window_type = NET::Normal;
767 }
768 m_rules["types"]->setSuggestedValue(1 << window_type);
769
770 const QString wmsimpleclass = info.value("resourceClass").toString();
771 const QString wmcompleteclass = QStringLiteral("%1 %2").arg(info.value("resourceName").toString(),
772 info.value("resourceClass").toString());
773
774 // This window is not providing the class according to spec (WM_CLASS on X11, appId on Wayland)
775 // Notify the user that this is a bug within the application, so there's nothing we can do
776 if (wmsimpleclass.isEmpty()) {
777 Q_EMIT showErrorMessage(i18n("Window class not available"),
778 xi18nc("@info", "This application is not providing a class for the window, "
779 "so KWin cannot use it to match and apply any rules. "
780 "If you still want to apply some rules to it, "
781 "try to match other properties like the window title instead.<nl/><nl/>"
782 "Please consider reporting this bug to the application's developers."));
783 }
784
785 m_rules["wmclass"]->setSuggestedValue(wmsimpleclass);
786 m_rules["wmclasshelper"]->setSuggestedValue(wmcompleteclass);
787
788#if KWIN_BUILD_ACTIVITIES
789 const QStringList activities = info.value("activities").toStringList();
790 m_rules["activity"]->setSuggestedValue(activities.isEmpty() ? QStringList{Activities::nullUuid()}
791 : activities);
792#endif
793
794 const auto ruleForProperty = x11PropertyHash();
795 for (QString &property : info.keys()) {
796 if (!ruleForProperty.contains(property)) {
797 continue;
798 }
799 const QString ruleKey = ruleForProperty.value(property, QString());
800 Q_ASSERT(hasRule(ruleKey));
801
802 m_rules[ruleKey]->setSuggestedValue(info.value(property));
803 }
804
805 Q_EMIT dataChanged(index(0), index(rowCount() - 1), {RulesModel::SuggestedValueRole});
806}
807
808QList<OptionsModel::Data> RulesModel::windowTypesModelData() const
809{
810 static const auto modelData = QList<OptionsModel::Data>{
811 // TODO: Find/create better icons
812 {0, i18n("All Window Types"), {}, {}, OptionsModel::SelectAllOption},
813 {1 << NET::Normal, i18n("Normal Window"), QIcon::fromTheme("window")},
814 {1 << NET::Dialog, i18n("Dialog Window"), QIcon::fromTheme("window-duplicate")},
815 {1 << NET::Utility, i18n("Utility Window"), QIcon::fromTheme("dialog-object-properties")},
816 {1 << NET::Dock, i18n("Dock (panel)"), QIcon::fromTheme("list-remove")},
817 {1 << NET::Toolbar, i18n("Toolbar"), QIcon::fromTheme("tools")},
818 {1 << NET::Menu, i18n("Torn-Off Menu"), QIcon::fromTheme("overflow-menu-left")},
819 {1 << NET::Splash, i18n("Splash Screen"), QIcon::fromTheme("embosstool")},
820 {1 << NET::Desktop, i18n("Desktop"), QIcon::fromTheme("desktop")},
821 // {1 << NET::Override, i18n("Unmanaged Window")}, deprecated
822 {1 << NET::TopMenu, i18n("Standalone Menubar"), QIcon::fromTheme("application-menu")},
823 {1 << NET::OnScreenDisplay, i18n("On Screen Display"), QIcon::fromTheme("osd-duplicate")}};
824
825 return modelData;
826}
827
828QList<OptionsModel::Data> RulesModel::virtualDesktopsModelData() const
829{
830 QList<OptionsModel::Data> modelData;
831 modelData << OptionsModel::Data{
832 QString(),
833 i18n("All Desktops"),
834 QIcon::fromTheme("window-pin"),
835 i18nc("@info:tooltip in the virtual desktop list", "Make the window available on all desktops"),
837 };
838 for (const DBusDesktopDataStruct &desktop : m_virtualDesktops) {
839 modelData << OptionsModel::Data{
840 desktop.id,
841 QString::number(desktop.position + 1).rightJustified(2) + QStringLiteral(": ") + desktop.name,
842 QIcon::fromTheme("virtual-desktops")};
843 }
844 return modelData;
845}
846
847QList<OptionsModel::Data> RulesModel::activitiesModelData() const
848{
849#if KWIN_BUILD_ACTIVITIES
850 QList<OptionsModel::Data> modelData;
851
852 modelData << OptionsModel::Data{
854 i18n("All Activities"),
855 QIcon::fromTheme("activities"),
856 i18nc("@info:tooltip in the activity list", "Make the window available on all activities"),
858 };
859
860 const auto activities = m_activities->activities(KActivities::Info::Running);
861 if (m_activities->serviceStatus() == KActivities::Consumer::Running) {
862 for (const QString &activityId : activities) {
863 const KActivities::Info info(activityId);
864 modelData << OptionsModel::Data{activityId, info.name(), QIcon::fromTheme(info.icon())};
865 }
866 }
867
868 return modelData;
869#else
870 return {};
871#endif
872}
873
874QList<OptionsModel::Data> RulesModel::placementModelData() const
875{
876 static const auto modelData = QList<OptionsModel::Data>{
877 {PlacementDefault, i18n("Default")},
878 {PlacementNone, i18n("No Placement")},
879 {PlacementSmart, i18n("Minimal Overlapping")},
880 {PlacementMaximizing, i18n("Maximized")},
881 {PlacementCentered, i18n("Centered")},
882 {PlacementRandom, i18n("Random")},
883 {PlacementZeroCornered, i18n("In Top-Left Corner")},
884 {PlacementUnderMouse, i18n("Under Mouse")},
885 {PlacementOnMainWindow, i18n("On Main Window")}};
886 return modelData;
887}
888
889QList<OptionsModel::Data> RulesModel::focusModelData() const
890{
891 static const auto modelData = QList<OptionsModel::Data>{
892 {0, i18n("None")},
893 {1, i18n("Low")},
894 {2, i18n("Normal")},
895 {3, i18n("High")},
896 {4, i18n("Extreme")}};
897 return modelData;
898}
899
900QList<OptionsModel::Data> RulesModel::colorSchemesModelData() const
901{
902 QList<OptionsModel::Data> modelData;
903
904 KColorSchemeManager schemes;
905 QAbstractItemModel *schemesModel = schemes.model();
906
907 // Skip row 0, which is Default scheme
908 for (int r = 1; r < schemesModel->rowCount(); r++) {
909 const QModelIndex index = schemesModel->index(r, 0);
910 modelData << OptionsModel::Data{
911 QFileInfo(index.data(Qt::UserRole).toString()).baseName(),
912 index.data(Qt::DisplayRole).toString(),
913 index.data(Qt::DecorationRole).value<QIcon>()};
914 }
915
916 return modelData;
917}
918
919QList<OptionsModel::Data> RulesModel::layerModelData() const
920{
921 static const auto modelData = QList<OptionsModel::Data>{
922 {DesktopLayer, i18n("Desktop")},
923 {BelowLayer, i18n("Below")},
924 {NormalLayer, i18n("Normal")},
925 {AboveLayer, i18n("Above")},
926 {NotificationLayer, i18n("Notification")},
927 {ActiveLayer, i18n("Fullscreen")},
928 {PopupLayer, i18n("Popup")},
929 {CriticalNotificationLayer, i18n("Critical Notification")},
930 {OnScreenDisplayLayer, i18n("OSD")},
931 {OverlayLayer, i18n("Overlay")},
932 };
933 return modelData;
934}
935
937{
938 QTimer::singleShot(miliseconds, this, &RulesModel::selectX11Window);
939}
940
941void RulesModel::selectX11Window()
942{
943 QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KWin"),
944 QStringLiteral("/KWin"),
945 QStringLiteral("org.kde.KWin"),
946 QStringLiteral("queryWindowInfo"));
947
948 QDBusPendingReply<QVariantMap> async = QDBusConnection::sessionBus().asyncCall(message);
949
950 QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(async, this);
951 connect(callWatcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *self) {
952 QDBusPendingReply<QVariantMap> reply = *self;
953 self->deleteLater();
954 if (!reply.isValid()) {
955 if (reply.error().name() == QLatin1String("org.kde.KWin.Error.InvalidWindow")) {
956 Q_EMIT showErrorMessage(i18n("Unmanaged window"),
957 i18n("Could not detect window properties. The window is not managed by KWin."));
958 }
959 return;
960 }
961 const QVariantMap windowInfo = reply.value();
962 setSuggestedProperties(windowInfo);
963 Q_EMIT showSuggestions();
964 });
965}
966
967void RulesModel::updateVirtualDesktops()
968{
969 QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KWin"),
970 QStringLiteral("/VirtualDesktopManager"),
971 QStringLiteral("org.freedesktop.DBus.Properties"),
972 QStringLiteral("Get"));
973 message.setArguments(QVariantList{
974 QStringLiteral("org.kde.KWin.VirtualDesktopManager"),
975 QStringLiteral("desktops")});
976
977 QDBusPendingReply<QVariant> async = QDBusConnection::sessionBus().asyncCall(message);
978
979 QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(async, this);
980 connect(callWatcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *self) {
981 QDBusPendingReply<QVariant> reply = *self;
982 self->deleteLater();
983 if (!reply.isValid()) {
984 return;
985 }
986 m_virtualDesktops = qdbus_cast<KWin::DBusDesktopDataVector>(reply.value());
987 Q_EMIT virtualDesktopsUpdated();
988 });
989}
990
991} // namespace
992
993#include "moc_rulesmodel.cpp"
static QString nullUuid()
Definition activities.h:103
QString description() const
Definition ruleitem.cpp:68
QIcon icon() const
Definition ruleitem.cpp:63
QVariant suggestedValue() const
Definition ruleitem.cpp:114
void setEnabled(bool enabled)
Definition ruleitem.cpp:78
void setPolicy(int policy)
Definition ruleitem.cpp:149
QString name() const
Definition ruleitem.cpp:48
bool hasFlag(RuleItem::Flags flag) const
Definition ruleitem.cpp:83
void setValue(QVariant value)
Definition ruleitem.cpp:106
QString key() const
Definition ruleitem.cpp:43
bool isEnabled() const
Definition ruleitem.cpp:73
QVariant policyModel() const
Definition ruleitem.cpp:159
int policy() const
Definition ruleitem.cpp:144
QString iconName() const
Definition ruleitem.cpp:58
QString section() const
Definition ruleitem.cpp:53
QString policyKey() const
Definition ruleitem.cpp:164
QVariant options() const
Definition ruleitem.cpp:124
Type type() const
Definition ruleitem.cpp:93
void setSuggestedValue(QVariant value)
Definition ruleitem.cpp:119
QVariant value() const
Definition ruleitem.cpp:98
@ DontAffect
Definition rules.h:122
@ UnimportantMatch
Definition rules.h:131
QString description
Definition rulesmodel.h:29
void showErrorMessage(const QString &title, const QString &message)
RulesModel(QObject *parent=nullptr)
RuleSettings * settings() const
void setDescription(const QString &description)
void descriptionChanged()
QHash< int, QByteArray > roleNames() const override
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
bool setData(const QModelIndex &index, const QVariant &value, int role) override
bool hasRule(const QString &key) const
void virtualDesktopsUpdated()
int rowCount(const QModelIndex &parent=QModelIndex()) const override
Q_INVOKABLE void detectWindowProperties(int miliseconds)
QStringList warningMessages
Definition rulesmodel.h:30
void setSuggestedProperties(const QVariantMap &info)
RuleItem * ruleItem(const QString &key) const
void warningMessagesChanged()
void showSuggestions()
QModelIndex indexOf(const QString &key) const
void setSettings(RuleSettings *settings)
@ PlacementZeroCornered
Definition options.h:64
@ PlacementUnderMouse
Definition options.h:65
@ PlacementMaximizing
Definition options.h:67
@ PlacementRandom
Definition options.h:61
@ PlacementOnMainWindow
Definition options.h:66
@ PlacementCentered
Definition options.h:63
@ PlacementSmart
Definition options.h:62
@ PlacementDefault
Definition options.h:59
@ PlacementNone
Definition options.h:58
bool match(QList< GlobalShortcut > &shortcuts, Args... args)
@ BelowLayer
Definition globals.h:166
@ DesktopLayer
Definition globals.h:165
@ CriticalNotificationLayer
Definition globals.h:172
@ AboveLayer
Definition globals.h:168
@ ActiveLayer
Definition globals.h:170
@ OverlayLayer
Definition globals.h:174
@ PopupLayer
Definition globals.h:171
@ NotificationLayer
Definition globals.h:169
@ NormalLayer
Definition globals.h:167
@ OnScreenDisplayLayer
Definition globals.h:173