1/********************************************************************
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5Copyright (C) 2004 Lubos Lunak <l.lunak@kde.org>
6
7This program is free software; you can redistribute it and/or modify
8it under the terms of the GNU General Public License as published by
9the Free Software Foundation; either version 2 of the License, or
10(at your option) any later version.
11
12This program is distributed in the hope that it will be useful,
13but WITHOUT ANY WARRANTY; without even the implied warranty of
14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15GNU General Public License for more details.
16
17You should have received a copy of the GNU General Public License
18along with this program. If not, see <http://www.gnu.org/licenses/>.
19*********************************************************************/
20
21#include "rules.h"
22
23#include <fixx11h.h>
24#include <kconfig.h>
25#include <KDE/KXMessages>
26#include <QRegExp>
27#include <ktemporaryfile.h>
28#include <QFile>
29#include <ktoolinvocation.h>
30
31#ifndef KCMRULES
32#include "client.h"
33#include "client_machine.h"
34#include "screens.h"
35#include "workspace.h"
36#endif
37
38namespace KWin
39{
40
41Rules::Rules()
42 : temporary_state(0)
43 , wmclassmatch(UnimportantMatch)
44 , wmclasscomplete(UnimportantMatch)
45 , windowrolematch(UnimportantMatch)
46 , titlematch(UnimportantMatch)
47 , clientmachinematch(UnimportantMatch)
48 , types(NET::AllTypesMask)
49 , placementrule(UnusedForceRule)
50 , positionrule(UnusedSetRule)
51 , sizerule(UnusedSetRule)
52 , minsizerule(UnusedForceRule)
53 , maxsizerule(UnusedForceRule)
54 , opacityactiverule(UnusedForceRule)
55 , opacityinactiverule(UnusedForceRule)
56 , ignoregeometryrule(UnusedSetRule)
57 , desktoprule(UnusedSetRule)
58 , screenrule(UnusedSetRule)
59 , activityrule(UnusedSetRule)
60 , typerule(UnusedForceRule)
61 , maximizevertrule(UnusedSetRule)
62 , maximizehorizrule(UnusedSetRule)
63 , minimizerule(UnusedSetRule)
64 , shaderule(UnusedSetRule)
65 , skiptaskbarrule(UnusedSetRule)
66 , skippagerrule(UnusedSetRule)
67 , skipswitcherrule(UnusedSetRule)
68 , aboverule(UnusedSetRule)
69 , belowrule(UnusedSetRule)
70 , fullscreenrule(UnusedSetRule)
71 , noborderrule(UnusedSetRule)
72 , blockcompositingrule(UnusedForceRule)
73 , fsplevelrule(UnusedForceRule)
74 , acceptfocusrule(UnusedForceRule)
75 , closeablerule(UnusedForceRule)
76 , autogrouprule(UnusedForceRule)
77 , autogroupfgrule(UnusedForceRule)
78 , autogroupidrule(UnusedForceRule)
79 , strictgeometryrule(UnusedForceRule)
80 , shortcutrule(UnusedSetRule)
81 , disableglobalshortcutsrule(UnusedForceRule)
82{
83}
84
85Rules::Rules(const QString& str, bool temporary)
86 : temporary_state(temporary ? 2 : 0)
87{
88 KTemporaryFile file;
89 if (file.open()) {
90 QByteArray s = str.toUtf8();
91 file.write(s.data(), s.length());
92 }
93 file.flush();
94 KConfig cfg(file.fileName(), KConfig::SimpleConfig);
95 readFromCfg(cfg.group(QString()));
96 if (description.isEmpty())
97 description = "temporary";
98}
99
100#define READ_MATCH_STRING( var, func ) \
101 var = cfg.readEntry( #var ) func; \
102 var##match = (StringMatch) qMax( FirstStringMatch, \
103 qMin( LastStringMatch, static_cast< StringMatch >( cfg.readEntry( #var "match",0 ))));
104
105#define READ_SET_RULE( var, func, def ) \
106 var = func ( cfg.readEntry( #var, def)); \
107 var##rule = readSetRule( cfg, #var "rule" );
108
109#define READ_SET_RULE_DEF( var , func, def ) \
110 var = func ( cfg.readEntry( #var, def )); \
111 var##rule = readSetRule( cfg, #var "rule" );
112
113#define READ_FORCE_RULE( var, func, def) \
114 var = func ( cfg.readEntry( #var, def)); \
115 var##rule = readForceRule( cfg, #var "rule" );
116
117#define READ_FORCE_RULE2( var, def, func, funcarg ) \
118 var = func ( cfg.readEntry( #var, def),funcarg ); \
119 var##rule = readForceRule( cfg, #var "rule" );
120
121
122
123Rules::Rules(const KConfigGroup& cfg)
124 : temporary_state(0)
125{
126 readFromCfg(cfg);
127}
128
129static int limit0to4(int i)
130{
131 return qMax(0, qMin(4, i));
132}
133
134void Rules::readFromCfg(const KConfigGroup& cfg)
135{
136 description = cfg.readEntry("Description");
137 if (description.isEmpty()) // capitalized first, lowercase for backwards compatibility
138 description = cfg.readEntry("description");
139 READ_MATCH_STRING(wmclass, .toLower().toLatin1());
140 wmclasscomplete = cfg.readEntry("wmclasscomplete" , false);
141 READ_MATCH_STRING(windowrole, .toLower().toLatin1());
142 READ_MATCH_STRING(title,);
143 READ_MATCH_STRING(clientmachine, .toLower().toLatin1());
144 types = cfg.readEntry("types", uint(NET::AllTypesMask));
145 READ_FORCE_RULE2(placement, QString(), Placement::policyFromString, false);
146 READ_SET_RULE_DEF(position, , invalidPoint);
147 READ_SET_RULE(size, , QSize());
148 if (size.isEmpty() && sizerule != (SetRule)Remember)
149 sizerule = UnusedSetRule;
150 READ_FORCE_RULE(minsize, , QSize());
151 if (!minsize.isValid())
152 minsize = QSize(1, 1);
153 READ_FORCE_RULE(maxsize, , QSize());
154 if (maxsize.isEmpty())
155 maxsize = QSize(32767, 32767);
156 READ_FORCE_RULE(opacityactive, , 0);
157 if (opacityactive < 0 || opacityactive > 100)
158 opacityactive = 100;
159 READ_FORCE_RULE(opacityinactive, , 0);
160 if (opacityinactive < 0 || opacityinactive > 100)
161 opacityinactive = 100;
162 READ_SET_RULE(ignoregeometry, , false);
163 READ_SET_RULE(desktop, , 0);
164 READ_SET_RULE(screen, , 0);
165 READ_SET_RULE(activity, , QString());
166 type = readType(cfg, "type");
167 typerule = type != NET::Unknown ? readForceRule(cfg, "typerule") : UnusedForceRule;
168 READ_SET_RULE(maximizevert, , false);
169 READ_SET_RULE(maximizehoriz, , false);
170 READ_SET_RULE(minimize, , false);
171 READ_SET_RULE(shade, , false);
172 READ_SET_RULE(skiptaskbar, , false);
173 READ_SET_RULE(skippager, , false);
174 READ_SET_RULE(skipswitcher, , false);
175 READ_SET_RULE(above, , false);
176 READ_SET_RULE(below, , false);
177 READ_SET_RULE(fullscreen, , false);
178 READ_SET_RULE(noborder, , false);
179 READ_FORCE_RULE(blockcompositing, , false);
180 READ_FORCE_RULE(fsplevel, limit0to4, 0); // fsp is 0-4
181 READ_FORCE_RULE(acceptfocus, , false);
182 READ_FORCE_RULE(closeable, , false);
183 READ_FORCE_RULE(autogroup, , false);
184 READ_FORCE_RULE(autogroupfg, , true);
185 READ_FORCE_RULE(autogroupid, , QString());
186 READ_FORCE_RULE(strictgeometry, , false);
187 READ_SET_RULE(shortcut, , QString());
188 READ_FORCE_RULE(disableglobalshortcuts, , false);
189}
190
191#undef READ_MATCH_STRING
192#undef READ_SET_RULE
193#undef READ_FORCE_RULE
194#undef READ_FORCE_RULE2
195
196#define WRITE_MATCH_STRING( var, cast, force ) \
197 if ( !var.isEmpty() || force ) \
198 { \
199 cfg.writeEntry( #var, cast var ); \
200 cfg.writeEntry( #var "match", (int)var##match ); \
201 } \
202 else \
203 { \
204 cfg.deleteEntry( #var ); \
205 cfg.deleteEntry( #var "match" ); \
206 }
207
208#define WRITE_SET_RULE( var, func ) \
209 if ( var##rule != UnusedSetRule ) \
210 { \
211 cfg.writeEntry( #var, func ( var )); \
212 cfg.writeEntry( #var "rule", (int)var##rule ); \
213 } \
214 else \
215 { \
216 cfg.deleteEntry( #var ); \
217 cfg.deleteEntry( #var "rule" ); \
218 }
219
220#define WRITE_FORCE_RULE( var, func ) \
221 if ( var##rule != UnusedForceRule ) \
222 { \
223 cfg.writeEntry( #var, func ( var )); \
224 cfg.writeEntry( #var "rule", (int)var##rule ); \
225 } \
226 else \
227 { \
228 cfg.deleteEntry( #var ); \
229 cfg.deleteEntry( #var "rule" ); \
230 }
231
232void Rules::write(KConfigGroup& cfg) const
233{
234 cfg.writeEntry("Description", description);
235 // always write wmclass
236 WRITE_MATCH_STRING(wmclass, (const char*), true);
237 cfg.writeEntry("wmclasscomplete", wmclasscomplete);
238 WRITE_MATCH_STRING(windowrole, (const char*), false);
239 WRITE_MATCH_STRING(title, , false);
240 WRITE_MATCH_STRING(clientmachine, (const char*), false);
241 if (types != NET::AllTypesMask)
242 cfg.writeEntry("types", uint(types));
243 else
244 cfg.deleteEntry("types");
245 WRITE_FORCE_RULE(placement, Placement::policyToString);
246 WRITE_SET_RULE(position,);
247 WRITE_SET_RULE(size,);
248 WRITE_FORCE_RULE(minsize,);
249 WRITE_FORCE_RULE(maxsize,);
250 WRITE_FORCE_RULE(opacityactive,);
251 WRITE_FORCE_RULE(opacityinactive,);
252 WRITE_SET_RULE(ignoregeometry,);
253 WRITE_SET_RULE(desktop,);
254 WRITE_SET_RULE(screen,);
255 WRITE_SET_RULE(activity,);
256 WRITE_FORCE_RULE(type, int);
257 WRITE_SET_RULE(maximizevert,);
258 WRITE_SET_RULE(maximizehoriz,);
259 WRITE_SET_RULE(minimize,);
260 WRITE_SET_RULE(shade,);
261 WRITE_SET_RULE(skiptaskbar,);
262 WRITE_SET_RULE(skippager,);
263 WRITE_SET_RULE(skipswitcher,);
264 WRITE_SET_RULE(above,);
265 WRITE_SET_RULE(below,);
266 WRITE_SET_RULE(fullscreen,);
267 WRITE_SET_RULE(noborder,);
268 WRITE_FORCE_RULE(blockcompositing,);
269 WRITE_FORCE_RULE(fsplevel,);
270 WRITE_FORCE_RULE(acceptfocus,);
271 WRITE_FORCE_RULE(closeable,);
272 WRITE_FORCE_RULE(autogroup,);
273 WRITE_FORCE_RULE(autogroupfg,);
274 WRITE_FORCE_RULE(autogroupid,);
275 WRITE_FORCE_RULE(strictgeometry,);
276 WRITE_SET_RULE(shortcut,);
277 WRITE_FORCE_RULE(disableglobalshortcuts,);
278}
279
280#undef WRITE_MATCH_STRING
281#undef WRITE_SET_RULE
282#undef WRITE_FORCE_RULE
283
284// returns true if it doesn't affect anything
285bool Rules::isEmpty() const
286{
287 return(placementrule == UnusedForceRule
288 && positionrule == UnusedSetRule
289 && sizerule == UnusedSetRule
290 && minsizerule == UnusedForceRule
291 && maxsizerule == UnusedForceRule
292 && opacityactiverule == UnusedForceRule
293 && opacityinactiverule == UnusedForceRule
294 && ignoregeometryrule == UnusedSetRule
295 && desktoprule == UnusedSetRule
296 && screenrule == UnusedSetRule
297 && activityrule == UnusedSetRule
298 && typerule == UnusedForceRule
299 && maximizevertrule == UnusedSetRule
300 && maximizehorizrule == UnusedSetRule
301 && minimizerule == UnusedSetRule
302 && shaderule == UnusedSetRule
303 && skiptaskbarrule == UnusedSetRule
304 && skippagerrule == UnusedSetRule
305 && skipswitcherrule == UnusedSetRule
306 && aboverule == UnusedSetRule
307 && belowrule == UnusedSetRule
308 && fullscreenrule == UnusedSetRule
309 && noborderrule == UnusedSetRule
310 && blockcompositingrule == UnusedForceRule
311 && fsplevelrule == UnusedForceRule
312 && acceptfocusrule == UnusedForceRule
313 && closeablerule == UnusedForceRule
314 && autogrouprule == UnusedForceRule
315 && autogroupfgrule == UnusedForceRule
316 && autogroupidrule == UnusedForceRule
317 && strictgeometryrule == UnusedForceRule
318 && shortcutrule == UnusedSetRule
319 && disableglobalshortcutsrule == UnusedForceRule);
320}
321
322Rules::SetRule Rules::readSetRule(const KConfigGroup& cfg, const QString& key)
323{
324 int v = cfg.readEntry(key, 0);
325 if (v >= DontAffect && v <= ForceTemporarily)
326 return static_cast< SetRule >(v);
327 return UnusedSetRule;
328}
329
330Rules::ForceRule Rules::readForceRule(const KConfigGroup& cfg, const QString& key)
331{
332 int v = cfg.readEntry(key, 0);
333 if (v == DontAffect || v == Force || v == ForceTemporarily)
334 return static_cast< ForceRule >(v);
335 return UnusedForceRule;
336}
337
338NET::WindowType Rules::readType(const KConfigGroup& cfg, const QString& key)
339{
340 int v = cfg.readEntry(key, 0);
341 if (v >= NET::Normal && v <= NET::Splash)
342 return static_cast< NET::WindowType >(v);
343 return NET::Unknown;
344}
345
346bool Rules::matchType(NET::WindowType match_type) const
347{
348 if (types != NET::AllTypesMask) {
349 if (match_type == NET::Unknown)
350 match_type = NET::Normal; // NET::Unknown->NET::Normal is only here for matching
351 if (!NET::typeMatchesMask(match_type, types))
352 return false;
353 }
354 return true;
355}
356
357bool Rules::matchWMClass(const QByteArray& match_class, const QByteArray& match_name) const
358{
359 if (wmclassmatch != UnimportantMatch) {
360 // TODO optimize?
361 QByteArray cwmclass = wmclasscomplete
362 ? match_name + ' ' + match_class : match_class;
363 if (wmclassmatch == RegExpMatch && QRegExp(wmclass).indexIn(cwmclass) == -1)
364 return false;
365 if (wmclassmatch == ExactMatch && wmclass != cwmclass)
366 return false;
367 if (wmclassmatch == SubstringMatch && !cwmclass.contains(wmclass))
368 return false;
369 }
370 return true;
371}
372
373bool Rules::matchRole(const QByteArray& match_role) const
374{
375 if (windowrolematch != UnimportantMatch) {
376 if (windowrolematch == RegExpMatch && QRegExp(windowrole).indexIn(match_role) == -1)
377 return false;
378 if (windowrolematch == ExactMatch && windowrole != match_role)
379 return false;
380 if (windowrolematch == SubstringMatch && !match_role.contains(windowrole))
381 return false;
382 }
383 return true;
384}
385
386bool Rules::matchTitle(const QString& match_title) const
387{
388 if (titlematch != UnimportantMatch) {
389 if (titlematch == RegExpMatch && QRegExp(title).indexIn(match_title) == -1)
390 return false;
391 if (titlematch == ExactMatch && title != match_title)
392 return false;
393 if (titlematch == SubstringMatch && !match_title.contains(title))
394 return false;
395 }
396 return true;
397}
398
399bool Rules::matchClientMachine(const QByteArray& match_machine, bool local) const
400{
401 if (clientmachinematch != UnimportantMatch) {
402 // if it's localhost, check also "localhost" before checking hostname
403 if (match_machine != "localhost" && local
404 && matchClientMachine("localhost", true))
405 return true;
406 if (clientmachinematch == RegExpMatch
407 && QRegExp(clientmachine).indexIn(match_machine) == -1)
408 return false;
409 if (clientmachinematch == ExactMatch
410 && clientmachine != match_machine)
411 return false;
412 if (clientmachinematch == SubstringMatch
413 && !match_machine.contains(clientmachine))
414 return false;
415 }
416 return true;
417}
418
419#ifndef KCMRULES
420bool Rules::match(const Client* c) const
421{
422 if (!matchType(c->windowType(true)))
423 return false;
424 if (!matchWMClass(c->resourceClass(), c->resourceName()))
425 return false;
426 if (!matchRole(c->windowRole()))
427 return false;
428 if (!matchTitle(c->caption(false)))
429 return false;
430 if (!matchClientMachine(c->clientMachine()->hostName(), c->clientMachine()->isLocal()))
431 return false;
432 return true;
433}
434
435#define NOW_REMEMBER(_T_, _V_) ((selection & _T_) && (_V_##rule == (SetRule)Remember))
436
437bool Rules::update(Client* c, int selection)
438{
439 // TODO check this setting is for this client ?
440 bool updated = false;
441 if NOW_REMEMBER(Position, position) {
442 if (!c->isFullScreen()) {
443 QPoint new_pos = position;
444 // don't use the position in the direction which is maximized
445 if ((c->maximizeMode() & MaximizeHorizontal) == 0)
446 new_pos.setX(c->pos().x());
447 if ((c->maximizeMode() & MaximizeVertical) == 0)
448 new_pos.setY(c->pos().y());
449 updated = updated || position != new_pos;
450 position = new_pos;
451 }
452 }
453 if NOW_REMEMBER(Size, size) {
454 if (!c->isFullScreen()) {
455 QSize new_size = size;
456 // don't use the position in the direction which is maximized
457 if ((c->maximizeMode() & MaximizeHorizontal) == 0)
458 new_size.setWidth(c->size().width());
459 if ((c->maximizeMode() & MaximizeVertical) == 0)
460 new_size.setHeight(c->size().height());
461 updated = updated || size != new_size;
462 size = new_size;
463 }
464 }
465 if NOW_REMEMBER(Desktop, desktop) {
466 updated = updated || desktop != c->desktop();
467 desktop = c->desktop();
468 }
469 if NOW_REMEMBER(Screen, screen) {
470 updated = updated || screen != c->screen();
471 screen = c->screen();
472 }
473 if NOW_REMEMBER(Activity, activity) {
474 // TODO: ivan - multiple activities support
475 const QString & joinedActivities = c->activities().join(",");
476 updated = updated || activity != joinedActivities;
477 activity = joinedActivities;
478 }
479 if NOW_REMEMBER(MaximizeVert, maximizevert) {
480 updated = updated || maximizevert != bool(c->maximizeMode() & MaximizeVertical);
481 maximizevert = c->maximizeMode() & MaximizeVertical;
482 }
483 if NOW_REMEMBER(MaximizeHoriz, maximizehoriz) {
484 updated = updated || maximizehoriz != bool(c->maximizeMode() & MaximizeHorizontal);
485 maximizehoriz = c->maximizeMode() & MaximizeHorizontal;
486 }
487 if NOW_REMEMBER(Minimize, minimize) {
488 updated = updated || minimize != c->isMinimized();
489 minimize = c->isMinimized();
490 }
491 if NOW_REMEMBER(Shade, shade) {
492 updated = updated || (shade != (c->shadeMode() != ShadeNone));
493 shade = c->shadeMode() != ShadeNone;
494 }
495 if NOW_REMEMBER(SkipTaskbar, skiptaskbar) {
496 updated = updated || skiptaskbar != c->skipTaskbar();
497 skiptaskbar = c->skipTaskbar();
498 }
499 if NOW_REMEMBER(SkipPager, skippager) {
500 updated = updated || skippager != c->skipPager();
501 skippager = c->skipPager();
502 }
503 if NOW_REMEMBER(SkipSwitcher, skipswitcher) {
504 updated = updated || skipswitcher != c->skipSwitcher();
505 skipswitcher = c->skipSwitcher();
506 }
507 if NOW_REMEMBER(Above, above) {
508 updated = updated || above != c->keepAbove();
509 above = c->keepAbove();
510 }
511 if NOW_REMEMBER(Below, below) {
512 updated = updated || below != c->keepBelow();
513 below = c->keepBelow();
514 }
515 if NOW_REMEMBER(Fullscreen, fullscreen) {
516 updated = updated || fullscreen != c->isFullScreen();
517 fullscreen = c->isFullScreen();
518 }
519 if NOW_REMEMBER(NoBorder, noborder) {
520 updated = updated || noborder != c->noBorder();
521 noborder = c->noBorder();
522 }
523 if NOW_REMEMBER(OpacityActive, opacityactive) {
524 // TODO
525 }
526 if NOW_REMEMBER(OpacityInactive, opacityinactive) {
527 // TODO
528 }
529 return updated;
530}
531
532#undef NOW_REMEMBER
533
534#define APPLY_RULE( var, name, type ) \
535 bool Rules::apply##name( type& arg, bool init ) const \
536 { \
537 if ( checkSetRule( var##rule, init )) \
538 arg = this->var; \
539 return checkSetStop( var##rule ); \
540 }
541
542#define APPLY_FORCE_RULE( var, name, type ) \
543 bool Rules::apply##name( type& arg ) const \
544 { \
545 if ( checkForceRule( var##rule )) \
546 arg = this->var; \
547 return checkForceStop( var##rule ); \
548 }
549
550APPLY_FORCE_RULE(placement, Placement, Placement::Policy)
551
552bool Rules::applyGeometry(QRect& rect, bool init) const
553{
554 QPoint p = rect.topLeft();
555 QSize s = rect.size();
556 bool ret = false; // no short-circuiting
557 if (applyPosition(p, init)) {
558 rect.moveTopLeft(p);
559 ret = true;
560 }
561 if (applySize(s, init)) {
562 rect.setSize(s);
563 ret = true;
564 }
565 return ret;
566}
567
568bool Rules::applyPosition(QPoint& pos, bool init) const
569{
570 if (this->position != invalidPoint && checkSetRule(positionrule, init))
571 pos = this->position;
572 return checkSetStop(positionrule);
573}
574
575bool Rules::applySize(QSize& s, bool init) const
576{
577 if (this->size.isValid() && checkSetRule(sizerule, init))
578 s = this->size;
579 return checkSetStop(sizerule);
580}
581
582APPLY_FORCE_RULE(minsize, MinSize, QSize)
583APPLY_FORCE_RULE(maxsize, MaxSize, QSize)
584APPLY_FORCE_RULE(opacityactive, OpacityActive, int)
585APPLY_FORCE_RULE(opacityinactive, OpacityInactive, int)
586APPLY_RULE(ignoregeometry, IgnoreGeometry, bool)
587
588APPLY_RULE(desktop, Desktop, int)
589APPLY_RULE(screen, Screen, int)
590APPLY_RULE(activity, Activity, QString)
591APPLY_FORCE_RULE(type, Type, NET::WindowType)
592
593bool Rules::applyMaximizeHoriz(MaximizeMode& mode, bool init) const
594{
595 if (checkSetRule(maximizehorizrule, init))
596 mode = static_cast< MaximizeMode >((maximizehoriz ? MaximizeHorizontal : 0) | (mode & MaximizeVertical));
597 return checkSetStop(maximizehorizrule);
598}
599
600bool Rules::applyMaximizeVert(MaximizeMode& mode, bool init) const
601{
602 if (checkSetRule(maximizevertrule, init))
603 mode = static_cast< MaximizeMode >((maximizevert ? MaximizeVertical : 0) | (mode & MaximizeHorizontal));
604 return checkSetStop(maximizevertrule);
605}
606
607APPLY_RULE(minimize, Minimize, bool)
608
609bool Rules::applyShade(ShadeMode& sh, bool init) const
610{
611 if (checkSetRule(shaderule, init)) {
612 if (!this->shade)
613 sh = ShadeNone;
614 if (this->shade && sh == ShadeNone)
615 sh = ShadeNormal;
616 }
617 return checkSetStop(shaderule);
618}
619
620APPLY_RULE(skiptaskbar, SkipTaskbar, bool)
621APPLY_RULE(skippager, SkipPager, bool)
622APPLY_RULE(skipswitcher, SkipSwitcher, bool)
623APPLY_RULE(above, KeepAbove, bool)
624APPLY_RULE(below, KeepBelow, bool)
625APPLY_RULE(fullscreen, FullScreen, bool)
626APPLY_RULE(noborder, NoBorder, bool)
627APPLY_FORCE_RULE(blockcompositing, BlockCompositing, bool)
628APPLY_FORCE_RULE(fsplevel, FSP, int)
629APPLY_FORCE_RULE(acceptfocus, AcceptFocus, bool)
630APPLY_FORCE_RULE(closeable, Closeable, bool)
631APPLY_FORCE_RULE(autogroup, Autogrouping, bool)
632APPLY_FORCE_RULE(autogroupfg, AutogroupInForeground, bool)
633APPLY_FORCE_RULE(autogroupid, AutogroupById, QString)
634APPLY_FORCE_RULE(strictgeometry, StrictGeometry, bool)
635APPLY_RULE(shortcut, Shortcut, QString)
636APPLY_FORCE_RULE(disableglobalshortcuts, DisableGlobalShortcuts, bool)
637
638
639#undef APPLY_RULE
640#undef APPLY_FORCE_RULE
641
642bool Rules::isTemporary() const
643{
644 return temporary_state > 0;
645}
646
647bool Rules::discardTemporary(bool force)
648{
649 if (temporary_state == 0) // not temporary
650 return false;
651 if (force || --temporary_state == 0) { // too old
652 delete this;
653 return true;
654 }
655 return false;
656}
657
658#define DISCARD_USED_SET_RULE( var ) \
659 do { \
660 if ( var##rule == ( SetRule ) ApplyNow || ( withdrawn && var##rule == ( SetRule ) ForceTemporarily )) \
661 var##rule = UnusedSetRule; \
662 } while ( false )
663#define DISCARD_USED_FORCE_RULE( var ) \
664 do { \
665 if ( withdrawn && var##rule == ( ForceRule ) ForceTemporarily ) \
666 var##rule = UnusedForceRule; \
667 } while ( false )
668
669void Rules::discardUsed(bool withdrawn)
670{
671 DISCARD_USED_FORCE_RULE(placement);
672 DISCARD_USED_SET_RULE(position);
673 DISCARD_USED_SET_RULE(size);
674 DISCARD_USED_FORCE_RULE(minsize);
675 DISCARD_USED_FORCE_RULE(maxsize);
676 DISCARD_USED_FORCE_RULE(opacityactive);
677 DISCARD_USED_FORCE_RULE(opacityinactive);
678 DISCARD_USED_SET_RULE(ignoregeometry);
679 DISCARD_USED_SET_RULE(desktop);
680 DISCARD_USED_SET_RULE(screen);
681 DISCARD_USED_SET_RULE(activity);
682 DISCARD_USED_FORCE_RULE(type);
683 DISCARD_USED_SET_RULE(maximizevert);
684 DISCARD_USED_SET_RULE(maximizehoriz);
685 DISCARD_USED_SET_RULE(minimize);
686 DISCARD_USED_SET_RULE(shade);
687 DISCARD_USED_SET_RULE(skiptaskbar);
688 DISCARD_USED_SET_RULE(skippager);
689 DISCARD_USED_SET_RULE(skipswitcher);
690 DISCARD_USED_SET_RULE(above);
691 DISCARD_USED_SET_RULE(below);
692 DISCARD_USED_SET_RULE(fullscreen);
693 DISCARD_USED_SET_RULE(noborder);
694 DISCARD_USED_FORCE_RULE(blockcompositing);
695 DISCARD_USED_FORCE_RULE(fsplevel);
696 DISCARD_USED_FORCE_RULE(acceptfocus);
697 DISCARD_USED_FORCE_RULE(closeable);
698 DISCARD_USED_FORCE_RULE(autogroup);
699 DISCARD_USED_FORCE_RULE(autogroupfg);
700 DISCARD_USED_FORCE_RULE(autogroupid);
701 DISCARD_USED_FORCE_RULE(strictgeometry);
702 DISCARD_USED_SET_RULE(shortcut);
703 DISCARD_USED_FORCE_RULE(disableglobalshortcuts);
704}
705#undef DISCARD_USED_SET_RULE
706#undef DISCARD_USED_FORCE_RULE
707
708#endif
709
710QDebug& operator<<(QDebug& stream, const Rules* r)
711{
712 return stream << "[" << r->description << ":" << r->wmclass << "]" ;
713}
714
715#ifndef KCMRULES
716void WindowRules::discardTemporary()
717{
718 QVector< Rules* >::Iterator it2 = rules.begin();
719 for (QVector< Rules* >::Iterator it = rules.begin();
720 it != rules.end();
721 ) {
722 if ((*it)->discardTemporary(true))
723 ++it;
724 else {
725 *it2++ = *it++;
726 }
727 }
728 rules.erase(it2, rules.end());
729}
730
731void WindowRules::update(Client* c, int selection)
732{
733 bool updated = false;
734 for (QVector< Rules* >::ConstIterator it = rules.constBegin();
735 it != rules.constEnd();
736 ++it)
737 if ((*it)->update(c, selection)) // no short-circuiting here
738 updated = true;
739 if (updated)
740 RuleBook::self()->requestDiskStorage();
741}
742
743#define CHECK_RULE( rule, type ) \
744 type WindowRules::check##rule( type arg, bool init ) const \
745 { \
746 if ( rules.count() == 0 ) \
747 return arg; \
748 type ret = arg; \
749 for ( QVector< Rules* >::ConstIterator it = rules.constBegin(); \
750 it != rules.constEnd(); \
751 ++it ) \
752 { \
753 if ( (*it)->apply##rule( ret, init )) \
754 break; \
755 } \
756 return ret; \
757 }
758
759#define CHECK_FORCE_RULE( rule, type ) \
760 type WindowRules::check##rule( type arg ) const \
761 { \
762 if ( rules.count() == 0 ) \
763 return arg; \
764 type ret = arg; \
765 for ( QVector< Rules* >::ConstIterator it = rules.begin(); \
766 it != rules.end(); \
767 ++it ) \
768 { \
769 if ( (*it)->apply##rule( ret )) \
770 break; \
771 } \
772 return ret; \
773 }
774
775CHECK_FORCE_RULE(Placement, Placement::Policy)
776
777QRect WindowRules::checkGeometry(QRect rect, bool init) const
778{
779 return QRect(checkPosition(rect.topLeft(), init), checkSize(rect.size(), init));
780}
781
782CHECK_RULE(Position, QPoint)
783CHECK_RULE(Size, QSize)
784CHECK_FORCE_RULE(MinSize, QSize)
785CHECK_FORCE_RULE(MaxSize, QSize)
786CHECK_FORCE_RULE(OpacityActive, int)
787CHECK_FORCE_RULE(OpacityInactive, int)
788CHECK_RULE(IgnoreGeometry, bool)
789
790CHECK_RULE(Desktop, int)
791CHECK_RULE(Activity, QString)
792CHECK_FORCE_RULE(Type, NET::WindowType)
793CHECK_RULE(MaximizeVert, KDecorationDefines::MaximizeMode)
794CHECK_RULE(MaximizeHoriz, KDecorationDefines::MaximizeMode)
795
796KDecorationDefines::MaximizeMode WindowRules::checkMaximize(MaximizeMode mode, bool init) const
797{
798 bool vert = checkMaximizeVert(mode, init) & MaximizeVertical;
799 bool horiz = checkMaximizeHoriz(mode, init) & MaximizeHorizontal;
800 return static_cast< MaximizeMode >((vert ? MaximizeVertical : 0) | (horiz ? MaximizeHorizontal : 0));
801}
802
803int WindowRules::checkScreen(int screen, bool init) const
804{
805 if ( rules.count() == 0 )
806 return screen;
807 int ret = screen;
808 for ( QVector< Rules* >::ConstIterator it = rules.constBegin(); it != rules.constEnd(); ++it ) {
809 if ( (*it)->applyScreen( ret, init ))
810 break;
811 }
812 if (ret >= Screens::self()->count())
813 ret = screen;
814 return ret;
815}
816
817CHECK_RULE(Minimize, bool)
818CHECK_RULE(Shade, ShadeMode)
819CHECK_RULE(SkipTaskbar, bool)
820CHECK_RULE(SkipPager, bool)
821CHECK_RULE(SkipSwitcher, bool)
822CHECK_RULE(KeepAbove, bool)
823CHECK_RULE(KeepBelow, bool)
824CHECK_RULE(FullScreen, bool)
825CHECK_RULE(NoBorder, bool)
826CHECK_FORCE_RULE(BlockCompositing, bool)
827CHECK_FORCE_RULE(FSP, int)
828CHECK_FORCE_RULE(AcceptFocus, bool)
829CHECK_FORCE_RULE(Closeable, bool)
830CHECK_FORCE_RULE(Autogrouping, bool)
831CHECK_FORCE_RULE(AutogroupInForeground, bool)
832CHECK_FORCE_RULE(AutogroupById, QString)
833CHECK_FORCE_RULE(StrictGeometry, bool)
834CHECK_RULE(Shortcut, QString)
835CHECK_FORCE_RULE(DisableGlobalShortcuts, bool)
836
837#undef CHECK_RULE
838#undef CHECK_FORCE_RULE
839
840// Client
841
842void Client::setupWindowRules(bool ignore_temporary)
843{
844 client_rules = RuleBook::self()->find(this, ignore_temporary);
845 // check only after getting the rules, because there may be a rule forcing window type
846}
847
848// Applies Force, ForceTemporarily and ApplyNow rules
849// Used e.g. after the rules have been modified using the kcm.
850void Client::applyWindowRules()
851{
852 // apply force rules
853 // Placement - does need explicit update, just like some others below
854 // Geometry : setGeometry() doesn't check rules
855 QRect orig_geom = QRect(pos(), sizeForClientSize(clientSize())); // handle shading
856 QRect geom = client_rules.checkGeometry(orig_geom);
857 if (geom != orig_geom)
858 setGeometry(geom);
859 // MinSize, MaxSize handled by Geometry
860 // IgnoreGeometry
861 setDesktop(desktop());
862 workspace()->sendClientToScreen(this, screen());
863 setOnActivities(activities());
864 // Type
865 maximize(maximizeMode());
866 // Minimize : functions don't check, and there are two functions
867 if (client_rules.checkMinimize(isMinimized()))
868 minimize();
869 else
870 unminimize();
871 setShade(shadeMode());
872 setSkipTaskbar(skipTaskbar(), true);
873 setSkipPager(skipPager());
874 setSkipSwitcher(skipSwitcher());
875 setKeepAbove(keepAbove());
876 setKeepBelow(keepBelow());
877 setFullScreen(isFullScreen(), true);
878 setNoBorder(noBorder());
879 // FSP
880 // AcceptFocus :
881 if (workspace()->mostRecentlyActivatedClient() == this
882 && !client_rules.checkAcceptFocus(true))
883 workspace()->activateNextClient(this);
884 // Closeable
885 QSize s = adjustedSize();
886 if (s != size())
887 resizeWithChecks(s);
888 // Autogrouping : Only checked on window manage
889 // AutogroupInForeground : Only checked on window manage
890 // AutogroupById : Only checked on window manage
891 // StrictGeometry
892 setShortcut(rules()->checkShortcut(shortcut().toString()));
893 // see also Client::setActive()
894 if (isActive()) {
895 setOpacity(rules()->checkOpacityActive(qRound(opacity() * 100.0)) / 100.0);
896 workspace()->disableGlobalShortcutsForClient(rules()->checkDisableGlobalShortcuts(false));
897 } else
898 setOpacity(rules()->checkOpacityInactive(qRound(opacity() * 100.0)) / 100.0);
899}
900
901void Client::updateWindowRules(Rules::Types selection)
902{
903 if (!isManaged()) // not fully setup yet
904 return;
905 if (RuleBook::self()->areUpdatesDisabled())
906 return;
907 client_rules.update(this, selection);
908}
909
910void Client::finishWindowRules()
911{
912 updateWindowRules(Rules::All);
913 client_rules = WindowRules();
914}
915
916// Workspace
917KWIN_SINGLETON_FACTORY(RuleBook)
918
919RuleBook::RuleBook(QObject *parent)
920 : QObject(parent)
921 , m_updateTimer(new QTimer(this))
922 , m_updatesDisabled(false)
923 , m_temporaryRulesMessages(new KXMessages("_KDE_NET_WM_TEMPORARY_RULES", NULL, false)) // TODO KF5 - remove *then* obsolete last parameter which is *now* mandatory
924{
925 connect(m_temporaryRulesMessages.data(), SIGNAL(gotMessage(QString)), SLOT(temporaryRulesMessage(QString)));
926 connect(m_updateTimer, SIGNAL(timeout()), SLOT(save()));
927 m_updateTimer->setInterval(1000);
928 m_updateTimer->setSingleShot(true);
929}
930
931RuleBook::~RuleBook()
932{
933 save();
934 deleteAll();
935}
936
937void RuleBook::deleteAll()
938{
939 qDeleteAll(m_rules);
940 m_rules.clear();
941}
942
943WindowRules RuleBook::find(const Client* c, bool ignore_temporary)
944{
945 QVector< Rules* > ret;
946 for (QList< Rules* >::Iterator it = m_rules.begin();
947 it != m_rules.end();
948 ) {
949 if (ignore_temporary && (*it)->isTemporary()) {
950 ++it;
951 continue;
952 }
953 if ((*it)->match(c)) {
954 Rules* rule = *it;
955 kDebug(1212) << "Rule found:" << rule << ":" << c;
956 if (rule->isTemporary())
957 it = m_rules.erase(it);
958 else
959 ++it;
960 ret.append(rule);
961 continue;
962 }
963 ++it;
964 }
965 return WindowRules(ret);
966}
967
968void RuleBook::edit(Client* c, bool whole_app)
969{
970 save();
971 QStringList args;
972 args << "--wid" << QString::number(c->window());
973 if (whole_app)
974 args << "--whole-app";
975 KToolInvocation::kdeinitExec("kwin_rules_dialog", args);
976}
977
978void RuleBook::load()
979{
980 deleteAll();
981 KConfig cfg(QLatin1String(KWIN_NAME) + "rulesrc", KConfig::NoGlobals);
982 int count = cfg.group("General").readEntry("count", 0);
983 for (int i = 1;
984 i <= count;
985 ++i) {
986 KConfigGroup cg(&cfg, QString::number(i));
987 Rules* rule = new Rules(cg);
988 m_rules.append(rule);
989 }
990}
991
992void RuleBook::save()
993{
994 m_updateTimer->stop();
995 KConfig cfg(QLatin1String(KWIN_NAME) + "rulesrc", KConfig::NoGlobals);
996 QStringList groups = cfg.groupList();
997 for (QStringList::ConstIterator it = groups.constBegin();
998 it != groups.constEnd();
999 ++it)
1000 cfg.deleteGroup(*it);
1001 cfg.group("General").writeEntry("count", m_rules.count());
1002 int i = 1;
1003 for (QList< Rules* >::ConstIterator it = m_rules.constBegin();
1004 it != m_rules.constEnd();
1005 ++it) {
1006 if ((*it)->isTemporary())
1007 continue;
1008 KConfigGroup cg(&cfg, QString::number(i));
1009 (*it)->write(cg);
1010 ++i;
1011 }
1012}
1013
1014void RuleBook::temporaryRulesMessage(const QString& message)
1015{
1016 bool was_temporary = false;
1017 for (QList< Rules* >::ConstIterator it = m_rules.constBegin();
1018 it != m_rules.constEnd();
1019 ++it)
1020 if ((*it)->isTemporary())
1021 was_temporary = true;
1022 Rules* rule = new Rules(message, true);
1023 m_rules.prepend(rule); // highest priority first
1024 if (!was_temporary)
1025 QTimer::singleShot(60000, this, SLOT(cleanupTemporaryRules()));
1026}
1027
1028void RuleBook::cleanupTemporaryRules()
1029{
1030 bool has_temporary = false;
1031 for (QList< Rules* >::Iterator it = m_rules.begin();
1032 it != m_rules.end();
1033 ) {
1034 if ((*it)->discardTemporary(false)) { // deletes (*it)
1035 it = m_rules.erase(it);
1036 } else {
1037 if ((*it)->isTemporary())
1038 has_temporary = true;
1039 ++it;
1040 }
1041 }
1042 if (has_temporary)
1043 QTimer::singleShot(60000, this, SLOT(cleanupTemporaryRules()));
1044}
1045
1046void RuleBook::discardUsed(Client* c, bool withdrawn)
1047{
1048 bool updated = false;
1049 for (QList< Rules* >::Iterator it = m_rules.begin();
1050 it != m_rules.end();
1051 ) {
1052 if (c->rules()->contains(*it)) {
1053 updated = true;
1054 (*it)->discardUsed(withdrawn);
1055 if ((*it)->isEmpty()) {
1056 c->removeRule(*it);
1057 Rules* r = *it;
1058 it = m_rules.erase(it);
1059 delete r;
1060 continue;
1061 }
1062 }
1063 ++it;
1064 }
1065 if (updated)
1066 requestDiskStorage();
1067}
1068
1069void RuleBook::requestDiskStorage()
1070{
1071 m_updateTimer->start();
1072}
1073
1074void RuleBook::setUpdatesDisabled(bool disable)
1075{
1076 m_updatesDisabled = disable;
1077 if (!disable) {
1078 foreach (Client * c, Workspace::self()->clientList())
1079 c->updateWindowRules(Rules::All);
1080 }
1081}
1082
1083#endif
1084
1085} // namespace
1086