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 | /* |
23 | |
24 | This file contains things relevant to window activation and focus |
25 | stealing prevention. |
26 | |
27 | */ |
28 | |
29 | #include "client.h" |
30 | #include "cursor.h" |
31 | #include "focuschain.h" |
32 | #include "netinfo.h" |
33 | #include "workspace.h" |
34 | #ifdef KWIN_BUILD_ACTIVITIES |
35 | #include "activities.h" |
36 | #endif |
37 | |
38 | #include <fixx11h.h> |
39 | #include <kxerrorhandler.h> |
40 | #include <kstartupinfo.h> |
41 | #include <kstringhandler.h> |
42 | #include <KDE/KLocalizedString> |
43 | |
44 | #include "atoms.h" |
45 | #include "group.h" |
46 | #include "rules.h" |
47 | #include "screens.h" |
48 | #include "useractions.h" |
49 | #include <QX11Info> |
50 | |
51 | namespace KWin |
52 | { |
53 | |
54 | /* |
55 | Prevention of focus stealing: |
56 | |
57 | KWin tries to prevent unwanted changes of focus, that would result |
58 | from mapping a new window. Also, some nasty applications may try |
59 | to force focus change even in cases when ICCCM 4.2.7 doesn't allow it |
60 | (e.g. they may try to activate their main window because the user |
61 | definitely "needs" to see something happened - misusing |
62 | of QWidget::setActiveWindow() may be such case). |
63 | |
64 | There are 4 ways how a window may become active: |
65 | - the user changes the active window (e.g. focus follows mouse, clicking |
66 | on some window's titlebar) - the change of focus will |
67 | be done by KWin, so there's nothing to solve in this case |
68 | - the change of active window will be requested using the _NET_ACTIVE_WINDOW |
69 | message (handled in RootInfo::changeActiveWindow()) - such requests |
70 | will be obeyed, because this request is meant mainly for e.g. taskbar |
71 | asking the WM to change the active window as a result of some user action. |
72 | Normal applications should use this request only rarely in special cases. |
73 | See also below the discussion of _NET_ACTIVE_WINDOW_TRANSFER. |
74 | - the change of active window will be done by performing XSetInputFocus() |
75 | on a window that's not currently active. ICCCM 4.2.7 describes when |
76 | the application may perform change of input focus. In order to handle |
77 | misbehaving applications, KWin will try to detect focus changes to |
78 | windows that don't belong to currently active application, and restore |
79 | focus back to the currently active window, instead of activating the window |
80 | that got focus (unfortunately there's no way to FocusChangeRedirect similar |
81 | to e.g. SubstructureRedirect, so there will be short time when the focus |
82 | will be changed). The check itself that's done is |
83 | Workspace::allowClientActivation() (see below). |
84 | - a new window will be mapped - this is the most complicated case. If |
85 | the new window belongs to the currently active application, it may be safely |
86 | mapped on top and activated. The same if there's no active window, |
87 | or the active window is the desktop. These checks are done by |
88 | Workspace::allowClientActivation(). |
89 | Following checks need to compare times. One time is the timestamp |
90 | of last user action in the currently active window, the other time is |
91 | the timestamp of the action that originally caused mapping of the new window |
92 | (e.g. when the application was started). If the first time is newer than |
93 | the second one, the window will not be activated, as that indicates |
94 | futher user actions took place after the action leading to this new |
95 | mapped window. This check is done by Workspace::allowClientActivation(). |
96 | There are several ways how to get the timestamp of action that caused |
97 | the new mapped window (done in Client::readUserTimeMapTimestamp()) : |
98 | - the window may have the _NET_WM_USER_TIME property. This way |
99 | the application may either explicitly request that the window is not |
100 | activated (by using 0 timestamp), or the property contains the time |
101 | of last user action in the application. |
102 | - KWin itself tries to detect time of last user action in every window, |
103 | by watching KeyPress and ButtonPress events on windows. This way some |
104 | events may be missed (if they don't propagate to the toplevel window), |
105 | but it's good as a fallback for applications that don't provide |
106 | _NET_WM_USER_TIME, and missing some events may at most lead |
107 | to unwanted focus stealing. |
108 | - the timestamp may come from application startup notification. |
109 | Application startup notification, if it exists for the new mapped window, |
110 | should include time of the user action that caused it. |
111 | - if there's no timestamp available, it's checked whether the new window |
112 | belongs to some already running application - if yes, the timestamp |
113 | will be 0 (i.e. refuse activation) |
114 | - if the window is from session restored window, the timestamp will |
115 | be 0 too, unless this application was the active one at the time |
116 | when the session was saved, in which case the window will be |
117 | activated if there wasn't any user interaction since the time |
118 | KWin was started. |
119 | - as the last resort, the _KDE_NET_USER_CREATION_TIME timestamp |
120 | is used. For every toplevel window that is created (see CreateNotify |
121 | handling), this property is set to the at that time current time. |
122 | Since at this time it's known that the new window doesn't belong |
123 | to any existing application (better said, the application doesn't |
124 | have any other window mapped), it is either the very first window |
125 | of the application, or it is the only window of the application |
126 | that was hidden before. The latter case is handled by removing |
127 | the property from windows before withdrawing them, making |
128 | the timestamp empty for next mapping of the window. In the sooner |
129 | case, the timestamp will be used. This helps in case when |
130 | an application is launched without application startup notification, |
131 | it creates its mainwindow, and starts its initialization (that |
132 | may possibly take long time). The timestamp used will be older |
133 | than any user action done after launching this application. |
134 | - if no timestamp is found at all, the window is activated. |
135 | The check whether two windows belong to the same application (same |
136 | process) is done in Client::belongToSameApplication(). Not 100% reliable, |
137 | but hopefully 99,99% reliable. |
138 | |
139 | As a somewhat special case, window activation is always enabled when |
140 | session saving is in progress. When session saving, the session |
141 | manager allows only one application to interact with the user. |
142 | Not allowing window activation in such case would result in e.g. dialogs |
143 | not becoming active, so focus stealing prevention would cause here |
144 | more harm than good. |
145 | |
146 | Windows that attempted to become active but KWin prevented this will |
147 | be marked as demanding user attention. They'll get |
148 | the _NET_WM_STATE_DEMANDS_ATTENTION state, and the taskbar should mark |
149 | them specially (blink, etc.). The state will be reset when the window |
150 | eventually really becomes active. |
151 | |
152 | There are one more ways how a window can become obstrusive, window stealing |
153 | focus: By showing above the active window, by either raising itself, |
154 | or by moving itself on the active desktop. |
155 | - KWin will refuse raising non-active window above the active one, |
156 | unless they belong to the same application. Applications shouldn't |
157 | raise their windows anyway (unless the app wants to raise one |
158 | of its windows above another of its windows). |
159 | - KWin activates windows moved to the current desktop (as that seems |
160 | logical from the user's point of view, after sending the window |
161 | there directly from KWin, or e.g. using pager). This means |
162 | applications shouldn't send their windows to another desktop |
163 | (SELI TODO - but what if they do?) |
164 | |
165 | Special cases I can think of: |
166 | - konqueror reusing, i.e. kfmclient tells running Konqueror instance |
167 | to open new window |
168 | - without focus stealing prevention - no problem |
169 | - with ASN (application startup notification) - ASN is forwarded, |
170 | and because it's newer than the instance's user timestamp, |
171 | it takes precedence |
172 | - without ASN - user timestamp needs to be reset, otherwise it would |
173 | be used, and it's old; moreover this new window mustn't be detected |
174 | as window belonging to already running application, or it wouldn't |
175 | be activated - see Client::sameAppWindowRoleMatch() for the (rather ugly) |
176 | hack |
177 | - konqueror preloading, i.e. window is created in advance, and kfmclient |
178 | tells this Konqueror instance to show it later |
179 | - without focus stealing prevention - no problem |
180 | - with ASN - ASN is forwarded, and because it's newer than the instance's |
181 | user timestamp, it takes precedence |
182 | - without ASN - user timestamp needs to be reset, otherwise it would |
183 | be used, and it's old; also, creation timestamp is changed to |
184 | the time the instance starts (re-)initializing the window, |
185 | this ensures creation timestamp will still work somewhat even in this case |
186 | - KUniqueApplication - when the window is already visible, and the new instance |
187 | wants it to activate |
188 | - without focus stealing prevention - _NET_ACTIVE_WINDOW - no problem |
189 | - with ASN - ASN is forwarded, and set on the already visible window, KWin |
190 | treats the window as new with that ASN |
191 | - without ASN - _NET_ACTIVE_WINDOW as application request is used, |
192 | and there's no really usable timestamp, only timestamp |
193 | from the time the (new) application instance was started, |
194 | so KWin will activate the window *sigh* |
195 | - the bad thing here is that there's absolutely no chance to recognize |
196 | the case of starting this KUniqueApp from Konsole (and thus wanting |
197 | the already visible window to become active) from the case |
198 | when something started this KUniqueApp without ASN (in which case |
199 | the already visible window shouldn't become active) |
200 | - the only solution is using ASN for starting applications, at least silent |
201 | (i.e. without feedback) |
202 | - when one application wants to activate another application's window (e.g. KMail |
203 | activating already running KAddressBook window ?) |
204 | - without focus stealing prevention - _NET_ACTIVE_WINDOW - no problem |
205 | - with ASN - can't be here, it's the KUniqueApp case then |
206 | - without ASN - _NET_ACTIVE_WINDOW as application request should be used, |
207 | KWin will activate the new window depending on the timestamp and |
208 | whether it belongs to the currently active application |
209 | |
210 | _NET_ACTIVE_WINDOW usage: |
211 | data.l[0]= 1 ->app request |
212 | = 2 ->pager request |
213 | = 0 - backwards compatibility |
214 | data.l[1]= timestamp |
215 | */ |
216 | |
217 | |
218 | //**************************************** |
219 | // Workspace |
220 | //**************************************** |
221 | |
222 | |
223 | /*! |
224 | Informs the workspace about the active client, i.e. the client that |
225 | has the focus (or None if no client has the focus). This functions |
226 | is called by the client itself that gets focus. It has no other |
227 | effect than fixing the focus chain and the return value of |
228 | activeClient(). And of course, to propagate the active client to the |
229 | world. |
230 | */ |
231 | void Workspace::setActiveClient(Client* c) |
232 | { |
233 | if (active_client == c) |
234 | return; |
235 | |
236 | if (active_popup && active_popup_client != c && set_active_client_recursion == 0) |
237 | closeActivePopup(); |
238 | if (m_userActionsMenu->hasClient() && !m_userActionsMenu->isMenuClient(c) && set_active_client_recursion == 0) { |
239 | m_userActionsMenu->close(); |
240 | } |
241 | StackingUpdatesBlocker blocker(this); |
242 | ++set_active_client_recursion; |
243 | updateFocusMousePosition(cursorPos()); |
244 | if (active_client != NULL) { |
245 | // note that this may call setActiveClient( NULL ), therefore the recursion counter |
246 | active_client->setActive(false); |
247 | } |
248 | active_client = c; |
249 | Q_ASSERT(c == NULL || c->isActive()); |
250 | |
251 | if (active_client) { |
252 | last_active_client = active_client; |
253 | FocusChain::self()->update(active_client, FocusChain::MakeFirst); |
254 | active_client->demandAttention(false); |
255 | |
256 | // activating a client can cause a non active fullscreen window to loose the ActiveLayer status on > 1 screens |
257 | if (screens()->count() > 1) { |
258 | for (ClientList::Iterator it = clients.begin(); it != clients.end(); ++it) { |
259 | if (*it != active_client && (*it)->layer() == ActiveLayer && (*it)->screen() == active_client->screen()) { |
260 | updateClientLayer(*it); |
261 | } |
262 | } |
263 | } |
264 | } |
265 | pending_take_activity = NULL; |
266 | |
267 | updateToolWindows(false); |
268 | if (c) |
269 | disableGlobalShortcutsForClient(c->rules()->checkDisableGlobalShortcuts(false)); |
270 | else |
271 | disableGlobalShortcutsForClient(false); |
272 | |
273 | updateStackingOrder(); // e.g. fullscreens have different layer when active/not-active |
274 | |
275 | rootInfo()->setActiveWindow(active_client ? active_client->window() : 0); |
276 | |
277 | emit clientActivated(active_client); |
278 | --set_active_client_recursion; |
279 | } |
280 | |
281 | /*! |
282 | Tries to activate the client \a c. This function performs what you |
283 | expect when clicking the respective entry in a taskbar: showing and |
284 | raising the client (this may imply switching to the another virtual |
285 | desktop) and putting the focus onto it. Once X really gave focus to |
286 | the client window as requested, the client itself will call |
287 | setActiveClient() and the operation is complete. This may not happen |
288 | with certain focus policies, though. |
289 | |
290 | \sa stActiveClient(), requestFocus() |
291 | */ |
292 | void Workspace::activateClient(Client* c, bool force) |
293 | { |
294 | if (c == NULL) { |
295 | focusToNull(); |
296 | setActiveClient(NULL); |
297 | return; |
298 | } |
299 | raiseClient(c); |
300 | if (!c->isOnCurrentDesktop()) { |
301 | ++block_focus; |
302 | VirtualDesktopManager::self()->setCurrent(c->desktop()); |
303 | --block_focus; |
304 | } |
305 | #ifdef KWIN_BUILD_ACTIVITIES |
306 | if (!c->isOnCurrentActivity()) { |
307 | ++block_focus; |
308 | //DBUS! |
309 | Activities::self()->setCurrent(c->activities().first()); //first isn't necessarily best, but it's easiest |
310 | --block_focus; |
311 | } |
312 | #endif |
313 | if (c->isMinimized()) |
314 | c->unminimize(); |
315 | |
316 | // TODO force should perhaps allow this only if the window already contains the mouse |
317 | if (options->focusPolicyIsReasonable() || force) |
318 | requestFocus(c, force); |
319 | |
320 | // Don't update user time for clients that have focus stealing workaround. |
321 | // As they usually belong to the current active window but fail to provide |
322 | // this information, updating their user time would make the user time |
323 | // of the currently active window old, and reject further activation for it. |
324 | // E.g. typing URL in minicli which will show kio_uiserver dialog (with workaround), |
325 | // and then kdesktop shows dialog about SSL certificate. |
326 | // This needs also avoiding user creation time in Client::readUserTimeMapTimestamp(). |
327 | c->updateUserTime(); |
328 | } |
329 | |
330 | /*! |
331 | Tries to activate the client by asking X for the input focus. This |
332 | function does not perform any show, raise or desktop switching. See |
333 | Workspace::activateClient() instead. |
334 | |
335 | \sa Workspace::activateClient() |
336 | */ |
337 | void Workspace::requestFocus(Client* c, bool force) |
338 | { |
339 | takeActivity(c, ActivityFocus | (force ? ActivityFocusForce : 0), false); |
340 | } |
341 | |
342 | void Workspace::takeActivity(Client* c, int flags, bool handled) |
343 | { |
344 | // the 'if ( c == active_client ) return;' optimization mustn't be done here |
345 | if (!focusChangeEnabled() && (c != active_client)) |
346 | flags &= ~ActivityFocus; |
347 | |
348 | if (!c) { |
349 | focusToNull(); |
350 | return; |
351 | } |
352 | |
353 | if (flags & ActivityFocus) { |
354 | Client* modal = c->findModal(); |
355 | if (modal != NULL && modal != c) { |
356 | if (!modal->isOnDesktop(c->desktop())) { |
357 | modal->setDesktop(c->desktop()); |
358 | if (modal->desktop() != c->desktop()) // forced desktop |
359 | activateClient(modal); |
360 | } |
361 | // if the click was inside the window (i.e. handled is set), |
362 | // but it has a modal, there's no need to use handled mode, because |
363 | // the modal doesn't get the click anyway |
364 | // raising of the original window needs to be still done |
365 | if (flags & ActivityRaise) |
366 | raiseClient(c); |
367 | c = modal; |
368 | handled = false; |
369 | } |
370 | cancelDelayFocus(); |
371 | } |
372 | if (!(flags & ActivityFocusForce) && (c->isDock() || c->isSplash())) |
373 | flags &= ~ActivityFocus; // toplevel menus and dock windows don't take focus if not forced |
374 | if (c->isShade()) { |
375 | if (c->wantsInput() && (flags & ActivityFocus)) { |
376 | // client cannot accept focus, but at least the window should be active (window menu, et. al. ) |
377 | c->setActive(true); |
378 | focusToNull(); |
379 | } |
380 | flags &= ~ActivityFocus; |
381 | handled = false; // no point, can't get clicks |
382 | } |
383 | if (c->tabGroup() && c->tabGroup()->current() != c) |
384 | c->tabGroup()->setCurrent(c); |
385 | if (!c->isShown(true)) { // shouldn't happen, call activateClient() if needed |
386 | kWarning(1212) << "takeActivity: not shown" ; |
387 | return; |
388 | } |
389 | c->takeActivity(flags, handled); |
390 | if (!c->isOnActiveScreen()) |
391 | screens()->setCurrent(c->screen()); |
392 | } |
393 | |
394 | void Workspace::handleTakeActivity(KWin::Client *c, xcb_timestamp_t /*timestamp*/, int flags) |
395 | { |
396 | if (pending_take_activity != c) // pending_take_activity is reset when doing restack or activation |
397 | return; |
398 | if ((flags & ActivityRaise) != 0) |
399 | raiseClient(c); |
400 | if ((flags & ActivityFocus) != 0 && c->isShown(false)) |
401 | c->takeFocus(); |
402 | pending_take_activity = NULL; |
403 | } |
404 | |
405 | /*! |
406 | Informs the workspace that the client \a c has been hidden. If it |
407 | was the active client (or to-become the active client), |
408 | the workspace activates another one. |
409 | |
410 | \a c may already be destroyed |
411 | */ |
412 | void Workspace::clientHidden(Client* c) |
413 | { |
414 | assert(!c->isShown(true) || !c->isOnCurrentDesktop() || !c->isOnCurrentActivity()); |
415 | activateNextClient(c); |
416 | } |
417 | |
418 | Client *Workspace::clientUnderMouse(int screen) const |
419 | { |
420 | ToplevelList::const_iterator it = stackingOrder().constEnd(); |
421 | while (it != stackingOrder().constBegin()) { |
422 | Client *client = qobject_cast<Client*>(*(--it)); |
423 | if (!client) { |
424 | continue; |
425 | } |
426 | |
427 | // rule out clients which are not really visible. |
428 | // the screen test is rather superfluous for xrandr & twinview since the geometry would differ -> TODO: might be dropped |
429 | if (!(client->isShown(false) && client->isOnCurrentDesktop() && |
430 | client->isOnCurrentActivity() && client->isOnScreen(screen))) |
431 | continue; |
432 | |
433 | if (client->geometry().contains(Cursor::pos())) { |
434 | return client; |
435 | } |
436 | } |
437 | return 0; |
438 | } |
439 | |
440 | // deactivates 'c' and activates next client |
441 | bool Workspace::activateNextClient(Client* c) |
442 | { |
443 | // if 'c' is not the active or the to-become active one, do nothing |
444 | if (!(c == active_client || (should_get_focus.count() > 0 && c == should_get_focus.last()))) |
445 | return false; |
446 | |
447 | closeActivePopup(); |
448 | |
449 | if (c != NULL) { |
450 | if (c == active_client) |
451 | setActiveClient(NULL); |
452 | should_get_focus.removeAll(c); |
453 | } |
454 | |
455 | // if blocking focus, move focus to the desktop later if needed |
456 | // in order to avoid flickering |
457 | if (!focusChangeEnabled()) { |
458 | focusToNull(); |
459 | return true; |
460 | } |
461 | |
462 | if (!options->focusPolicyIsReasonable()) |
463 | return false; |
464 | |
465 | Client* get_focus = NULL; |
466 | |
467 | // precedence on keeping the current tabgroup active. to the user that's the same window |
468 | if (c && c->tabGroup() && c->isShown(false)) { |
469 | if (c == c->tabGroup()->current()) |
470 | c->tabGroup()->activateNext(); |
471 | get_focus = c->tabGroup()->current(); |
472 | if (get_focus == c) // single tab case - should not happen |
473 | get_focus = NULL; |
474 | } |
475 | |
476 | if (!get_focus && options->isNextFocusPrefersMouse()) { |
477 | get_focus = clientUnderMouse(c ? c->screen() : screens()->current()); |
478 | if (get_focus && (get_focus == c || get_focus->isDesktop())) { |
479 | // should rather not happen, but it cannot get the focus. rest of usability is tested above |
480 | get_focus = NULL; |
481 | } |
482 | } |
483 | |
484 | const int desktop = VirtualDesktopManager::self()->current(); |
485 | |
486 | if (!get_focus) { // no suitable window under the mouse -> find sth. else |
487 | // first try to pass the focus to the (former) active clients leader |
488 | if (c && (get_focus = c->transientFor()) && FocusChain::self()->isUsableFocusCandidate(get_focus, c)) { |
489 | raiseClient(get_focus); // also raise - we don't know where it came from |
490 | } else { |
491 | // nope, ask the focus chain for the next candidate |
492 | get_focus = FocusChain::self()->nextForDesktop(c, desktop); |
493 | } |
494 | } |
495 | |
496 | if (get_focus == NULL) // last chance: focus the desktop |
497 | get_focus = findDesktop(true, desktop); |
498 | |
499 | if (get_focus != NULL) |
500 | requestFocus(get_focus); |
501 | else |
502 | focusToNull(); |
503 | |
504 | return true; |
505 | |
506 | } |
507 | |
508 | void Workspace::setCurrentScreen(int new_screen) |
509 | { |
510 | if (new_screen < 0 || new_screen >= screens()->count()) |
511 | return; |
512 | if (!options->focusPolicyIsReasonable()) |
513 | return; |
514 | closeActivePopup(); |
515 | const int desktop = VirtualDesktopManager::self()->current(); |
516 | Client *get_focus = FocusChain::self()->getForActivation(desktop, new_screen); |
517 | if (get_focus == NULL) |
518 | get_focus = findDesktop(true, desktop); |
519 | if (get_focus != NULL && get_focus != mostRecentlyActivatedClient()) |
520 | requestFocus(get_focus); |
521 | screens()->setCurrent(new_screen); |
522 | } |
523 | |
524 | void Workspace::gotFocusIn(const Client* c) |
525 | { |
526 | if (should_get_focus.contains(const_cast< Client* >(c))) { |
527 | // remove also all sooner elements that should have got FocusIn, |
528 | // but didn't for some reason (and also won't anymore, because they were sooner) |
529 | while (should_get_focus.first() != c) |
530 | should_get_focus.pop_front(); |
531 | should_get_focus.pop_front(); // remove 'c' |
532 | } |
533 | } |
534 | |
535 | void Workspace::setShouldGetFocus(Client* c) |
536 | { |
537 | should_get_focus.append(c); |
538 | updateStackingOrder(); // e.g. fullscreens have different layer when active/not-active |
539 | } |
540 | |
541 | // focus_in -> the window got FocusIn event |
542 | // ignore_desktop - call comes from _NET_ACTIVE_WINDOW message, don't refuse just because of window |
543 | // is on a different desktop |
544 | bool Workspace::allowClientActivation(const KWin::Client *c, xcb_timestamp_t time, bool focus_in, bool ignore_desktop) |
545 | { |
546 | // options->focusStealingPreventionLevel : |
547 | // 0 - none - old KWin behaviour, new windows always get focus |
548 | // 1 - low - focus stealing prevention is applied normally, when unsure, activation is allowed |
549 | // 2 - normal - focus stealing prevention is applied normally, when unsure, activation is not allowed, |
550 | // this is the default |
551 | // 3 - high - new window gets focus only if it belongs to the active application, |
552 | // or when no window is currently active |
553 | // 4 - extreme - no window gets focus without user intervention |
554 | if (time == -1U) |
555 | time = c->userTime(); |
556 | int level = c->rules()->checkFSP(options->focusStealingPreventionLevel()); |
557 | if (session_saving && level <= 2) { // <= normal |
558 | return true; |
559 | } |
560 | Client* ac = mostRecentlyActivatedClient(); |
561 | if (focus_in) { |
562 | if (should_get_focus.contains(const_cast< Client* >(c))) |
563 | return true; // FocusIn was result of KWin's action |
564 | // Before getting FocusIn, the active Client already |
565 | // got FocusOut, and therefore got deactivated. |
566 | ac = last_active_client; |
567 | } |
568 | if (time == 0) // explicitly asked not to get focus |
569 | return false; |
570 | if (level == 0) // none |
571 | return true; |
572 | if (level == 4) // extreme |
573 | return false; |
574 | if (!ignore_desktop && !c->isOnCurrentDesktop()) |
575 | return false; // allow only with level == 0 |
576 | if (ac == NULL || ac->isDesktop()) { |
577 | kDebug(1212) << "Activation: No client active, allowing" ; |
578 | return true; // no active client -> always allow |
579 | } |
580 | // TODO window urgency -> return true? |
581 | if (Client::belongToSameApplication(c, ac, true)) { |
582 | kDebug(1212) << "Activation: Belongs to active application" ; |
583 | return true; |
584 | } |
585 | if (level == 3) // high |
586 | return false; |
587 | if (time == -1U) { // no time known |
588 | kDebug(1212) << "Activation: No timestamp at all" ; |
589 | if (level == 1) // low |
590 | return true; |
591 | // no timestamp at all, don't activate - because there's also creation timestamp |
592 | // done on CreateNotify, this case should happen only in case application |
593 | // maps again already used window, i.e. this won't happen after app startup |
594 | return false; |
595 | } |
596 | // level == 2 // normal |
597 | Time user_time = ac->userTime(); |
598 | kDebug(1212) << "Activation, compared:" << c << ":" << time << ":" << user_time |
599 | << ":" << (timestampCompare(time, user_time) >= 0) << endl; |
600 | return timestampCompare(time, user_time) >= 0; // time >= user_time |
601 | } |
602 | |
603 | // basically the same like allowClientActivation(), this time allowing |
604 | // a window to be fully raised upon its own request (XRaiseWindow), |
605 | // if refused, it will be raised only on top of windows belonging |
606 | // to the same application |
607 | bool Workspace::allowFullClientRaising(const KWin::Client *c, xcb_timestamp_t time) |
608 | { |
609 | int level = c->rules()->checkFSP(options->focusStealingPreventionLevel()); |
610 | if (session_saving && level <= 2) { // <= normal |
611 | return true; |
612 | } |
613 | Client* ac = mostRecentlyActivatedClient(); |
614 | if (level == 0) // none |
615 | return true; |
616 | if (level == 4) // extreme |
617 | return false; |
618 | if (ac == NULL || ac->isDesktop()) { |
619 | kDebug(1212) << "Raising: No client active, allowing" ; |
620 | return true; // no active client -> always allow |
621 | } |
622 | // TODO window urgency -> return true? |
623 | if (Client::belongToSameApplication(c, ac, true)) { |
624 | kDebug(1212) << "Raising: Belongs to active application" ; |
625 | return true; |
626 | } |
627 | if (level == 3) // high |
628 | return false; |
629 | xcb_timestamp_t user_time = ac->userTime(); |
630 | kDebug(1212) << "Raising, compared:" << time << ":" << user_time |
631 | << ":" << (timestampCompare(time, user_time) >= 0) << endl; |
632 | return timestampCompare(time, user_time) >= 0; // time >= user_time |
633 | } |
634 | |
635 | // called from Client after FocusIn that wasn't initiated by KWin and the client |
636 | // wasn't allowed to activate |
637 | void Workspace::restoreFocus() |
638 | { |
639 | // this updateXTime() is necessary - as FocusIn events don't have |
640 | // a timestamp *sigh*, kwin's timestamp would be older than the timestamp |
641 | // that was used by whoever caused the focus change, and therefore |
642 | // the attempt to restore the focus would fail due to old timestamp |
643 | updateXTime(); |
644 | if (should_get_focus.count() > 0) |
645 | requestFocus(should_get_focus.last()); |
646 | else if (last_active_client) |
647 | requestFocus(last_active_client); |
648 | } |
649 | |
650 | void Workspace::clientAttentionChanged(Client* c, bool set) |
651 | { |
652 | if (set) { |
653 | attention_chain.removeAll(c); |
654 | attention_chain.prepend(c); |
655 | } else |
656 | attention_chain.removeAll(c); |
657 | emit clientDemandsAttentionChanged(c, set); |
658 | } |
659 | |
660 | //******************************************** |
661 | // Client |
662 | //******************************************** |
663 | |
664 | /*! |
665 | Updates the user time (time of last action in the active window). |
666 | This is called inside kwin for every action with the window |
667 | that qualifies for user interaction (clicking on it, activate it |
668 | externally, etc.). |
669 | */ |
670 | void Client::updateUserTime(xcb_timestamp_t time) |
671 | { |
672 | // copied in Group::updateUserTime |
673 | if (time == XCB_TIME_CURRENT_TIME) |
674 | time = xTime(); |
675 | if (time != -1U |
676 | && (m_userTime == XCB_TIME_CURRENT_TIME |
677 | || timestampCompare(time, m_userTime) > 0)) { // time > user_time |
678 | m_userTime = time; |
679 | shade_below = NULL; // do not hover re-shade a window after it got interaction |
680 | } |
681 | group()->updateUserTime(m_userTime); |
682 | } |
683 | |
684 | xcb_timestamp_t Client::readUserCreationTime() const |
685 | { |
686 | const xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, window(), |
687 | atoms->kde_net_wm_user_creation_time, XCB_ATOM_CARDINAL, 0, 10000); |
688 | ScopedCPointer<xcb_get_property_reply_t> property(xcb_get_property_reply(connection(), cookie, NULL)); |
689 | if (property.isNull() || xcb_get_property_value_length(property.data()) == 0) { |
690 | return -1; |
691 | } |
692 | return *(reinterpret_cast<xcb_timestamp_t*>(xcb_get_property_value(property.data()))); |
693 | } |
694 | |
695 | void Client::demandAttention(bool set) |
696 | { |
697 | if (isActive()) |
698 | set = false; |
699 | if (demands_attention == set) |
700 | return; |
701 | demands_attention = set; |
702 | info->setState(set ? NET::DemandsAttention : 0, NET::DemandsAttention); |
703 | workspace()->clientAttentionChanged(this, set); |
704 | emit demandsAttentionChanged(); |
705 | } |
706 | |
707 | // TODO I probably shouldn't be lazy here and do it without the macro, so that people can read it |
708 | KWIN_COMPARE_PREDICATE(SameApplicationActiveHackPredicate, Client, const Client*, |
709 | // ignore already existing splashes, toolbars, utilities and menus, |
710 | // as the app may show those before the main window |
711 | !cl->isSplash() && !cl->isToolbar() && !cl->isUtility() && !cl->isMenu() |
712 | && Client::belongToSameApplication(cl, value, true) && cl != value); |
713 | |
714 | xcb_timestamp_t Client::readUserTimeMapTimestamp(const KStartupInfoId *asn_id, const KStartupInfoData *asn_data, |
715 | bool session) const |
716 | { |
717 | xcb_timestamp_t time = info->userTime(); |
718 | //kDebug( 1212 ) << "User timestamp, initial:" << time; |
719 | //^^ this deadlocks kwin --replace sometimes. |
720 | |
721 | // newer ASN timestamp always replaces user timestamp, unless user timestamp is 0 |
722 | // helps e.g. with konqy reusing |
723 | if (asn_data != NULL && time != 0) { |
724 | if (asn_id->timestamp() != 0 |
725 | && (time == -1U || timestampCompare(asn_id->timestamp(), time) > 0)) { |
726 | time = asn_id->timestamp(); |
727 | } |
728 | } |
729 | kDebug(1212) << "User timestamp, ASN:" << time; |
730 | if (time == -1U) { |
731 | // The window doesn't have any timestamp. |
732 | // If it's the first window for its application |
733 | // (i.e. there's no other window from the same app), |
734 | // use the _KDE_NET_WM_USER_CREATION_TIME trick. |
735 | // Otherwise, refuse activation of a window |
736 | // from already running application if this application |
737 | // is not the active one (unless focus stealing prevention is turned off). |
738 | Client* act = workspace()->mostRecentlyActivatedClient(); |
739 | if (act != NULL && !belongToSameApplication(act, this, true)) { |
740 | bool first_window = true; |
741 | if (isTransient()) { |
742 | if (act->hasTransient(this, true)) |
743 | ; // is transient for currently active window, even though it's not |
744 | // the same app (e.g. kcookiejar dialog) -> allow activation |
745 | else if (groupTransient() && |
746 | findClientInList(mainClients(), SameApplicationActiveHackPredicate(this)) == NULL) |
747 | ; // standalone transient |
748 | else |
749 | first_window = false; |
750 | } else { |
751 | if (workspace()->findClient(SameApplicationActiveHackPredicate(this))) |
752 | first_window = false; |
753 | } |
754 | // don't refuse if focus stealing prevention is turned off |
755 | if (!first_window && rules()->checkFSP(options->focusStealingPreventionLevel()) > 0) { |
756 | kDebug(1212) << "User timestamp, already exists:" << 0; |
757 | return 0; // refuse activation |
758 | } |
759 | } |
760 | // Creation time would just mess things up during session startup, |
761 | // as possibly many apps are started up at the same time. |
762 | // If there's no active window yet, no timestamp will be needed, |
763 | // as plain Workspace::allowClientActivation() will return true |
764 | // in such case. And if there's already active window, |
765 | // it's better not to activate the new one. |
766 | // Unless it was the active window at the time |
767 | // of session saving and there was no user interaction yet, |
768 | // this check will be done in manage(). |
769 | if (session) |
770 | return -1U; |
771 | time = readUserCreationTime(); |
772 | } |
773 | kDebug(1212) << "User timestamp, final:" << this << ":" << time; |
774 | return time; |
775 | } |
776 | |
777 | xcb_timestamp_t Client::userTime() const |
778 | { |
779 | xcb_timestamp_t time = m_userTime; |
780 | if (time == 0) // doesn't want focus after showing |
781 | return 0; |
782 | assert(group() != NULL); |
783 | if (time == -1U |
784 | || (group()->userTime() != -1U |
785 | && timestampCompare(group()->userTime(), time) > 0)) |
786 | time = group()->userTime(); |
787 | return time; |
788 | } |
789 | |
790 | /*! |
791 | Sets the client's active state to \a act. |
792 | |
793 | This function does only change the visual appearance of the client, |
794 | it does not change the focus setting. Use |
795 | Workspace::activateClient() or Workspace::requestFocus() instead. |
796 | |
797 | If a client receives or looses the focus, it calls setActive() on |
798 | its own. |
799 | |
800 | */ |
801 | void Client::setActive(bool act) |
802 | { |
803 | if (active == act) |
804 | return; |
805 | active = act; |
806 | const int ruledOpacity = active |
807 | ? rules()->checkOpacityActive(qRound(opacity() * 100.0)) |
808 | : rules()->checkOpacityInactive(qRound(opacity() * 100.0)); |
809 | setOpacity(ruledOpacity / 100.0); |
810 | workspace()->setActiveClient(act ? this : NULL); |
811 | |
812 | if (!active) |
813 | cancelAutoRaise(); |
814 | |
815 | if (!active && shade_mode == ShadeActivated) |
816 | setShade(ShadeNormal); |
817 | |
818 | StackingUpdatesBlocker blocker(workspace()); |
819 | workspace()->updateClientLayer(this); // active windows may get different layer |
820 | ClientList mainclients = mainClients(); |
821 | for (ClientList::ConstIterator it = mainclients.constBegin(); |
822 | it != mainclients.constEnd(); |
823 | ++it) |
824 | if ((*it)->isFullScreen()) // fullscreens go high even if their transient is active |
825 | workspace()->updateClientLayer(*it); |
826 | emit activeChanged(); |
827 | updateMouseGrab(); |
828 | updateUrgency(); // demand attention again if it's still urgent |
829 | } |
830 | |
831 | void Client::startupIdChanged() |
832 | { |
833 | KStartupInfoId asn_id; |
834 | KStartupInfoData asn_data; |
835 | bool asn_valid = workspace()->checkStartupNotification(window(), asn_id, asn_data); |
836 | if (!asn_valid) |
837 | return; |
838 | // If the ASN contains desktop, move it to the desktop, otherwise move it to the current |
839 | // desktop (since the new ASN should make the window act like if it's a new application |
840 | // launched). However don't affect the window's desktop if it's set to be on all desktops. |
841 | int desktop = VirtualDesktopManager::self()->current(); |
842 | if (asn_data.desktop() != 0) |
843 | desktop = asn_data.desktop(); |
844 | if (!isOnAllDesktops()) |
845 | workspace()->sendClientToDesktop(this, desktop, true); |
846 | if (asn_data.xinerama() != -1) |
847 | workspace()->sendClientToScreen(this, asn_data.xinerama()); |
848 | Time timestamp = asn_id.timestamp(); |
849 | if (timestamp != 0) { |
850 | bool activate = workspace()->allowClientActivation(this, timestamp); |
851 | if (asn_data.desktop() != 0 && !isOnCurrentDesktop()) |
852 | activate = false; // it was started on different desktop than current one |
853 | if (activate) |
854 | workspace()->activateClient(this); |
855 | else |
856 | demandAttention(); |
857 | } |
858 | } |
859 | |
860 | void Client::updateUrgency() |
861 | { |
862 | if (urgency) |
863 | demandAttention(); |
864 | } |
865 | |
866 | void Client::shortcutActivated() |
867 | { |
868 | workspace()->activateClient(this, true); // force |
869 | } |
870 | |
871 | //**************************************** |
872 | // Group |
873 | //**************************************** |
874 | |
875 | void Group::startupIdChanged() |
876 | { |
877 | KStartupInfoId asn_id; |
878 | KStartupInfoData asn_data; |
879 | bool asn_valid = workspace()->checkStartupNotification(leader_wid, asn_id, asn_data); |
880 | if (!asn_valid) |
881 | return; |
882 | if (asn_id.timestamp() != -1U && user_time != -1U |
883 | && timestampCompare(asn_id.timestamp(), user_time) > 0) { |
884 | user_time = asn_id.timestamp(); |
885 | } |
886 | } |
887 | |
888 | void Group::updateUserTime(Time time) |
889 | { |
890 | // copy of Client::updateUserTime |
891 | if (time == CurrentTime) |
892 | time = xTime(); |
893 | if (time != -1U |
894 | && (user_time == CurrentTime |
895 | || timestampCompare(time, user_time) > 0)) // time > user_time |
896 | user_time = time; |
897 | } |
898 | |
899 | } // namespace |
900 | |