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 #ifdef KWIN_BUILD_ACTIVITIES
11 #include "activities.h"
12 #endif
13
14 #include <QIcon>
15 #include <QQmlEngine>
16 #include <QtDBus>
17 #include <QX11Info>
18
19 #include <KColorSchemeManager>
20 #include <KConfig>
21 #include <KLocalizedString>
22 #include <KWindowSystem>
23
24
25 namespace KWin
26 {
27
RulesModel(QObject * parent)28 RulesModel::RulesModel(QObject *parent)
29 : QAbstractListModel(parent)
30 {
31 qmlRegisterUncreatableType<RuleItem>("org.kde.kcms.kwinrules", 1, 0, "RuleItem",
32 QStringLiteral("Do not create objects of type RuleItem"));
33 qmlRegisterUncreatableType<RulesModel>("org.kde.kcms.kwinrules", 1, 0, "RulesModel",
34 QStringLiteral("Do not create objects of type RulesModel"));
35
36 qDBusRegisterMetaType<KWin::DBusDesktopDataStruct>();
37 qDBusRegisterMetaType<KWin::DBusDesktopDataVector>();
38
39 populateRuleList();
40 }
41
~RulesModel()42 RulesModel::~RulesModel()
43 {
44 }
45
roleNames() const46 QHash< int, QByteArray > RulesModel::roleNames() const
47 {
48 return {
49 {KeyRole, QByteArrayLiteral("key")},
50 {NameRole, QByteArrayLiteral("name")},
51 {IconRole, QByteArrayLiteral("icon")},
52 {IconNameRole, QByteArrayLiteral("iconName")},
53 {SectionRole, QByteArrayLiteral("section")},
54 {DescriptionRole, QByteArrayLiteral("description")},
55 {EnabledRole, QByteArrayLiteral("enabled")},
56 {SelectableRole, QByteArrayLiteral("selectable")},
57 {ValueRole, QByteArrayLiteral("value")},
58 {TypeRole, QByteArrayLiteral("type")},
59 {PolicyRole, QByteArrayLiteral("policy")},
60 {PolicyModelRole, QByteArrayLiteral("policyModel")},
61 {OptionsModelRole, QByteArrayLiteral("options")},
62 {OptionsMaskRole, QByteArrayLiteral("optionsMask")},
63 {SuggestedValueRole, QByteArrayLiteral("suggested")},
64 };
65 }
66
rowCount(const QModelIndex & parent) const67 int RulesModel::rowCount(const QModelIndex &parent) const
68 {
69 if (parent.isValid()) {
70 return 0;
71 }
72 return m_ruleList.size();
73 }
74
data(const QModelIndex & index,int role) const75 QVariant RulesModel::data(const QModelIndex &index, int role) const
76 {
77 if (!checkIndex(index, CheckIndexOption::IndexIsValid | CheckIndexOption::ParentIsInvalid)) {
78 return QVariant();
79 }
80
81 const RuleItem *rule = m_ruleList.at(index.row());
82
83 switch (role) {
84 case KeyRole:
85 return rule->key();
86 case NameRole:
87 return rule->name();
88 case IconRole:
89 return rule->icon();
90 case IconNameRole:
91 return rule->iconName();
92 case DescriptionRole:
93 return rule->description();
94 case SectionRole:
95 return rule->section();
96 case EnabledRole:
97 return rule->isEnabled();
98 case SelectableRole:
99 return !rule->hasFlag(RuleItem::AlwaysEnabled) && !rule->hasFlag(RuleItem::SuggestionOnly);
100 case ValueRole:
101 return rule->value();
102 case TypeRole:
103 return rule->type();
104 case PolicyRole:
105 return rule->policy();
106 case PolicyModelRole:
107 return rule->policyModel();
108 case OptionsModelRole:
109 return rule->options();
110 case OptionsMaskRole:
111 return rule->optionsMask();
112 case SuggestedValueRole:
113 return rule->suggestedValue();
114 }
115 return QVariant();
116 }
117
setData(const QModelIndex & index,const QVariant & value,int role)118 bool RulesModel::setData(const QModelIndex &index, const QVariant &value, int role)
119 {
120 if (!checkIndex(index, CheckIndexOption::IndexIsValid | CheckIndexOption::ParentIsInvalid)) {
121 return false;
122 }
123
124 RuleItem *rule = m_ruleList.at(index.row());
125
126 switch (role) {
127 case EnabledRole:
128 if (value.toBool() == rule->isEnabled()) {
129 return true;
130 }
131 rule->setEnabled(value.toBool());
132 break;
133 case ValueRole:
134 if (rule->hasFlag(RuleItem::SuggestionOnly)) {
135 processSuggestion(rule->key(), value);
136 }
137 if (value == rule->value()) {
138 return true;
139 }
140 rule->setValue(value);
141 break;
142 case PolicyRole:
143 if (value.toInt() == rule->policy()) {
144 return true;
145 }
146 rule->setPolicy(value.toInt());
147 break;
148 case SuggestedValueRole:
149 if (value == rule->suggestedValue()) {
150 return true;
151 }
152 rule->setSuggestedValue(value);
153 break;
154 default:
155 return false;
156 }
157
158 writeToSettings(rule);
159
160 Q_EMIT dataChanged(index, index, QVector<int>{role});
161 if (rule->hasFlag(RuleItem::AffectsDescription)) {
162 Q_EMIT descriptionChanged();
163 }
164 if (rule->hasFlag(RuleItem::AffectsWarning)) {
165 Q_EMIT warningMessagesChanged();
166 }
167
168 return true;
169 }
170
indexOf(const QString & key) const171 QModelIndex RulesModel::indexOf(const QString& key) const
172 {
173 const QModelIndexList indexes = match(index(0), RulesModel::KeyRole, key, 1, Qt::MatchFixedString);
174 if (indexes.isEmpty()) {
175 return QModelIndex();
176 }
177 return indexes.at(0);
178 }
179
addRule(RuleItem * rule)180 RuleItem *RulesModel::addRule(RuleItem *rule)
181 {
182 m_ruleList << rule;
183 m_rules.insert(rule->key(), rule);
184
185 return rule;
186 }
187
hasRule(const QString & key) const188 bool RulesModel::hasRule(const QString& key) const
189 {
190 return m_rules.contains(key);
191 }
192
193
ruleItem(const QString & key) const194 RuleItem *RulesModel::ruleItem(const QString& key) const
195 {
196 return m_rules.value(key);
197 }
198
description() const199 QString RulesModel::description() const
200 {
201 const QString desc = m_rules["description"]->value().toString();
202 if (!desc.isEmpty()) {
203 return desc;
204 }
205 return defaultDescription();
206 }
207
setDescription(const QString & description)208 void RulesModel::setDescription(const QString &description)
209 {
210 setData(indexOf("description"), description, RulesModel::ValueRole);
211 }
212
defaultDescription() const213 QString RulesModel::defaultDescription() const
214 {
215 const QString wmclass = m_rules["wmclass"]->value().toString();
216 const QString title = m_rules["title"]->isEnabled() ? m_rules["title"]->value().toString() : QString();
217
218 if (!title.isEmpty()) {
219 return i18n("Window settings for %1", title);
220 }
221 if (!wmclass.isEmpty()) {
222 return i18n("Settings for %1", wmclass);
223 }
224
225 return i18n("New window settings");
226 }
227
processSuggestion(const QString & key,const QVariant & value)228 void RulesModel::processSuggestion(const QString &key, const QVariant &value)
229 {
230 if (key == QLatin1String("wmclasshelper")) {
231 setData(indexOf("wmclass"), value, RulesModel::ValueRole);
232 setData(indexOf("wmclasscomplete"), true, RulesModel::ValueRole);
233 }
234 }
235
warningMessages() const236 QStringList RulesModel::warningMessages() const
237 {
238 QStringList messages;
239
240 if (wmclassWarning()) {
241 messages << i18n("You have specified the window class as unimportant.\n"
242 "This means the settings will possibly apply to windows from all applications."
243 " If you really want to create a generic setting, it is recommended"
244 " you at least limit the window types to avoid special window types.");
245 }
246
247 if (geometryWarning()) {
248 messages << i18n("Some applications set their own geometry after starting,"
249 " overriding your initial settings for size and position. "
250 "To enforce these settings, also force the property \"%1\" to \"Yes\".",
251 m_rules["ignoregeometry"]->name());
252 }
253
254 return messages;
255 }
256
wmclassWarning() const257 bool RulesModel::wmclassWarning() const
258 {
259 const bool no_wmclass = !m_rules["wmclass"]->isEnabled()
260 || m_rules["wmclass"]->policy() == Rules::UnimportantMatch;
261 const bool alltypes = !m_rules["types"]->isEnabled()
262 || (m_rules["types"]->value() == 0)
263 || (m_rules["types"]->value() == NET::AllTypesMask)
264 || ((m_rules["types"]->value().toInt() | (1 << NET::Override)) == 0x3FF);
265
266 return (no_wmclass && alltypes);
267 }
268
geometryWarning() const269 bool RulesModel::geometryWarning() const
270 {
271 const bool ignoregeometry = m_rules["ignoregeometry"]->isEnabled()
272 && m_rules["ignoregeometry"]->policy() == Rules::Force
273 && m_rules["ignoregeometry"]->value() == true;
274
275 const bool initialPos = m_rules["position"]->isEnabled()
276 && (m_rules["position"]->policy() == Rules::Apply
277 || m_rules["position"]->policy() == Rules::Remember);
278
279 const bool initialSize = m_rules["size"]->isEnabled()
280 && (m_rules["size"]->policy() == Rules::Apply
281 || m_rules["size"]->policy() == Rules::Remember);
282
283 const bool initialPlacement = m_rules["placement"]->isEnabled()
284 && m_rules["placement"]->policy() == Rules::Force;
285
286 return (!ignoregeometry && (initialPos || initialSize || initialPlacement));
287 }
288
settings() const289 RuleSettings *RulesModel::settings() const
290 {
291 return m_settings;
292 }
293
setSettings(RuleSettings * settings)294 void RulesModel::setSettings(RuleSettings *settings)
295 {
296 if (m_settings == settings) {
297 return;
298 }
299
300 beginResetModel();
301
302 m_settings = settings;
303
304 for (RuleItem *rule : qAsConst(m_ruleList)) {
305 const KConfigSkeletonItem *configItem = m_settings->findItem(rule->key());
306 const KConfigSkeletonItem *configPolicyItem = m_settings->findItem(rule->policyKey());
307
308 rule->reset();
309
310 if (!configItem) {
311 continue;
312 }
313
314 const bool isEnabled = configPolicyItem ? configPolicyItem->property() != Rules::Unused
315 : !configItem->property().toString().isEmpty();
316 rule->setEnabled(isEnabled);
317
318 const QVariant value = configItem->property();
319 rule->setValue(value);
320
321 if (configPolicyItem) {
322 const int policy = configPolicyItem->property().toInt();
323 rule->setPolicy(policy);
324 }
325 }
326
327 endResetModel();
328
329 Q_EMIT descriptionChanged();
330 Q_EMIT warningMessagesChanged();
331 }
332
writeToSettings(RuleItem * rule)333 void RulesModel::writeToSettings(RuleItem *rule)
334 {
335 KConfigSkeletonItem *configItem = m_settings->findItem(rule->key());
336 KConfigSkeletonItem *configPolicyItem = m_settings->findItem(rule->policyKey());
337
338 if (!configItem) {
339 return;
340 }
341
342 if (rule->isEnabled()) {
343 configItem->setProperty(rule->value());
344 if (configPolicyItem) {
345 configPolicyItem->setProperty(rule->policy());
346 }
347 } else {
348 configItem->setDefault();
349 if (configPolicyItem) {
350 configPolicyItem->setDefault();
351 }
352 }
353 }
354
populateRuleList()355 void RulesModel::populateRuleList()
356 {
357 qDeleteAll(m_ruleList);
358 m_ruleList.clear();
359
360 //Rule description
361 auto description = addRule(new RuleItem(QLatin1String("description"),
362 RulePolicy::NoPolicy, RuleItem::String,
363 i18n("Description"), i18n("Window matching"),
364 QIcon::fromTheme("entry-edit")));
365 description->setFlag(RuleItem::AlwaysEnabled);
366 description->setFlag(RuleItem::AffectsDescription);
367
368 // Window matching
369 auto wmclass = addRule(new RuleItem(QLatin1String("wmclass"),
370 RulePolicy::StringMatch, RuleItem::String,
371 i18n("Window class (application)"), i18n("Window matching"),
372 QIcon::fromTheme("window")));
373 wmclass->setFlag(RuleItem::AlwaysEnabled);
374 wmclass->setFlag(RuleItem::AffectsDescription);
375 wmclass->setFlag(RuleItem::AffectsWarning);
376
377 auto wmclasscomplete = addRule(new RuleItem(QLatin1String("wmclasscomplete"),
378 RulePolicy::NoPolicy, RuleItem::Boolean,
379 i18n("Match whole window class"), i18n("Window matching"),
380 QIcon::fromTheme("window")));
381 wmclasscomplete->setFlag(RuleItem::AlwaysEnabled);
382
383 // Helper item to store the detected whole window class when detecting properties
384 auto wmclasshelper = addRule(new RuleItem(QLatin1String("wmclasshelper"),
385 RulePolicy::NoPolicy, RuleItem::String,
386 i18n("Whole window class"), i18n("Window matching"),
387 QIcon::fromTheme("window")));
388 wmclasshelper->setFlag(RuleItem::SuggestionOnly);
389
390 auto types = addRule(new RuleItem(QLatin1String("types"),
391 RulePolicy::NoPolicy, RuleItem::NetTypes,
392 i18n("Window types"), i18n("Window matching"),
393 QIcon::fromTheme("window-duplicate")));
394 types->setOptionsData(windowTypesModelData());
395 types->setFlag(RuleItem::AlwaysEnabled);
396 types->setFlag(RuleItem::AffectsWarning);
397
398 addRule(new RuleItem(QLatin1String("windowrole"),
399 RulePolicy::StringMatch, RuleItem::String,
400 i18n("Window role"), i18n("Window matching"),
401 QIcon::fromTheme("dialog-object-properties")));
402
403 auto title = addRule(new RuleItem(QLatin1String("title"),
404 RulePolicy::StringMatch, RuleItem::String,
405 i18n("Window title"), i18n("Window matching"),
406 QIcon::fromTheme("edit-comment")));
407 title->setFlag(RuleItem::AffectsDescription);
408
409 addRule(new RuleItem(QLatin1String("clientmachine"),
410 RulePolicy::StringMatch, RuleItem::String,
411 i18n("Machine (hostname)"), i18n("Window matching"),
412 QIcon::fromTheme("computer")));
413
414 // Size & Position
415 auto position = addRule(new RuleItem(QLatin1String("position"),
416 RulePolicy::SetRule, RuleItem::Point,
417 i18n("Position"), i18n("Size & Position"),
418 QIcon::fromTheme("transform-move")));
419 position->setFlag(RuleItem::AffectsWarning);
420
421 auto size = addRule(new RuleItem(QLatin1String("size"),
422 RulePolicy::SetRule, RuleItem::Size,
423 i18n("Size"), i18n("Size & Position"),
424 QIcon::fromTheme("transform-scale")));
425 size->setFlag(RuleItem::AffectsWarning);
426
427 addRule(new RuleItem(QLatin1String("maximizehoriz"),
428 RulePolicy::SetRule, RuleItem::Boolean,
429 i18n("Maximized horizontally"), i18n("Size & Position"),
430 QIcon::fromTheme("resizecol")));
431
432 addRule(new RuleItem(QLatin1String("maximizevert"),
433 RulePolicy::SetRule, RuleItem::Boolean,
434 i18n("Maximized vertically"), i18n("Size & Position"),
435 QIcon::fromTheme("resizerow")));
436
437 RuleItem *desktops;
438 if (QX11Info::isPlatformX11()) {
439 // Single selection of Virtual Desktop on X11
440 desktops = new RuleItem(QLatin1String("desktops"),
441 RulePolicy::SetRule, RuleItem::Option,
442 i18n("Virtual Desktop"), i18n("Size & Position"),
443 QIcon::fromTheme("virtual-desktops"));
444 } else {
445 // Multiple selection on Wayland
446 desktops = new RuleItem(QLatin1String("desktops"),
447 RulePolicy::SetRule, RuleItem::OptionList,
448 i18n("Virtual Desktops"), i18n("Size & Position"),
449 QIcon::fromTheme("virtual-desktops"));
450 }
451 addRule(desktops);
452 desktops->setOptionsData(virtualDesktopsModelData());
453
454 connect(this, &RulesModel::virtualDesktopsUpdated,
455 this, [this] { m_rules["desktops"]->setOptionsData(virtualDesktopsModelData()); });
456
457 updateVirtualDesktops();
458
459 #ifdef KWIN_BUILD_ACTIVITIES
460 m_activities = new KActivities::Consumer(this);
461
462 auto activity = addRule(new RuleItem(QLatin1String("activity"),
463 RulePolicy::SetRule, RuleItem::OptionList,
464 i18n("Activities"), i18n("Size & Position"),
465 QIcon::fromTheme("activities")));
466 activity->setOptionsData(activitiesModelData());
467
468 // Activites consumer may update the available activities later
469 connect(m_activities, &KActivities::Consumer::activitiesChanged,
470 this, [this] { m_rules["activity"]->setOptionsData(activitiesModelData()); });
471 connect(m_activities, &KActivities::Consumer::serviceStatusChanged,
472 this, [this] { m_rules["activity"]->setOptionsData(activitiesModelData()); });
473
474 #endif
475
476 addRule(new RuleItem(QLatin1String("screen"),
477 RulePolicy::SetRule, RuleItem::Integer,
478 i18n("Screen"), i18n("Size & Position"),
479 QIcon::fromTheme("osd-shutd-screen")));
480
481 addRule(new RuleItem(QLatin1String("fullscreen"),
482 RulePolicy::SetRule, RuleItem::Boolean,
483 i18n("Fullscreen"), i18n("Size & Position"),
484 QIcon::fromTheme("view-fullscreen")));
485
486 addRule(new RuleItem(QLatin1String("minimize"),
487 RulePolicy::SetRule, RuleItem::Boolean,
488 i18n("Minimized"), i18n("Size & Position"),
489 QIcon::fromTheme("window-minimize")));
490
491 addRule(new RuleItem(QLatin1String("shade"),
492 RulePolicy::SetRule, RuleItem::Boolean,
493 i18n("Shaded"), i18n("Size & Position"),
494 QIcon::fromTheme("window-shade")));
495
496 auto placement = addRule(new RuleItem(QLatin1String("placement"),
497 RulePolicy::ForceRule, RuleItem::Option,
498 i18n("Initial placement"), i18n("Size & Position"),
499 QIcon::fromTheme("region")));
500 placement->setOptionsData(placementModelData());
501 placement->setFlag(RuleItem::AffectsWarning);
502
503 auto ignoregeometry = addRule(new RuleItem(QLatin1String("ignoregeometry"),
504 RulePolicy::SetRule, RuleItem::Boolean,
505 i18n("Ignore requested geometry"), i18n("Size & Position"),
506 QIcon::fromTheme("view-time-schedule-baselined-remove"),
507 i18n("Windows can ask to appear in a certain position.\n"
508 "By default this overrides the placement strategy\n"
509 "what might be nasty if the client abuses the feature\n"
510 "to unconditionally popup in the middle of your screen.")));
511 ignoregeometry->setFlag(RuleItem::AffectsWarning);
512
513 addRule(new RuleItem(QLatin1String("minsize"),
514 RulePolicy::ForceRule, RuleItem::Size,
515 i18n("Minimum Size"), i18n("Size & Position"),
516 QIcon::fromTheme("transform-scale")));
517
518 addRule(new RuleItem(QLatin1String("maxsize"),
519 RulePolicy::ForceRule, RuleItem::Size,
520 i18n("Maximum Size"), i18n("Size & Position"),
521 QIcon::fromTheme("transform-scale")));
522
523 addRule(new RuleItem(QLatin1String("strictgeometry"),
524 RulePolicy::ForceRule, RuleItem::Boolean,
525 i18n("Obey geometry restrictions"), i18n("Size & Position"),
526 QIcon::fromTheme("transform-crop-and-resize"),
527 i18n("Eg. terminals or video players can ask to keep a certain aspect ratio\n"
528 "or only grow by values larger than one\n"
529 "(eg. by the dimensions of one character).\n"
530 "This may be pointless and the restriction prevents arbitrary dimensions\n"
531 "like your complete screen area.")));
532
533 // Arrangement & Access
534 addRule(new RuleItem(QLatin1String("above"),
535 RulePolicy::SetRule, RuleItem::Boolean,
536 i18n("Keep above other windows"), i18n("Arrangement & Access"),
537 QIcon::fromTheme("window-keep-above")));
538
539 addRule(new RuleItem(QLatin1String("below"),
540 RulePolicy::SetRule, RuleItem::Boolean,
541 i18n("Keep below other windows"), i18n("Arrangement & Access"),
542 QIcon::fromTheme("window-keep-below")));
543
544 addRule(new RuleItem(QLatin1String("skiptaskbar"),
545 RulePolicy::SetRule, RuleItem::Boolean,
546 i18n("Skip taskbar"), i18n("Arrangement & Access"),
547 QIcon::fromTheme("kt-show-statusbar"),
548 i18n("Window shall (not) appear in the taskbar.")));
549
550 addRule(new RuleItem(QLatin1String("skippager"),
551 RulePolicy::SetRule, RuleItem::Boolean,
552 i18n("Skip pager"), i18n("Arrangement & Access"),
553 QIcon::fromTheme("org.kde.plasma.pager"),
554 i18n("Window shall (not) appear in the manager for virtual desktops")));
555
556 addRule(new RuleItem(QLatin1String("skipswitcher"),
557 RulePolicy::SetRule, RuleItem::Boolean,
558 i18n("Skip switcher"), i18n("Arrangement & Access"),
559 QIcon::fromTheme("preferences-system-windows-effect-flipswitch"),
560 i18n("Window shall (not) appear in the Alt+Tab list")));
561
562 addRule(new RuleItem(QLatin1String("shortcut"),
563 RulePolicy::SetRule, RuleItem::Shortcut,
564 i18n("Shortcut"), i18n("Arrangement & Access"),
565 QIcon::fromTheme("configure-shortcuts")));
566
567 // Appearance & Fixes
568 addRule(new RuleItem(QLatin1String("noborder"),
569 RulePolicy::SetRule, RuleItem::Boolean,
570 i18n("No titlebar and frame"), i18n("Appearance & Fixes"),
571 QIcon::fromTheme("dialog-cancel")));
572
573 auto decocolor = addRule(new RuleItem(QLatin1String("decocolor"),
574 RulePolicy::ForceRule, RuleItem::Option,
575 i18n("Titlebar color scheme"), i18n("Appearance & Fixes"),
576 QIcon::fromTheme("preferences-desktop-theme")));
577 decocolor->setOptionsData(colorSchemesModelData());
578
579 addRule(new RuleItem(QLatin1String("opacityactive"),
580 RulePolicy::ForceRule, RuleItem::Percentage,
581 i18n("Active opacity"), i18n("Appearance & Fixes"),
582 QIcon::fromTheme("edit-opacity")));
583
584 addRule(new RuleItem(QLatin1String("opacityinactive"),
585 RulePolicy::ForceRule, RuleItem::Percentage,
586 i18n("Inactive opacity"), i18n("Appearance & Fixes"),
587 QIcon::fromTheme("edit-opacity")));
588
589 auto fsplevel = addRule(new RuleItem(QLatin1String("fsplevel"),
590 RulePolicy::ForceRule, RuleItem::Option,
591 i18n("Focus stealing prevention"), i18n("Appearance & Fixes"),
592 QIcon::fromTheme("preferences-system-windows-effect-glide"),
593 i18n("KWin tries to prevent windows from taking the focus\n"
594 "(\"activate\") while you're working in another window,\n"
595 "but this may sometimes fail or superact.\n"
596 "\"None\" will unconditionally allow this window to get the focus while\n"
597 "\"Extreme\" will completely prevent it from taking the focus.")));
598 fsplevel->setOptionsData(focusModelData());
599
600 auto fpplevel = addRule(new RuleItem(QLatin1String("fpplevel"),
601 RulePolicy::ForceRule, RuleItem::Option,
602 i18n("Focus protection"), i18n("Appearance & Fixes"),
603 QIcon::fromTheme("preferences-system-windows-effect-minimize"),
604 i18n("This controls the focus protection of the currently active window.\n"
605 "None will always give the focus away,\n"
606 "Extreme will keep it.\n"
607 "Otherwise it's interleaved with the stealing prevention\n"
608 "assigned to the window that wants the focus.")));
609 fpplevel->setOptionsData(focusModelData());
610
611 addRule(new RuleItem(QLatin1String("acceptfocus"),
612 RulePolicy::ForceRule, RuleItem::Boolean,
613 i18n("Accept focus"), i18n("Appearance & Fixes"),
614 QIcon::fromTheme("preferences-desktop-cursors"),
615 i18n("Windows may prevent to get the focus (activate) when being clicked.\n"
616 "On the other hand you might wish to prevent a window\n"
617 "from getting focused on a mouse click.")));
618
619 addRule(new RuleItem(QLatin1String("disableglobalshortcuts"),
620 RulePolicy::ForceRule, RuleItem::Boolean,
621 i18n("Ignore global shortcuts"), i18n("Appearance & Fixes"),
622 QIcon::fromTheme("input-keyboard-virtual-off"),
623 i18n("When used, a window will receive\n"
624 "all keyboard inputs while it is active, including Alt+Tab etc.\n"
625 "This is especially interesting for emulators or virtual machines.\n"
626 "\n"
627 "Be warned:\n"
628 "you won't be able to Alt+Tab out of the window\n"
629 "nor use any other global shortcut (such as Alt+F2 to show KRunner)\n"
630 "while it's active!")));
631
632 addRule(new RuleItem(QLatin1String("closeable"),
633 RulePolicy::ForceRule, RuleItem::Boolean,
634 i18n("Closeable"), i18n("Appearance & Fixes"),
635 QIcon::fromTheme("dialog-close")));
636
637 auto type = addRule(new RuleItem(QLatin1String("type"),
638 RulePolicy::ForceRule, RuleItem::Option,
639 i18n("Set window type"), i18n("Appearance & Fixes"),
640 QIcon::fromTheme("window-duplicate")));
641 type->setOptionsData(windowTypesModelData());
642
643 addRule(new RuleItem(QLatin1String("desktopfile"),
644 RulePolicy::SetRule, RuleItem::String,
645 i18n("Desktop file name"), i18n("Appearance & Fixes"),
646 QIcon::fromTheme("application-x-desktop")));
647
648 addRule(new RuleItem(QLatin1String("blockcompositing"),
649 RulePolicy::ForceRule, RuleItem::Boolean,
650 i18n("Block compositing"), i18n("Appearance & Fixes"),
651 QIcon::fromTheme("composite-track-on")));
652 }
653
654
x11PropertyHash()655 const QHash<QString, QString> RulesModel::x11PropertyHash()
656 {
657 static const auto propertyToRule = QHash<QString, QString> {
658 { "caption", "title" },
659 { "role", "windowrole" },
660 { "clientMachine", "clientmachine" },
661 { "maximizeHorizontal", "maximizehoriz" },
662 { "maximizeVertical", "maximizevert" },
663 { "minimized", "minimize" },
664 { "shaded", "shade" },
665 { "fullscreen", "fullscreen" },
666 { "keepAbove", "above" },
667 { "keepBelow", "below" },
668 { "noBorder", "noborder" },
669 { "skipTaskbar", "skiptaskbar" },
670 { "skipPager", "skippager" },
671 { "skipSwitcher", "skipswitcher" },
672 { "type", "type" },
673 { "desktopFile", "desktopfile" },
674 { "desktops", "desktops" },
675 };
676 return propertyToRule;
677 };
678
setSuggestedProperties(const QVariantMap & info)679 void RulesModel::setSuggestedProperties(const QVariantMap &info)
680 {
681 // Properties that cannot be directly applied via x11PropertyHash
682 const QPoint position = QPoint(info.value("x").toInt(), info.value("y").toInt());
683 const QSize size = QSize(info.value("width").toInt(), info.value("height").toInt());
684
685 m_rules["position"]->setSuggestedValue(position);
686 m_rules["size"]->setSuggestedValue(size);
687 m_rules["minsize"]->setSuggestedValue(size);
688 m_rules["maxsize"]->setSuggestedValue(size);
689
690 NET::WindowType window_type = static_cast<NET::WindowType>(info.value("type", 0).toInt());
691 if (window_type == NET::Unknown) {
692 window_type = NET::Normal;
693 }
694 m_rules["types"]->setSuggestedValue(1 << window_type);
695
696 const QString wmsimpleclass = info.value("resourceClass").toString();
697 const QString wmcompleteclass = QStringLiteral("%1 %2").arg(info.value("resourceName").toString(),
698 info.value("resourceClass").toString());
699
700 m_rules["wmclass"]->setSuggestedValue(wmsimpleclass);
701 m_rules["wmclasshelper"]->setSuggestedValue(wmcompleteclass);
702
703 #ifdef KWIN_BUILD_ACTIVITIES
704 const QStringList activities = info.value("activities").toStringList();
705 m_rules["activity"]->setSuggestedValue(activities.isEmpty() ? QStringList{ Activities::nullUuid() }
706 : activities);
707 #endif
708
709 const auto ruleForProperty = x11PropertyHash();
710 for (QString &property : info.keys()) {
711 if (!ruleForProperty.contains(property)) {
712 continue;
713 }
714 const QString ruleKey = ruleForProperty.value(property, QString());
715 Q_ASSERT(hasRule(ruleKey));
716
717 m_rules[ruleKey]->setSuggestedValue(info.value(property));
718 }
719
720 Q_EMIT dataChanged(index(0), index(rowCount()-1), {RulesModel::SuggestedValueRole});
721 }
722
723
windowTypesModelData() const724 QList<OptionsModel::Data> RulesModel::windowTypesModelData() const
725 {
726 static const auto modelData = QList<OptionsModel::Data> {
727 //TODO: Find/create better icons
728 { NET::Normal, i18n("Normal Window") , QIcon::fromTheme("window") },
729 { NET::Dialog, i18n("Dialog Window") , QIcon::fromTheme("window-duplicate") },
730 { NET::Utility, i18n("Utility Window") , QIcon::fromTheme("dialog-object-properties") },
731 { NET::Dock, i18n("Dock (panel)") , QIcon::fromTheme("list-remove") },
732 { NET::Toolbar, i18n("Toolbar") , QIcon::fromTheme("tools") },
733 { NET::Menu, i18n("Torn-Off Menu") , QIcon::fromTheme("overflow-menu-left") },
734 { NET::Splash, i18n("Splash Screen") , QIcon::fromTheme("embosstool") },
735 { NET::Desktop, i18n("Desktop") , QIcon::fromTheme("desktop") },
736 // { NET::Override, i18n("Unmanaged Window") }, deprecated
737 { NET::TopMenu, i18n("Standalone Menubar"), QIcon::fromTheme("application-menu") },
738 { NET::OnScreenDisplay, i18n("On Screen Display"), QIcon::fromTheme("osd-duplicate") }
739 };
740 return modelData;
741 }
742
virtualDesktopsModelData() const743 QList<OptionsModel::Data> RulesModel::virtualDesktopsModelData() const
744 {
745 QList<OptionsModel::Data> modelData = { {QString(), i18n("All Desktops"), QIcon::fromTheme("window-pin")} };
746 for (const DBusDesktopDataStruct &desktop : m_virtualDesktops) {
747 modelData << OptionsModel::Data{
748 desktop.id,
749 QString::number(desktop.position + 1).rightJustified(2) + QStringLiteral(": ") + desktop.name,
750 QIcon::fromTheme("virtual-desktops")
751 };
752 }
753 return modelData;
754 }
755
756
activitiesModelData() const757 QList<OptionsModel::Data> RulesModel::activitiesModelData() const
758 {
759 #ifdef KWIN_BUILD_ACTIVITIES
760 QList<OptionsModel::Data> modelData;
761
762 modelData << OptionsModel::Data{
763 Activities::nullUuid(),
764 i18n("All Activities"),
765 QIcon::fromTheme("activities")
766 };
767
768 const auto activities = m_activities->activities(KActivities::Info::Running);
769 if (m_activities->serviceStatus() == KActivities::Consumer::Running) {
770 for (const QString &activityId : activities) {
771 const KActivities::Info info(activityId);
772 modelData << OptionsModel::Data{ activityId, info.name(), QIcon::fromTheme(info.icon()) };
773 }
774 }
775
776 return modelData;
777 #else
778 return {};
779 #endif
780 }
781
placementModelData() const782 QList<OptionsModel::Data> RulesModel::placementModelData() const
783 {
784 static const auto modelData = QList<OptionsModel::Data> {
785 { Placement::Default, i18n("Default") },
786 { Placement::NoPlacement, i18n("No Placement") },
787 { Placement::Smart, i18n("Minimal Overlapping") },
788 { Placement::Maximizing, i18n("Maximized") },
789 { Placement::Cascade, i18n("Cascaded") },
790 { Placement::Centered, i18n("Centered") },
791 { Placement::Random, i18n("Random") },
792 { Placement::ZeroCornered, i18n("In Top-Left Corner") },
793 { Placement::UnderMouse, i18n("Under Mouse") },
794 { Placement::OnMainWindow, i18n("On Main Window") }
795 };
796 return modelData;
797 }
798
focusModelData() const799 QList<OptionsModel::Data> RulesModel::focusModelData() const
800 {
801 static const auto modelData = QList<OptionsModel::Data> {
802 { 0, i18n("None") },
803 { 1, i18n("Low") },
804 { 2, i18n("Normal") },
805 { 3, i18n("High") },
806 { 4, i18n("Extreme") }
807 };
808 return modelData;
809 }
810
colorSchemesModelData() const811 QList<OptionsModel::Data> RulesModel::colorSchemesModelData() const
812 {
813 QList<OptionsModel::Data> modelData;
814
815 KColorSchemeManager schemes;
816 QAbstractItemModel *schemesModel = schemes.model();
817
818 // Skip row 0, which is Default scheme
819 for (int r = 1; r < schemesModel->rowCount(); r++) {
820 const QModelIndex index = schemesModel->index(r, 0);
821 modelData << OptionsModel::Data{
822 QFileInfo(index.data(Qt::UserRole).toString()).baseName(),
823 index.data(Qt::DisplayRole).toString(),
824 index.data(Qt::DecorationRole).value<QIcon>()
825 };
826 }
827
828 return modelData;
829 }
830
detectWindowProperties(int miliseconds)831 void RulesModel::detectWindowProperties(int miliseconds)
832 {
833 QTimer::singleShot(miliseconds, this, &RulesModel::selectX11Window);
834 }
835
selectX11Window()836 void RulesModel::selectX11Window()
837 {
838 QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KWin"),
839 QStringLiteral("/KWin"),
840 QStringLiteral("org.kde.KWin"),
841 QStringLiteral("queryWindowInfo"));
842
843 QDBusPendingReply<QVariantMap> async = QDBusConnection::sessionBus().asyncCall(message);
844
845 QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(async, this);
846 connect(callWatcher, &QDBusPendingCallWatcher::finished, this,
847 [this](QDBusPendingCallWatcher *self) {
848 QDBusPendingReply<QVariantMap> reply = *self;
849 self->deleteLater();
850 if (!reply.isValid()) {
851 if (reply.error().name() == QLatin1String("org.kde.KWin.Error.InvalidWindow")) {
852 Q_EMIT showErrorMessage(i18n("Could not detect window properties. The window is not managed by KWin."));
853 }
854 return;
855 }
856 const QVariantMap windowInfo = reply.value();
857 setSuggestedProperties(windowInfo);
858 Q_EMIT showSuggestions();
859 }
860 );
861 }
862
updateVirtualDesktops()863 void RulesModel::updateVirtualDesktops()
864 {
865 QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KWin"),
866 QStringLiteral("/VirtualDesktopManager"),
867 QStringLiteral("org.freedesktop.DBus.Properties"),
868 QStringLiteral("Get"));
869 message.setArguments(QVariantList{
870 QStringLiteral("org.kde.KWin.VirtualDesktopManager"),
871 QStringLiteral("desktops")
872 });
873
874 QDBusPendingReply<QVariant> async = QDBusConnection::sessionBus().asyncCall(message);
875
876 QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(async, this);
877 connect(callWatcher, &QDBusPendingCallWatcher::finished, this,
878 [this](QDBusPendingCallWatcher *self) {
879 QDBusPendingReply<QVariant> reply = *self;
880 self->deleteLater();
881 if (!reply.isValid()) {
882 return;
883 }
884 m_virtualDesktops = qdbus_cast<KWin::DBusDesktopDataVector>(reply.value());
885 Q_EMIT virtualDesktopsUpdated();
886 }
887 );
888 }
889
890
891 } //namespace
892