1 | /******************************************************************** |
2 | KWin - the KDE window manager |
3 | This file is part of the KDE project. |
4 | |
5 | Copyright (C) 2004 Lubos Lunak <l.lunak@kde.org> |
6 | |
7 | This program is free software; you can redistribute it and/or modify |
8 | it under the terms of the GNU General Public License as published by |
9 | the Free Software Foundation; either version 2 of the License, or |
10 | (at your option) any later version. |
11 | |
12 | This program is distributed in the hope that it will be useful, |
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 | GNU General Public License for more details. |
16 | |
17 | You should have received a copy of the GNU General Public License |
18 | along 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 | |
38 | namespace KWin |
39 | { |
40 | |
41 | Rules::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 | |
85 | Rules::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 | |
123 | Rules::Rules(const KConfigGroup& cfg) |
124 | : temporary_state(0) |
125 | { |
126 | readFromCfg(cfg); |
127 | } |
128 | |
129 | static int limit0to4(int i) |
130 | { |
131 | return qMax(0, qMin(4, i)); |
132 | } |
133 | |
134 | void 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 | |
232 | void 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 |
285 | bool 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 | |
322 | Rules::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 | |
330 | Rules::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 | |
338 | NET::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 | |
346 | bool 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 | |
357 | bool 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 | |
373 | bool 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 | |
386 | bool 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 | |
399 | bool 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 |
420 | bool 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 | |
437 | bool 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 | |
550 | APPLY_FORCE_RULE(placement, Placement, Placement::Policy) |
551 | |
552 | bool 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 | |
568 | bool 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 | |
575 | bool 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 | |
582 | APPLY_FORCE_RULE(minsize, MinSize, QSize) |
583 | APPLY_FORCE_RULE(maxsize, MaxSize, QSize) |
584 | APPLY_FORCE_RULE(opacityactive, OpacityActive, int) |
585 | APPLY_FORCE_RULE(opacityinactive, OpacityInactive, int) |
586 | APPLY_RULE(ignoregeometry, IgnoreGeometry, bool) |
587 | |
588 | APPLY_RULE(desktop, Desktop, int) |
589 | APPLY_RULE(screen, Screen, int) |
590 | APPLY_RULE(activity, Activity, QString) |
591 | APPLY_FORCE_RULE(type, Type, NET::WindowType) |
592 | |
593 | bool 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 | |
600 | bool 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 | |
607 | APPLY_RULE(minimize, Minimize, bool) |
608 | |
609 | bool 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 | |
620 | APPLY_RULE(skiptaskbar, SkipTaskbar, bool) |
621 | APPLY_RULE(skippager, SkipPager, bool) |
622 | APPLY_RULE(skipswitcher, SkipSwitcher, bool) |
623 | APPLY_RULE(above, KeepAbove, bool) |
624 | APPLY_RULE(below, KeepBelow, bool) |
625 | APPLY_RULE(fullscreen, FullScreen, bool) |
626 | APPLY_RULE(noborder, NoBorder, bool) |
627 | APPLY_FORCE_RULE(blockcompositing, BlockCompositing, bool) |
628 | APPLY_FORCE_RULE(fsplevel, FSP, int) |
629 | APPLY_FORCE_RULE(acceptfocus, AcceptFocus, bool) |
630 | APPLY_FORCE_RULE(closeable, Closeable, bool) |
631 | APPLY_FORCE_RULE(autogroup, Autogrouping, bool) |
632 | APPLY_FORCE_RULE(autogroupfg, AutogroupInForeground, bool) |
633 | APPLY_FORCE_RULE(autogroupid, AutogroupById, QString) |
634 | APPLY_FORCE_RULE(strictgeometry, StrictGeometry, bool) |
635 | APPLY_RULE(shortcut, Shortcut, QString) |
636 | APPLY_FORCE_RULE(disableglobalshortcuts, DisableGlobalShortcuts, bool) |
637 | |
638 | |
639 | #undef APPLY_RULE |
640 | #undef APPLY_FORCE_RULE |
641 | |
642 | bool Rules::isTemporary() const |
643 | { |
644 | return temporary_state > 0; |
645 | } |
646 | |
647 | bool 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 | |
669 | void 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 | |
710 | QDebug& operator<<(QDebug& stream, const Rules* r) |
711 | { |
712 | return stream << "[" << r->description << ":" << r->wmclass << "]" ; |
713 | } |
714 | |
715 | #ifndef KCMRULES |
716 | void 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 | |
731 | void 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 | |
775 | CHECK_FORCE_RULE(Placement, Placement::Policy) |
776 | |
777 | QRect WindowRules::checkGeometry(QRect rect, bool init) const |
778 | { |
779 | return QRect(checkPosition(rect.topLeft(), init), checkSize(rect.size(), init)); |
780 | } |
781 | |
782 | CHECK_RULE(Position, QPoint) |
783 | CHECK_RULE(Size, QSize) |
784 | CHECK_FORCE_RULE(MinSize, QSize) |
785 | CHECK_FORCE_RULE(MaxSize, QSize) |
786 | CHECK_FORCE_RULE(OpacityActive, int) |
787 | CHECK_FORCE_RULE(OpacityInactive, int) |
788 | CHECK_RULE(IgnoreGeometry, bool) |
789 | |
790 | CHECK_RULE(Desktop, int) |
791 | CHECK_RULE(Activity, QString) |
792 | CHECK_FORCE_RULE(Type, NET::WindowType) |
793 | CHECK_RULE(MaximizeVert, KDecorationDefines::MaximizeMode) |
794 | CHECK_RULE(MaximizeHoriz, KDecorationDefines::MaximizeMode) |
795 | |
796 | KDecorationDefines::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 | |
803 | int 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 | |
817 | CHECK_RULE(Minimize, bool) |
818 | CHECK_RULE(Shade, ShadeMode) |
819 | CHECK_RULE(SkipTaskbar, bool) |
820 | CHECK_RULE(SkipPager, bool) |
821 | CHECK_RULE(SkipSwitcher, bool) |
822 | CHECK_RULE(KeepAbove, bool) |
823 | CHECK_RULE(KeepBelow, bool) |
824 | CHECK_RULE(FullScreen, bool) |
825 | CHECK_RULE(NoBorder, bool) |
826 | CHECK_FORCE_RULE(BlockCompositing, bool) |
827 | CHECK_FORCE_RULE(FSP, int) |
828 | CHECK_FORCE_RULE(AcceptFocus, bool) |
829 | CHECK_FORCE_RULE(Closeable, bool) |
830 | CHECK_FORCE_RULE(Autogrouping, bool) |
831 | CHECK_FORCE_RULE(AutogroupInForeground, bool) |
832 | CHECK_FORCE_RULE(AutogroupById, QString) |
833 | CHECK_FORCE_RULE(StrictGeometry, bool) |
834 | CHECK_RULE(Shortcut, QString) |
835 | CHECK_FORCE_RULE(DisableGlobalShortcuts, bool) |
836 | |
837 | #undef CHECK_RULE |
838 | #undef CHECK_FORCE_RULE |
839 | |
840 | // Client |
841 | |
842 | void 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. |
850 | void 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 | |
901 | void 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 | |
910 | void Client::finishWindowRules() |
911 | { |
912 | updateWindowRules(Rules::All); |
913 | client_rules = WindowRules(); |
914 | } |
915 | |
916 | // Workspace |
917 | KWIN_SINGLETON_FACTORY(RuleBook) |
918 | |
919 | RuleBook::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 | |
931 | RuleBook::~RuleBook() |
932 | { |
933 | save(); |
934 | deleteAll(); |
935 | } |
936 | |
937 | void RuleBook::deleteAll() |
938 | { |
939 | qDeleteAll(m_rules); |
940 | m_rules.clear(); |
941 | } |
942 | |
943 | WindowRules 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 | |
968 | void 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 | |
978 | void 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 | |
992 | void 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 | |
1014 | void 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 | |
1028 | void 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 | |
1046 | void 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 | |
1069 | void RuleBook::requestDiskStorage() |
1070 | { |
1071 | m_updateTimer->start(); |
1072 | } |
1073 | |
1074 | void 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 | |