1 | /******************************************************************** |
2 | KWin - the KDE window manager |
3 | This file is part of the KDE project. |
4 | |
5 | Copyright (C) 1999, 2000 Matthias Ettrich <ettrich@kde.org> |
6 | Copyright (C) 2003 Lubos Lunak <l.lunak@kde.org> |
7 | |
8 | This program is free software; you can redistribute it and/or modify |
9 | it under the terms of the GNU General Public License as published by |
10 | the Free Software Foundation; either version 2 of the License, or |
11 | (at your option) any later version. |
12 | |
13 | This program is distributed in the hope that it will be useful, |
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16 | GNU General Public License for more details. |
17 | |
18 | You should have received a copy of the GNU General Public License |
19 | along with this program. If not, see <http://www.gnu.org/licenses/>. |
20 | *********************************************************************/ |
21 | |
22 | // SELI zmenit doc |
23 | |
24 | /* |
25 | |
26 | This file contains things relevant to stacking order and layers. |
27 | |
28 | Design: |
29 | |
30 | Normal unconstrained stacking order, as requested by the user (by clicking |
31 | on windows to raise them, etc.), is in Workspace::unconstrained_stacking_order. |
32 | That list shouldn't be used at all, except for building |
33 | Workspace::stacking_order. The building is done |
34 | in Workspace::constrainedStackingOrder(). Only Workspace::stackingOrder() should |
35 | be used to get the stacking order, because it also checks the stacking order |
36 | is up to date. |
37 | All clients are also stored in Workspace::clients (except for isDesktop() clients, |
38 | as those are very special, and are stored in Workspace::desktops), in the order |
39 | the clients were created. |
40 | |
41 | Every window has one layer assigned in which it is. There are 6 layers, |
42 | from bottom : DesktopLayer, BelowLayer, NormalLayer, DockLayer, AboveLayer |
43 | and ActiveLayer (see also NETWM sect.7.10.). The layer a window is in depends |
44 | on the window type, and on other things like whether the window is active. |
45 | |
46 | NET::Splash clients belong to the Normal layer. NET::TopMenu clients |
47 | belong to Dock layer. Clients that are both NET::Dock and NET::KeepBelow |
48 | are in the Normal layer in order to keep the 'allow window to cover |
49 | the panel' Kicker setting to work as intended (this may look like a slight |
50 | spec violation, but a) I have no better idea, b) the spec allows adjusting |
51 | the stacking order if the WM thinks it's a good idea . We put all |
52 | NET::KeepAbove above all Docks too, even though the spec suggests putting |
53 | them in the same layer. |
54 | |
55 | Most transients are in the same layer as their mainwindow, |
56 | see Workspace::constrainedStackingOrder(), they may also be in higher layers, but |
57 | they should never be below their mainwindow. |
58 | |
59 | When some client attribute changes (above/below flag, transiency...), |
60 | Workspace::updateClientLayer() should be called in order to make |
61 | sure it's moved to the appropriate layer ClientList if needed. |
62 | |
63 | Currently the things that affect client in which layer a client |
64 | belongs: KeepAbove/Keep Below flags, window type, fullscreen |
65 | state and whether the client is active, mainclient (transiency). |
66 | |
67 | Make sure updateStackingOrder() is called in order to make |
68 | Workspace::stackingOrder() up to date and propagated to the world. |
69 | Using Workspace::blockStackingUpdates() (or the StackingUpdatesBlocker |
70 | helper class) it's possible to temporarily disable updates |
71 | and the stacking order will be updated once after it's allowed again. |
72 | |
73 | */ |
74 | |
75 | #include <assert.h> |
76 | |
77 | #include <kdebug.h> |
78 | |
79 | #include "utils.h" |
80 | #include "client.h" |
81 | #include "focuschain.h" |
82 | #include "netinfo.h" |
83 | #include "workspace.h" |
84 | #include "tabbox.h" |
85 | #include "group.h" |
86 | #include "rules.h" |
87 | #include "screens.h" |
88 | #include "unmanaged.h" |
89 | #include "deleted.h" |
90 | #include "effects.h" |
91 | #include <QX11Info> |
92 | #include "composite.h" |
93 | #ifdef KWIN_BUILD_SCREENEDGES |
94 | #include "screenedge.h" |
95 | #endif |
96 | |
97 | namespace KWin |
98 | { |
99 | |
100 | //******************************* |
101 | // Workspace |
102 | //******************************* |
103 | |
104 | void Workspace::updateClientLayer(Client* c) |
105 | { |
106 | if (c) |
107 | c->updateLayer(); |
108 | } |
109 | |
110 | void Workspace::updateStackingOrder(bool propagate_new_clients) |
111 | { |
112 | if (block_stacking_updates > 0) { |
113 | if (propagate_new_clients) |
114 | blocked_propagating_new_clients = true; |
115 | return; |
116 | } |
117 | ToplevelList new_stacking_order = constrainedStackingOrder(); |
118 | bool changed = (force_restacking || new_stacking_order != stacking_order); |
119 | force_restacking = false; |
120 | stacking_order = new_stacking_order; |
121 | #if 0 |
122 | kDebug(1212) << "stacking:" << changed; |
123 | if (changed || propagate_new_clients) { |
124 | for (ClientList::ConstIterator it = stacking_order.begin(); |
125 | it != stacking_order.end(); |
126 | ++it) |
127 | kDebug(1212) << (void*)(*it) << *it << ":" << (*it)->layer(); |
128 | } |
129 | #endif |
130 | if (changed || propagate_new_clients) { |
131 | propagateClients(propagate_new_clients); |
132 | emit stackingOrderChanged(); |
133 | if (m_compositor) { |
134 | m_compositor->addRepaintFull(); |
135 | } |
136 | |
137 | if (active_client) |
138 | active_client->updateMouseGrab(); |
139 | } |
140 | } |
141 | |
142 | #ifdef KWIN_BUILD_SCREENEDGES |
143 | /*! |
144 | * Some fullscreen effects have to raise the screenedge on top of an input window, thus all windows |
145 | * this function puts them back where they belong for regular use and is some cheap variant of |
146 | * the regular propagateClients function in that it completely ignores managed clients and everything |
147 | * else and also does not update the NETWM property. |
148 | * Called from Effects::destroyInputWindow so far. |
149 | */ |
150 | void Workspace::stackScreenEdgesUnderOverrideRedirect() |
151 | { |
152 | Xcb::restackWindows(QVector<xcb_window_t>() << rootInfo()->supportWindow() << ScreenEdges::self()->windows()); |
153 | } |
154 | #endif |
155 | |
156 | /*! |
157 | Propagates the managed clients to the world. |
158 | Called ONLY from updateStackingOrder(). |
159 | */ |
160 | void Workspace::propagateClients(bool propagate_new_clients) |
161 | { |
162 | // restack the windows according to the stacking order |
163 | // supportWindow > electric borders > clients > hidden clients |
164 | QVector<xcb_window_t> newWindowStack; |
165 | |
166 | // Stack all windows under the support window. The support window is |
167 | // not used for anything (besides the NETWM property), and it's not shown, |
168 | // but it was lowered after kwin startup. Stacking all clients below |
169 | // it ensures that no client will be ever shown above override-redirect |
170 | // windows (e.g. popups). |
171 | newWindowStack << rootInfo()->supportWindow(); |
172 | |
173 | #ifdef KWIN_BUILD_SCREENEDGES |
174 | newWindowStack << ScreenEdges::self()->windows(); |
175 | #endif |
176 | |
177 | newWindowStack.reserve(newWindowStack.size() + 2*stacking_order.size()); // *2 for inputWindow |
178 | |
179 | for (int i = stacking_order.size() - 1; i >= 0; --i) { |
180 | Client *client = qobject_cast<Client*>(stacking_order.at(i)); |
181 | if (!client || client->hiddenPreview()) { |
182 | continue; |
183 | } |
184 | |
185 | if (client->inputId()) |
186 | // Stack the input window above the frame |
187 | newWindowStack << client->inputId(); |
188 | |
189 | newWindowStack << client->frameId(); |
190 | } |
191 | |
192 | // when having hidden previews, stack hidden windows below everything else |
193 | // (as far as pure X stacking order is concerned), in order to avoid having |
194 | // these windows that should be unmapped to interfere with other windows |
195 | for (int i = stacking_order.size() - 1; i >= 0; --i) { |
196 | Client *client = qobject_cast<Client*>(stacking_order.at(i)); |
197 | if (!client || !client->hiddenPreview()) |
198 | continue; |
199 | newWindowStack << client->frameId(); |
200 | } |
201 | // TODO isn't it too inefficient to restack always all clients? |
202 | // TODO don't restack not visible windows? |
203 | assert(newWindowStack.at(0) == rootInfo()->supportWindow()); |
204 | Xcb::restackWindows(newWindowStack); |
205 | |
206 | int pos = 0; |
207 | Window *cl(NULL); |
208 | if (propagate_new_clients) { |
209 | cl = new Window[ desktops.count() + clients.count()]; |
210 | // TODO this is still not completely in the map order |
211 | for (ClientList::ConstIterator it = desktops.constBegin(); it != desktops.constEnd(); ++it) |
212 | cl[pos++] = (*it)->window(); |
213 | for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) |
214 | cl[pos++] = (*it)->window(); |
215 | rootInfo()->setClientList(cl, pos); |
216 | delete [] cl; |
217 | } |
218 | |
219 | cl = new Window[ stacking_order.count()]; |
220 | pos = 0; |
221 | for (ToplevelList::ConstIterator it = stacking_order.constBegin(); it != stacking_order.constEnd(); ++it) { |
222 | if ((*it)->isClient()) |
223 | cl[pos++] = (*it)->window(); |
224 | } |
225 | rootInfo()->setClientListStacking(cl, pos); |
226 | delete [] cl; |
227 | |
228 | // Make the cached stacking order invalid here, in case we need the new stacking order before we get |
229 | // the matching event, due to X being asynchronous. |
230 | x_stacking_dirty = true; |
231 | } |
232 | |
233 | /*! |
234 | Returns topmost visible client. Windows on the dock, the desktop |
235 | or of any other special kind are excluded. Also if the window |
236 | doesn't accept focus it's excluded. |
237 | */ |
238 | // TODO misleading name for this method, too many slightly different ways to use it |
239 | Client* Workspace::topClientOnDesktop(int desktop, int screen, bool unconstrained, bool only_normal) const |
240 | { |
241 | // TODO Q_ASSERT( block_stacking_updates == 0 ); |
242 | ToplevelList list; |
243 | if (!unconstrained) |
244 | list = stacking_order; |
245 | else |
246 | list = unconstrained_stacking_order; |
247 | for (int i = list.size() - 1; |
248 | i >= 0; |
249 | --i) { |
250 | Client *c = qobject_cast<Client*>(list.at(i)); |
251 | if (!c) { |
252 | continue; |
253 | } |
254 | if (c->isOnDesktop(desktop) && c->isShown(false) && c->isOnCurrentActivity()) { |
255 | if (screen != -1 && c->screen() != screen) |
256 | continue; |
257 | if (!only_normal) |
258 | return c; |
259 | if (c->wantsTabFocus() && !c->isSpecialWindow()) |
260 | return c; |
261 | } |
262 | } |
263 | return 0; |
264 | } |
265 | |
266 | Client* Workspace::findDesktop(bool topmost, int desktop) const |
267 | { |
268 | // TODO Q_ASSERT( block_stacking_updates == 0 ); |
269 | if (topmost) { |
270 | for (int i = stacking_order.size() - 1; i >= 0; i--) { |
271 | Client *c = qobject_cast<Client*>(stacking_order.at(i)); |
272 | if (c && c->isOnDesktop(desktop) && c->isDesktop() |
273 | && c->isShown(true)) |
274 | return c; |
275 | } |
276 | } else { // bottom-most |
277 | foreach (Toplevel * c, stacking_order) { |
278 | Client *client = qobject_cast<Client*>(c); |
279 | if (client && c->isOnDesktop(desktop) && c->isDesktop() |
280 | && client->isShown(true)) |
281 | return client; |
282 | } |
283 | } |
284 | return NULL; |
285 | } |
286 | |
287 | void Workspace::raiseOrLowerClient(Client *c) |
288 | { |
289 | if (!c) return; |
290 | Client* topmost = NULL; |
291 | // TODO Q_ASSERT( block_stacking_updates == 0 ); |
292 | if (most_recently_raised && stacking_order.contains(most_recently_raised) && |
293 | most_recently_raised->isShown(true) && c->isOnCurrentDesktop()) |
294 | topmost = most_recently_raised; |
295 | else |
296 | topmost = topClientOnDesktop(c->isOnAllDesktops() ? VirtualDesktopManager::self()->current() : c->desktop(), |
297 | options->isSeparateScreenFocus() ? c->screen() : -1); |
298 | |
299 | if (c == topmost) |
300 | lowerClient(c); |
301 | else |
302 | raiseClient(c); |
303 | } |
304 | |
305 | |
306 | void Workspace::lowerClient(Client* c, bool nogroup) |
307 | { |
308 | if (!c) |
309 | return; |
310 | |
311 | c->cancelAutoRaise(); |
312 | |
313 | StackingUpdatesBlocker blocker(this); |
314 | |
315 | unconstrained_stacking_order.removeAll(c); |
316 | unconstrained_stacking_order.prepend(c); |
317 | if (!nogroup && c->isTransient()) { |
318 | // lower also all windows in the group, in their reversed stacking order |
319 | ClientList wins = ensureStackingOrder(c->group()->members()); |
320 | for (int i = wins.size() - 1; |
321 | i >= 0; |
322 | --i) { |
323 | if (wins[ i ] != c) |
324 | lowerClient(wins[ i ], true); |
325 | } |
326 | } |
327 | |
328 | if (c == most_recently_raised) |
329 | most_recently_raised = 0; |
330 | } |
331 | |
332 | void Workspace::lowerClientWithinApplication(Client* c) |
333 | { |
334 | if (!c) |
335 | return; |
336 | |
337 | c->cancelAutoRaise(); |
338 | |
339 | StackingUpdatesBlocker blocker(this); |
340 | |
341 | unconstrained_stacking_order.removeAll(c); |
342 | bool lowered = false; |
343 | // first try to put it below the bottom-most window of the application |
344 | for (ToplevelList::Iterator it = unconstrained_stacking_order.begin(); |
345 | it != unconstrained_stacking_order.end(); |
346 | ++it) { |
347 | Client *client = qobject_cast<Client*>(*it); |
348 | if (!client) { |
349 | continue; |
350 | } |
351 | if (Client::belongToSameApplication(client, c)) { |
352 | unconstrained_stacking_order.insert(it, c); |
353 | lowered = true; |
354 | break; |
355 | } |
356 | } |
357 | if (!lowered) |
358 | unconstrained_stacking_order.prepend(c); |
359 | // ignore mainwindows |
360 | } |
361 | |
362 | void Workspace::raiseClient(Client* c, bool nogroup) |
363 | { |
364 | if (!c) |
365 | return; |
366 | |
367 | c->cancelAutoRaise(); |
368 | |
369 | StackingUpdatesBlocker blocker(this); |
370 | |
371 | if (!nogroup && c->isTransient()) { |
372 | ClientList transients; |
373 | Client *transient_parent = c; |
374 | while ((transient_parent = transient_parent->transientFor())) |
375 | transients << transient_parent; |
376 | foreach (transient_parent, transients) |
377 | raiseClient(transient_parent, true); |
378 | } |
379 | |
380 | unconstrained_stacking_order.removeAll(c); |
381 | unconstrained_stacking_order.append(c); |
382 | |
383 | if (!c->isSpecialWindow()) { |
384 | most_recently_raised = c; |
385 | pending_take_activity = NULL; |
386 | } |
387 | } |
388 | |
389 | void Workspace::raiseClientWithinApplication(Client* c) |
390 | { |
391 | if (!c) |
392 | return; |
393 | |
394 | c->cancelAutoRaise(); |
395 | |
396 | StackingUpdatesBlocker blocker(this); |
397 | // ignore mainwindows |
398 | |
399 | // first try to put it above the top-most window of the application |
400 | for (int i = unconstrained_stacking_order.size() - 1; i > -1 ; --i) { |
401 | Client *other = qobject_cast<Client*>(unconstrained_stacking_order.at(i)); |
402 | if (!other) { |
403 | continue; |
404 | } |
405 | if (other == c) // don't lower it just because it asked to be raised |
406 | return; |
407 | if (Client::belongToSameApplication(other, c)) { |
408 | unconstrained_stacking_order.removeAll(c); |
409 | unconstrained_stacking_order.insert(unconstrained_stacking_order.indexOf(other) + 1, c); // insert after the found one |
410 | break; |
411 | } |
412 | } |
413 | } |
414 | |
415 | void Workspace::raiseClientRequest(KWin::Client *c, NET::RequestSource src, xcb_timestamp_t timestamp) |
416 | { |
417 | if (src == NET::FromTool || allowFullClientRaising(c, timestamp)) |
418 | raiseClient(c); |
419 | else { |
420 | raiseClientWithinApplication(c); |
421 | c->demandAttention(); |
422 | } |
423 | } |
424 | |
425 | void Workspace::lowerClientRequest(KWin::Client *c, NET::RequestSource src, xcb_timestamp_t /*timestamp*/) |
426 | { |
427 | // If the client has support for all this focus stealing prevention stuff, |
428 | // do only lowering within the application, as that's the more logical |
429 | // variant of lowering when application requests it. |
430 | // No demanding of attention here of course. |
431 | if (src == NET::FromTool || !c->hasUserTimeSupport()) |
432 | lowerClient(c); |
433 | else |
434 | lowerClientWithinApplication(c); |
435 | } |
436 | |
437 | |
438 | void Workspace::restack(Client* c, Client* under) |
439 | { |
440 | assert(unconstrained_stacking_order.contains(under)); |
441 | if (!Client::belongToSameApplication(under, c)) { |
442 | // put in the stacking order below _all_ windows belonging to the active application |
443 | for (int i = 0; i < unconstrained_stacking_order.size(); ++i) { |
444 | Client *other = qobject_cast<Client*>(unconstrained_stacking_order.at(i)); |
445 | if (other && other->layer() == c->layer() && Client::belongToSameApplication(under, other)) { |
446 | under = (c == other) ? 0 : other; |
447 | break; |
448 | } |
449 | } |
450 | } |
451 | if (under) { |
452 | unconstrained_stacking_order.removeAll(c); |
453 | unconstrained_stacking_order.insert(unconstrained_stacking_order.indexOf(under), c); |
454 | } |
455 | |
456 | assert(unconstrained_stacking_order.contains(c)); |
457 | FocusChain::self()->moveAfterClient(c, under); |
458 | updateStackingOrder(); |
459 | } |
460 | |
461 | void Workspace::restackClientUnderActive(Client* c) |
462 | { |
463 | if (!active_client || active_client == c || active_client->layer() != c->layer()) { |
464 | raiseClient(c); |
465 | return; |
466 | } |
467 | restack(c, active_client); |
468 | } |
469 | |
470 | void Workspace::restoreSessionStackingOrder(Client* c) |
471 | { |
472 | if (c->sessionStackingOrder() < 0) |
473 | return; |
474 | StackingUpdatesBlocker blocker(this); |
475 | unconstrained_stacking_order.removeAll(c); |
476 | for (ToplevelList::Iterator it = unconstrained_stacking_order.begin(); // from bottom |
477 | it != unconstrained_stacking_order.end(); |
478 | ++it) { |
479 | Client *current = qobject_cast<Client*>(*it); |
480 | if (!current) { |
481 | continue; |
482 | } |
483 | if (current->sessionStackingOrder() > c->sessionStackingOrder()) { |
484 | unconstrained_stacking_order.insert(it, c); |
485 | return; |
486 | } |
487 | } |
488 | unconstrained_stacking_order.append(c); |
489 | } |
490 | |
491 | /*! |
492 | Returns a stacking order based upon \a list that fulfills certain contained. |
493 | */ |
494 | ToplevelList Workspace::constrainedStackingOrder() |
495 | { |
496 | ToplevelList layer[ NumLayers ]; |
497 | |
498 | #if 0 |
499 | kDebug(1212) << "stacking1:" ; |
500 | for (ClientList::ConstIterator it = unconstrained_stacking_order.begin(); |
501 | it != unconstrained_stacking_order.end(); |
502 | ++it) |
503 | kDebug(1212) << (void*)(*it) << *it << ":" << (*it)->layer(); |
504 | #endif |
505 | // build the order from layers |
506 | QVector< QMap<Group*, Layer> > minimum_layer(screens()->count()); |
507 | for (ToplevelList::ConstIterator it = unconstrained_stacking_order.constBegin(), |
508 | end = unconstrained_stacking_order.constEnd(); it != end; ++it) { |
509 | Layer l = (*it)->layer(); |
510 | |
511 | const int screen = (*it)->screen(); |
512 | Client *c = qobject_cast<Client*>(*it); |
513 | QMap< Group*, Layer >::iterator mLayer = minimum_layer[screen].find(c ? c->group() : NULL); |
514 | if (mLayer != minimum_layer[screen].end()) { |
515 | // If a window is raised above some other window in the same window group |
516 | // which is in the ActiveLayer (i.e. it's fulscreened), make sure it stays |
517 | // above that window (see #95731). |
518 | if (*mLayer == ActiveLayer && (l == NormalLayer || l == AboveLayer)) |
519 | l = ActiveLayer; |
520 | *mLayer = l; |
521 | } else if (c) { |
522 | minimum_layer[screen].insertMulti(c->group(), l); |
523 | } |
524 | layer[ l ].append(*it); |
525 | } |
526 | ToplevelList stacking; |
527 | for (Layer lay = FirstLayer; |
528 | lay < NumLayers; |
529 | ++lay) |
530 | stacking += layer[ lay ]; |
531 | #if 0 |
532 | kDebug(1212) << "stacking2:" ; |
533 | for (ClientList::ConstIterator it = stacking.begin(); |
534 | it != stacking.end(); |
535 | ++it) |
536 | kDebug(1212) << (void*)(*it) << *it << ":" << (*it)->layer(); |
537 | #endif |
538 | // now keep transients above their mainwindows |
539 | // TODO this could(?) use some optimization |
540 | for (int i = stacking.size() - 1; |
541 | i >= 0; |
542 | ) { |
543 | Client *current = qobject_cast<Client*>(stacking[i]); |
544 | if (!current || !current->isTransient()) { |
545 | --i; |
546 | continue; |
547 | } |
548 | int i2 = -1; |
549 | if (current->groupTransient()) { |
550 | if (current->group()->members().count() > 0) { |
551 | // find topmost client this one is transient for |
552 | for (i2 = stacking.size() - 1; |
553 | i2 >= 0; |
554 | --i2) { |
555 | if (stacking[ i2 ] == stacking[ i ]) { |
556 | i2 = -1; // don't reorder, already the topmost in the group |
557 | break; |
558 | } |
559 | Client *c2 = qobject_cast<Client*>(stacking[ i2 ]); |
560 | if (!c2) { |
561 | continue; |
562 | } |
563 | if (c2->hasTransient(current, true) |
564 | && keepTransientAbove(c2, current)) |
565 | break; |
566 | } |
567 | } // else i2 remains pointing at -1 |
568 | } else { |
569 | for (i2 = stacking.size() - 1; |
570 | i2 >= 0; |
571 | --i2) { |
572 | Client *c2 = qobject_cast<Client*>(stacking[ i2 ]); |
573 | if (!c2) { |
574 | continue; |
575 | } |
576 | if (c2 == current) { |
577 | i2 = -1; // don't reorder, already on top of its mainwindow |
578 | break; |
579 | } |
580 | if (c2 == current->transientFor() |
581 | && keepTransientAbove(c2, current)) |
582 | break; |
583 | } |
584 | } |
585 | if (i2 == -1) { |
586 | --i; |
587 | continue; |
588 | } |
589 | stacking.removeAt(i); |
590 | --i; // move onto the next item (for next for () iteration) |
591 | --i2; // adjust index of the mainwindow after the remove above |
592 | if (!current->transients().isEmpty()) // this one now can be possibly above its transients, |
593 | i = i2; // so go again higher in the stack order and possibly move those transients again |
594 | ++i2; // insert after (on top of) the mainwindow, it's ok if it2 is now stacking.end() |
595 | stacking.insert(i2, current); |
596 | } |
597 | #if 0 |
598 | kDebug(1212) << "stacking3:" ; |
599 | for (ClientList::ConstIterator it = stacking.begin(); |
600 | it != stacking.end(); |
601 | ++it) |
602 | kDebug(1212) << (void*)(*it) << *it << ":" << (*it)->layer(); |
603 | kDebug(1212) << "\n\n" ; |
604 | #endif |
605 | return stacking; |
606 | } |
607 | |
608 | void Workspace::blockStackingUpdates(bool block) |
609 | { |
610 | if (block) { |
611 | if (block_stacking_updates == 0) |
612 | blocked_propagating_new_clients = false; |
613 | ++block_stacking_updates; |
614 | } else // !block |
615 | if (--block_stacking_updates == 0) { |
616 | updateStackingOrder(blocked_propagating_new_clients); |
617 | if (effects) |
618 | static_cast<EffectsHandlerImpl*>(effects)->checkInputWindowStacking(); |
619 | } |
620 | } |
621 | |
622 | // Ensure list is in stacking order |
623 | ClientList Workspace::ensureStackingOrder(const ClientList& list) const |
624 | { |
625 | // TODO Q_ASSERT( block_stacking_updates == 0 ); |
626 | if (list.count() < 2) |
627 | return list; |
628 | // TODO is this worth optimizing? |
629 | ClientList result = list; |
630 | for (ToplevelList::ConstIterator it = stacking_order.constBegin(); |
631 | it != stacking_order.constEnd(); |
632 | ++it) { |
633 | Client *c = qobject_cast<Client*>(*it); |
634 | if (!c) { |
635 | continue; |
636 | } |
637 | if (result.removeAll(c) != 0) |
638 | result.append(c); |
639 | } |
640 | return result; |
641 | } |
642 | |
643 | // check whether a transient should be actually kept above its mainwindow |
644 | // there may be some special cases where this rule shouldn't be enfored |
645 | bool Workspace::keepTransientAbove(const Client* mainwindow, const Client* transient) |
646 | { |
647 | // #93832 - don't keep splashscreens above dialogs |
648 | if (transient->isSplash() && mainwindow->isDialog()) |
649 | return false; |
650 | // This is rather a hack for #76026. Don't keep non-modal dialogs above |
651 | // the mainwindow, but only if they're group transient (since only such dialogs |
652 | // have taskbar entry in Kicker). A proper way of doing this (both kwin and kicker) |
653 | // needs to be found. |
654 | if (transient->isDialog() && !transient->isModal() && transient->groupTransient()) |
655 | return false; |
656 | // #63223 - don't keep transients above docks, because the dock is kept high, |
657 | // and e.g. dialogs for them would be too high too |
658 | if (mainwindow->isDock()) |
659 | return false; |
660 | return true; |
661 | } |
662 | |
663 | // Returns all windows in their stacking order on the root window. |
664 | ToplevelList Workspace::xStackingOrder() const |
665 | { |
666 | if (!x_stacking_dirty) |
667 | return x_stacking; |
668 | x_stacking_dirty = false; |
669 | x_stacking.clear(); |
670 | Window dummy; |
671 | Window* windows = NULL; |
672 | unsigned int count = 0; |
673 | XQueryTree(display(), rootWindow(), &dummy, &dummy, &windows, &count); |
674 | // use our own stacking order, not the X one, as they may differ |
675 | foreach (Toplevel * c, stacking_order) |
676 | x_stacking.append(c); |
677 | for (unsigned int i = 0; |
678 | i < count; |
679 | ++i) { |
680 | if (Unmanaged* c = findUnmanaged(WindowMatchPredicate(windows[ i ]))) |
681 | x_stacking.append(c); |
682 | } |
683 | if (windows != NULL) |
684 | XFree(windows); |
685 | if (m_compositor) { |
686 | const_cast< Workspace* >(this)->m_compositor->checkUnredirect(); |
687 | } |
688 | return x_stacking; |
689 | } |
690 | |
691 | //******************************* |
692 | // Client |
693 | //******************************* |
694 | |
695 | void Client::restackWindow(xcb_window_t above, int detail, NET::RequestSource src, xcb_timestamp_t timestamp, bool send_event) |
696 | { |
697 | Client *other = 0; |
698 | if (detail == XCB_STACK_MODE_OPPOSITE) { |
699 | other = workspace()->findClient(WindowMatchPredicate(above)); |
700 | if (!other) { |
701 | workspace()->raiseOrLowerClient(this); |
702 | return; |
703 | } |
704 | ToplevelList::const_iterator it = workspace()->stackingOrder().constBegin(), |
705 | end = workspace()->stackingOrder().constEnd(); |
706 | while (it != end) { |
707 | if (*it == this) { |
708 | detail = XCB_STACK_MODE_ABOVE; |
709 | break; |
710 | } else if (*it == other) { |
711 | detail = XCB_STACK_MODE_BELOW; |
712 | break; |
713 | } |
714 | ++it; |
715 | } |
716 | } |
717 | else if (detail == XCB_STACK_MODE_TOP_IF) { |
718 | other = workspace()->findClient(WindowMatchPredicate(above)); |
719 | if (other && other->geometry().intersects(geometry())) |
720 | workspace()->raiseClientRequest(this, src, timestamp); |
721 | return; |
722 | } |
723 | else if (detail == XCB_STACK_MODE_BOTTOM_IF) { |
724 | other = workspace()->findClient(WindowMatchPredicate(above)); |
725 | if (other && other->geometry().intersects(geometry())) |
726 | workspace()->lowerClientRequest(this, src, timestamp); |
727 | return; |
728 | } |
729 | |
730 | if (!other) |
731 | other = workspace()->findClient(WindowMatchPredicate(above)); |
732 | |
733 | if (other && detail == XCB_STACK_MODE_ABOVE) { |
734 | ToplevelList::const_iterator it = workspace()->stackingOrder().constEnd(), |
735 | begin = workspace()->stackingOrder().constBegin(); |
736 | while (--it != begin) { |
737 | |
738 | if (*it == other) { // the other one is top on stack |
739 | it = begin; // invalidate |
740 | src = NET::FromTool; // force |
741 | break; |
742 | } |
743 | Client *c = qobject_cast<Client*>(*it); |
744 | |
745 | if (!c || !( (*it)->isNormalWindow() && c->isShown(true) && |
746 | (*it)->isOnCurrentDesktop() && (*it)->isOnCurrentActivity() && (*it)->isOnScreen(screen()) )) |
747 | continue; // irrelevant clients |
748 | |
749 | if (*(it - 1) == other) |
750 | break; // "it" is the one above the target one, stack below "it" |
751 | } |
752 | |
753 | if (it != begin && (*(it - 1) == other)) |
754 | other = qobject_cast<Client*>(*it); |
755 | else |
756 | other = 0; |
757 | } |
758 | |
759 | if (other) |
760 | workspace()->restack(this, other); |
761 | else if (detail == XCB_STACK_MODE_BELOW) |
762 | workspace()->lowerClientRequest(this, src, timestamp); |
763 | else if (detail == XCB_STACK_MODE_ABOVE) |
764 | workspace()->raiseClientRequest(this, src, timestamp); |
765 | |
766 | if (send_event) |
767 | sendSyntheticConfigureNotify(); |
768 | } |
769 | |
770 | void Client::setKeepAbove(bool b) |
771 | { |
772 | b = rules()->checkKeepAbove(b); |
773 | if (b && !rules()->checkKeepBelow(false)) |
774 | setKeepBelow(false); |
775 | if (b == keepAbove()) { |
776 | // force hint change if different |
777 | if (bool(info->state() & NET::KeepAbove) != keepAbove()) |
778 | info->setState(keepAbove() ? NET::KeepAbove : 0, NET::KeepAbove); |
779 | return; |
780 | } |
781 | keep_above = b; |
782 | info->setState(keepAbove() ? NET::KeepAbove : 0, NET::KeepAbove); |
783 | workspace()->updateClientLayer(this); |
784 | updateWindowRules(Rules::Above); |
785 | |
786 | // Update states of all other windows in this group |
787 | if (tabGroup()) |
788 | tabGroup()->updateStates(this, TabGroup::Layer); |
789 | emit keepAboveChanged(keep_above); |
790 | } |
791 | |
792 | void Client::setKeepBelow(bool b) |
793 | { |
794 | b = rules()->checkKeepBelow(b); |
795 | if (b && !rules()->checkKeepAbove(false)) |
796 | setKeepAbove(false); |
797 | if (b == keepBelow()) { |
798 | // force hint change if different |
799 | if (bool(info->state() & NET::KeepBelow) != keepBelow()) |
800 | info->setState(keepBelow() ? NET::KeepBelow : 0, NET::KeepBelow); |
801 | return; |
802 | } |
803 | keep_below = b; |
804 | info->setState(keepBelow() ? NET::KeepBelow : 0, NET::KeepBelow); |
805 | workspace()->updateClientLayer(this); |
806 | updateWindowRules(Rules::Below); |
807 | |
808 | // Update states of all other windows in this group |
809 | if (tabGroup()) |
810 | tabGroup()->updateStates(this, TabGroup::Layer); |
811 | emit keepBelowChanged(keep_below); |
812 | } |
813 | |
814 | Layer Client::layer() const |
815 | { |
816 | if (in_layer == UnknownLayer) |
817 | const_cast< Client* >(this)->in_layer = belongsToLayer(); |
818 | return in_layer; |
819 | } |
820 | |
821 | Layer Client::belongsToLayer() const |
822 | { |
823 | if (isDesktop()) |
824 | return DesktopLayer; |
825 | if (isSplash()) // no damn annoying splashscreens |
826 | return NormalLayer; // getting in the way of everything else |
827 | if (isDock()) { |
828 | // slight hack for the 'allow window to cover panel' Kicker setting |
829 | // don't move keepbelow docks below normal window, but only to the same |
830 | // layer, so that both may be raised to cover the other |
831 | if (keepBelow()) |
832 | return NormalLayer; |
833 | if (keepAbove()) // slight hack for the autohiding panels |
834 | return AboveLayer; |
835 | return DockLayer; |
836 | } |
837 | if (keepBelow()) |
838 | return BelowLayer; |
839 | if (isActiveFullScreen()) |
840 | return ActiveLayer; |
841 | if (keepAbove()) |
842 | return AboveLayer; |
843 | return NormalLayer; |
844 | } |
845 | |
846 | void Client::updateLayer() |
847 | { |
848 | if (layer() == belongsToLayer()) |
849 | return; |
850 | StackingUpdatesBlocker blocker(workspace()); |
851 | invalidateLayer(); // invalidate, will be updated when doing restacking |
852 | for (ClientList::ConstIterator it = transients().constBegin(), |
853 | end = transients().constEnd(); it != end; ++it) |
854 | (*it)->updateLayer(); |
855 | } |
856 | |
857 | bool rec_checkTransientOnTop(const ClientList &transients, const Client *topmost) |
858 | { |
859 | foreach (const Client *transient, transients) { |
860 | if (transient == topmost || rec_checkTransientOnTop(transient->transients(), topmost)) { |
861 | return true; |
862 | } |
863 | } |
864 | return false; |
865 | } |
866 | |
867 | bool Client::isActiveFullScreen() const |
868 | { |
869 | if (!isFullScreen()) |
870 | return false; |
871 | |
872 | const Client* ac = workspace()->mostRecentlyActivatedClient(); // instead of activeClient() - avoids flicker |
873 | // according to NETWM spec implementation notes suggests |
874 | // "focused windows having state _NET_WM_STATE_FULLSCREEN" to be on the highest layer. |
875 | // we'll also take the screen into account |
876 | return ac && (ac == this || this->group() == ac->group() || ac->screen() != screen()); |
877 | } |
878 | |
879 | } // namespace |
880 | |