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 | // This file contains things relevant to handling incoming events. |
23 | |
24 | #include "client.h" |
25 | |
26 | #include <kstartupinfo.h> |
27 | #include <kglobal.h> |
28 | #include <X11/extensions/shape.h> |
29 | |
30 | #ifdef KWIN_BUILD_ACTIVITIES |
31 | #include "activities.h" |
32 | #endif |
33 | #include "cursor.h" |
34 | #include "decorations.h" |
35 | #include <QX11Info> |
36 | #include "rules.h" |
37 | #include "group.h" |
38 | #include "netinfo.h" |
39 | #include "screens.h" |
40 | #include "workspace.h" |
41 | #include "xcbutils.h" |
42 | |
43 | namespace KWin |
44 | { |
45 | |
46 | /** |
47 | * Manages the clients. This means handling the very first maprequest: |
48 | * reparenting, initial geometry, initial state, placement, etc. |
49 | * Returns false if KWin is not going to manage this window. |
50 | */ |
51 | bool Client::manage(xcb_window_t w, bool isMapped) |
52 | { |
53 | StackingUpdatesBlocker stacking_blocker(workspace()); |
54 | |
55 | grabXServer(); |
56 | |
57 | XWindowAttributes attr; |
58 | if (!XGetWindowAttributes(display(), w, &attr)) { |
59 | ungrabXServer(); |
60 | return false; |
61 | } |
62 | |
63 | // From this place on, manage() must not return false |
64 | block_geometry_updates = 1; |
65 | pending_geometry_update = PendingGeometryForced; // Force update when finishing with geometry changes |
66 | |
67 | embedClient(w, attr); |
68 | |
69 | vis = attr.visual; |
70 | bit_depth = attr.depth; |
71 | |
72 | // SELI TODO: Order all these things in some sane manner |
73 | |
74 | bool init_minimize = false; |
75 | XWMHints* hints = XGetWMHints(display(), w); |
76 | if (hints && (hints->flags & StateHint) && hints->initial_state == IconicState) |
77 | init_minimize = true; |
78 | if (hints) |
79 | XFree(hints); |
80 | if (isMapped) |
81 | init_minimize = false; // If it's already mapped, ignore hint |
82 | |
83 | unsigned long properties[2]; |
84 | properties[WinInfo::PROTOCOLS] = |
85 | NET::WMDesktop | |
86 | NET::WMState | |
87 | NET::WMWindowType | |
88 | NET::WMStrut | |
89 | NET::WMName | |
90 | NET::WMIconGeometry | |
91 | NET::WMIcon | |
92 | NET::WMPid | |
93 | NET::WMIconName | |
94 | 0; |
95 | properties[WinInfo::PROTOCOLS2] = |
96 | NET::WM2UserTime | |
97 | NET::WM2StartupId | |
98 | NET::WM2ExtendedStrut | |
99 | NET::WM2Opacity | |
100 | NET::WM2FullscreenMonitors | |
101 | NET::WM2FrameOverlap | |
102 | 0; |
103 | |
104 | info = new WinInfo(this, display(), m_client, rootWindow(), properties, 2); |
105 | |
106 | m_colormap = attr.colormap; |
107 | |
108 | getResourceClass(); |
109 | getWindowRole(); |
110 | getWmClientLeader(); |
111 | getWmClientMachine(); |
112 | getSyncCounter(); |
113 | // First only read the caption text, so that setupWindowRules() can use it for matching, |
114 | // and only then really set the caption using setCaption(), which checks for duplicates etc. |
115 | // and also relies on rules already existing |
116 | cap_normal = readName(); |
117 | setupWindowRules(false); |
118 | setCaption(cap_normal, true); |
119 | |
120 | if (Xcb::Extensions::self()->isShapeAvailable()) |
121 | XShapeSelectInput(display(), window(), ShapeNotifyMask); |
122 | detectShape(window()); |
123 | detectNoBorder(); |
124 | fetchIconicName(); |
125 | getWMHints(); // Needs to be done before readTransient() because of reading the group |
126 | modal = (info->state() & NET::Modal) != 0; // Needs to be valid before handling groups |
127 | readTransient(); |
128 | getIcons(); |
129 | getWindowProtocols(); |
130 | getWmNormalHints(); // Get xSizeHint |
131 | getMotifHints(); |
132 | getWmOpaqueRegion(); |
133 | getSkipCloseAnimation(); |
134 | |
135 | // TODO: Try to obey all state information from info->state() |
136 | |
137 | original_skip_taskbar = skip_taskbar = (info->state() & NET::SkipTaskbar) != 0; |
138 | skip_pager = (info->state() & NET::SkipPager) != 0; |
139 | updateFirstInTabBox(); |
140 | |
141 | setupCompositing(); |
142 | |
143 | KStartupInfoId asn_id; |
144 | KStartupInfoData asn_data; |
145 | bool asn_valid = workspace()->checkStartupNotification(window(), asn_id, asn_data); |
146 | |
147 | // Make sure that the input window is created before we update the stacking order |
148 | updateInputWindow(); |
149 | |
150 | workspace()->updateClientLayer(this); |
151 | |
152 | SessionInfo* session = workspace()->takeSessionInfo(this); |
153 | if (session) { |
154 | init_minimize = session->minimized; |
155 | noborder = session->noBorder; |
156 | } |
157 | |
158 | setShortcut(rules()->checkShortcut(session ? session->shortcut : QString(), true)); |
159 | |
160 | init_minimize = rules()->checkMinimize(init_minimize, !isMapped); |
161 | noborder = rules()->checkNoBorder(noborder, !isMapped); |
162 | |
163 | checkActivities(); |
164 | |
165 | // Initial desktop placement |
166 | if (session) { |
167 | desk = session->desktop; |
168 | if (session->onAllDesktops) |
169 | desk = NET::OnAllDesktops; |
170 | setOnActivities(session->activities); |
171 | } else { |
172 | // If this window is transient, ensure that it is opened on the |
173 | // same window as its parent. this is necessary when an application |
174 | // starts up on a different desktop than is currently displayed |
175 | if (isTransient()) { |
176 | ClientList mainclients = mainClients(); |
177 | bool on_current = false; |
178 | bool on_all = false; |
179 | Client* maincl = NULL; |
180 | // This is slightly duplicated from Placement::placeOnMainWindow() |
181 | for (ClientList::ConstIterator it = mainclients.constBegin(); |
182 | it != mainclients.constEnd(); |
183 | ++it) { |
184 | if (mainclients.count() > 1 && (*it)->isSpecialWindow()) |
185 | continue; // Don't consider toolbars etc when placing |
186 | maincl = *it; |
187 | if ((*it)->isOnCurrentDesktop()) |
188 | on_current = true; |
189 | if ((*it)->isOnAllDesktops()) |
190 | on_all = true; |
191 | } |
192 | if (on_all) |
193 | desk = NET::OnAllDesktops; |
194 | else if (on_current) |
195 | desk = VirtualDesktopManager::self()->current(); |
196 | else if (maincl != NULL) |
197 | desk = maincl->desktop(); |
198 | |
199 | if (maincl) |
200 | setOnActivities(maincl->activities()); |
201 | } |
202 | if (info->desktop()) |
203 | desk = info->desktop(); // Window had the initial desktop property, force it |
204 | if (desktop() == 0 && asn_valid && asn_data.desktop() != 0) |
205 | desk = asn_data.desktop(); |
206 | #ifdef KWIN_BUILD_ACTIVITIES |
207 | if (!isMapped && !noborder && isNormalWindow() && !activitiesDefined) { |
208 | //a new, regular window, when we're not recovering from a crash, |
209 | //and it hasn't got an activity. let's try giving it the current one. |
210 | //TODO: decide whether to keep this before the 4.6 release |
211 | //TODO: if we are keeping it (at least as an option), replace noborder checking |
212 | //with a public API for setting windows to be on all activities. |
213 | //something like KWindowSystem::setOnAllActivities or |
214 | //KActivityConsumer::setOnAllActivities |
215 | setOnActivity(Activities::self()->current(), true); |
216 | } |
217 | #endif |
218 | } |
219 | |
220 | if (desk == 0) // Assume window wants to be visible on the current desktop |
221 | desk = isDesktop() ? static_cast<int>(NET::OnAllDesktops) : VirtualDesktopManager::self()->current(); |
222 | desk = rules()->checkDesktop(desk, !isMapped); |
223 | if (desk != NET::OnAllDesktops) // Do range check |
224 | desk = qBound(1, desk, static_cast<int>(VirtualDesktopManager::self()->count())); |
225 | info->setDesktop(desk); |
226 | workspace()->updateOnAllDesktopsOfTransients(this); // SELI TODO |
227 | //onAllDesktopsChange(); // Decoration doesn't exist here yet |
228 | |
229 | QString activitiesList; |
230 | activitiesList = rules()->checkActivity(activitiesList, !isMapped); |
231 | if (!activitiesList.isEmpty()) |
232 | setOnActivities(activitiesList.split(',')); |
233 | |
234 | QRect geom(attr.x, attr.y, attr.width, attr.height); |
235 | bool placementDone = false; |
236 | |
237 | if (session) |
238 | geom = session->geometry; |
239 | |
240 | QRect area; |
241 | bool partial_keep_in_area = isMapped || session; |
242 | if (isMapped || session) |
243 | area = workspace()->clientArea(FullArea, geom.center(), desktop()); |
244 | else { |
245 | int screen = asn_data.xinerama() == -1 ? screens()->current() : asn_data.xinerama(); |
246 | screen = rules()->checkScreen(screen, !isMapped); |
247 | area = workspace()->clientArea(PlacementArea, screens()->geometry(screen).center(), desktop()); |
248 | } |
249 | |
250 | if (int type = checkFullScreenHack(geom)) { |
251 | fullscreen_mode = FullScreenHack; |
252 | if (rules()->checkStrictGeometry(false)) { |
253 | geom = type == 2 // 1 = It's xinerama-aware fullscreen hack, 2 = It's full area |
254 | ? workspace()->clientArea(FullArea, geom.center(), desktop()) |
255 | : workspace()->clientArea(ScreenArea, geom.center(), desktop()); |
256 | } else |
257 | geom = workspace()->clientArea(FullScreenArea, geom.center(), desktop()); |
258 | placementDone = true; |
259 | } |
260 | |
261 | if (isDesktop()) |
262 | // KWin doesn't manage desktop windows |
263 | placementDone = true; |
264 | |
265 | bool usePosition = false; |
266 | if (isMapped || session || placementDone) |
267 | placementDone = true; // Use geometry |
268 | else if (isTransient() && !isUtility() && !isDialog() && !isSplash()) |
269 | usePosition = true; |
270 | else if (isTransient() && !hasNETSupport()) |
271 | usePosition = true; |
272 | else if (isDialog() && hasNETSupport()) { |
273 | // If the dialog is actually non-NETWM transient window, don't try to apply placement to it, |
274 | // it breaks with too many things (xmms, display) |
275 | if (mainClients().count() >= 1) { |
276 | #if 1 |
277 | // #78082 - Ok, it seems there are after all some cases when an application has a good |
278 | // reason to specify a position for its dialog. Too bad other WMs have never bothered |
279 | // with placement for dialogs, so apps always specify positions for their dialogs, |
280 | // including such silly positions like always centered on the screen or under mouse. |
281 | // Using ignoring requested position in window-specific settings helps, and now |
282 | // there's also _NET_WM_FULL_PLACEMENT. |
283 | usePosition = true; |
284 | #else |
285 | ; // Force using placement policy |
286 | #endif |
287 | } else |
288 | usePosition = true; |
289 | } else if (isSplash()) |
290 | ; // Force using placement policy |
291 | else |
292 | usePosition = true; |
293 | if (!rules()->checkIgnoreGeometry(!usePosition, true)) { |
294 | if (((xSizeHint.flags & PPosition)) || |
295 | (xSizeHint.flags & USPosition)) { |
296 | placementDone = true; |
297 | // Disobey xinerama placement option for now (#70943) |
298 | area = workspace()->clientArea(PlacementArea, geom.center(), desktop()); |
299 | } |
300 | } |
301 | //if ( true ) // Size is always obeyed for now, only with constraints applied |
302 | // if (( xSizeHint.flags & USSize ) || ( xSizeHint.flags & PSize )) |
303 | // { |
304 | // // Keep in mind that we now actually have a size :-) |
305 | // } |
306 | |
307 | if (xSizeHint.flags & PMaxSize) |
308 | geom.setSize(geom.size().boundedTo( |
309 | rules()->checkMaxSize(QSize(xSizeHint.max_width, xSizeHint.max_height)))); |
310 | if (xSizeHint.flags & PMinSize) |
311 | geom.setSize(geom.size().expandedTo( |
312 | rules()->checkMinSize(QSize(xSizeHint.min_width, xSizeHint.min_height)))); |
313 | |
314 | if (isMovable() && (geom.x() > area.right() || geom.y() > area.bottom())) |
315 | placementDone = false; // Weird, do not trust. |
316 | |
317 | if (placementDone) |
318 | move(geom.x(), geom.y()); // Before gravitating |
319 | |
320 | // Create client group if the window will have a decoration |
321 | bool dontKeepInArea = false; |
322 | setTabGroup(NULL); |
323 | if (!noBorder() && DecorationPlugin::self()->supportsTabbing()) { |
324 | const bool autogrouping = rules()->checkAutogrouping(options->isAutogroupSimilarWindows()); |
325 | const bool autogroupInFg = rules()->checkAutogroupInForeground(options->isAutogroupInForeground()); |
326 | // Automatically add to previous groups on session restore |
327 | if (session && session->tabGroupClient && !workspace()->hasClient(session->tabGroupClient)) |
328 | session->tabGroupClient = NULL; |
329 | if (session && session->tabGroupClient && session->tabGroupClient != this) { |
330 | tabBehind(session->tabGroupClient, autogroupInFg); |
331 | } else if (isMapped && autogrouping) { |
332 | // If the window is already mapped (Restarted KWin) add any windows that already have the |
333 | // same geometry to the same client group. (May incorrectly handle maximized windows) |
334 | foreach (Client *other, workspace()->clientList()) { |
335 | if (other->maximizeMode() != MaximizeFull && |
336 | geom == QRect(other->pos(), other->clientSize()) && |
337 | desk == other->desktop() && activities() == other->activities()) { |
338 | |
339 | tabBehind(other, autogroupInFg); |
340 | break; |
341 | |
342 | } |
343 | } |
344 | } |
345 | if (!(tab_group || isMapped || session)) { |
346 | // Attempt to automatically group similar windows |
347 | Client* similar = findAutogroupCandidate(); |
348 | if (similar && !similar->noBorder()) { |
349 | if (autogroupInFg) { |
350 | similar->setDesktop(desk); // can happen when grouping by id. ... |
351 | similar->setMinimized(false); // ... or anyway - still group, but "here" and visible |
352 | } |
353 | if (!similar->isMinimized()) { // do not attempt to tab in background of a hidden group |
354 | geom = QRect(similar->pos() + similar->clientPos(), similar->clientSize()); |
355 | updateDecoration(false); |
356 | if (tabBehind(similar, autogroupInFg)) { |
357 | // Don't move entire group |
358 | geom = QRect(similar->pos() + similar->clientPos(), similar->clientSize()); |
359 | placementDone = true; |
360 | dontKeepInArea = true; |
361 | } |
362 | } |
363 | } |
364 | } |
365 | } |
366 | |
367 | updateDecoration(false); // Also gravitates |
368 | // TODO: Is CentralGravity right here, when resizing is done after gravitating? |
369 | plainResize(rules()->checkSize(sizeForClientSize(geom.size()), !isMapped)); |
370 | |
371 | QPoint forced_pos = rules()->checkPosition(invalidPoint, !isMapped); |
372 | if (forced_pos != invalidPoint) { |
373 | move(forced_pos); |
374 | placementDone = true; |
375 | // Don't keep inside workarea if the window has specially configured position |
376 | partial_keep_in_area = true; |
377 | area = workspace()->clientArea(FullArea, geom.center(), desktop()); |
378 | } |
379 | if (!placementDone) { |
380 | // Placement needs to be after setting size |
381 | Placement::self()->place(this, area); |
382 | dontKeepInArea = true; |
383 | placementDone = true; |
384 | } |
385 | |
386 | // bugs #285967, #286146, #183694 |
387 | // geometry() now includes the requested size and the decoration and is at the correct screen/position (hopefully) |
388 | // Maximization for oversized windows must happen NOW. |
389 | // If we effectively pass keepInArea(), the window will resizeWithChecks() - i.e. constrained |
390 | // to the combo of all screen MINUS all struts on the edges |
391 | // If only one screen struts, this will affect screens as a side-effect, the window is artificailly shrinked |
392 | // below the screen size and as result no more maximized what breaks KMainWindow's stupid width+1, height+1 hack |
393 | // TODO: get KMainWindow a correct state storage what will allow to store the restore size as well. |
394 | |
395 | if (!session) { // has a better handling of this |
396 | geom_restore = geometry(); // Remember restore geometry |
397 | if (isMaximizable() && (width() >= area.width() || height() >= area.height())) { |
398 | // Window is too large for the screen, maximize in the |
399 | // directions necessary |
400 | const QSize ss = workspace()->clientArea(ScreenArea, area.center(), desktop()).size(); |
401 | const QRect fsa = workspace()->clientArea(FullArea, geom.center(), desktop()); |
402 | const QSize cs = clientSize(); |
403 | int pseudo_max = Client::MaximizeRestore; |
404 | if (width() >= area.width()) |
405 | pseudo_max |= Client::MaximizeHorizontal; |
406 | if (height() >= area.height()) |
407 | pseudo_max |= Client::MaximizeVertical; |
408 | |
409 | // heuristics: |
410 | // if decorated client is smaller than the entire screen, the user might want to move it around (multiscreen) |
411 | // in this case, if the decorated client is bigger than the screen (+1), we don't take this as an |
412 | // attempt for maximization, but just constrain the size (the window simply wants to be bigger) |
413 | // NOTICE |
414 | // i intended a second check on cs < area.size() ("the managed client ("minus border") is smaller |
415 | // than the workspace") but gtk / gimp seems to store it's size including the decoration, |
416 | // thus a former maximized window wil become non-maximized |
417 | bool keepInFsArea = false; |
418 | if (width() < fsa.width() && (cs.width() > ss.width()+1)) { |
419 | pseudo_max &= ~Client::MaximizeHorizontal; |
420 | keepInFsArea = true; |
421 | } |
422 | if (height() < fsa.height() && (cs.height() > ss.height()+1)) { |
423 | pseudo_max &= ~Client::MaximizeVertical; |
424 | keepInFsArea = true; |
425 | } |
426 | |
427 | if (pseudo_max != Client::MaximizeRestore) { |
428 | maximize((MaximizeMode)pseudo_max); |
429 | // from now on, care about maxmode, since the maximization call will override mode for fix aspects |
430 | dontKeepInArea |= (max_mode == Client::MaximizeFull); |
431 | geom_restore = QRect(); // Use placement when unmaximizing ... |
432 | if (!(max_mode & Client::MaximizeVertical)) { |
433 | geom_restore.setY(y()); // ...but only for horizontal direction |
434 | geom_restore.setHeight(height()); |
435 | } |
436 | if (!(max_mode & Client::MaximizeHorizontal)) { |
437 | geom_restore.setX(x()); // ...but only for vertical direction |
438 | geom_restore.setWidth(width()); |
439 | } |
440 | } |
441 | if (keepInFsArea) |
442 | keepInArea(fsa, partial_keep_in_area); |
443 | } |
444 | } |
445 | |
446 | if ((!isSpecialWindow() || isToolbar()) && isMovable() && !dontKeepInArea) |
447 | keepInArea(area, partial_keep_in_area); |
448 | |
449 | updateShape(); |
450 | |
451 | // CT: Extra check for stupid jdk 1.3.1. But should make sense in general |
452 | // if client has initial state set to Iconic and is transient with a parent |
453 | // window that is not Iconic, set init_state to Normal |
454 | if (init_minimize && isTransient()) { |
455 | ClientList mainclients = mainClients(); |
456 | for (ClientList::ConstIterator it = mainclients.constBegin(); |
457 | it != mainclients.constEnd(); |
458 | ++it) |
459 | if ((*it)->isShown(true)) |
460 | init_minimize = false; // SELI TODO: Even e.g. for NET::Utility? |
461 | } |
462 | // If a dialog is shown for minimized window, minimize it too |
463 | if (!init_minimize && isTransient() && mainClients().count() > 0 && !workspace()->sessionSaving()) { |
464 | bool visible_parent = false; |
465 | // Use allMainClients(), to include also main clients of group transients |
466 | // that have been optimized out in Client::checkGroupTransients() |
467 | ClientList mainclients = allMainClients(); |
468 | for (ClientList::ConstIterator it = mainclients.constBegin(); |
469 | it != mainclients.constEnd(); |
470 | ++it) |
471 | if ((*it)->isShown(true)) |
472 | visible_parent = true; |
473 | if (!visible_parent) { |
474 | init_minimize = true; |
475 | demandAttention(); |
476 | } |
477 | } |
478 | |
479 | if (init_minimize) |
480 | minimize(true); // No animation |
481 | |
482 | // Other settings from the previous session |
483 | if (session) { |
484 | // Session restored windows are not considered to be new windows WRT rules, |
485 | // I.e. obey only forcing rules |
486 | setKeepAbove(session->keepAbove); |
487 | setKeepBelow(session->keepBelow); |
488 | setSkipTaskbar(session->skipTaskbar, true); |
489 | setSkipPager(session->skipPager); |
490 | setSkipSwitcher(session->skipSwitcher); |
491 | setShade(session->shaded ? ShadeNormal : ShadeNone); |
492 | setOpacity(session->opacity); |
493 | geom_restore = session->restore; |
494 | if (session->maximized != MaximizeRestore) { |
495 | maximize(MaximizeMode(session->maximized)); |
496 | } |
497 | if (session->fullscreen == FullScreenHack) |
498 | ; // Nothing, this should be already set again above |
499 | else if (session->fullscreen != FullScreenNone) { |
500 | setFullScreen(true, false); |
501 | geom_fs_restore = session->fsrestore; |
502 | } |
503 | } else { |
504 | // Window may want to be maximized |
505 | // done after checking that the window isn't larger than the workarea, so that |
506 | // the restore geometry from the checks above takes precedence, and window |
507 | // isn't restored larger than the workarea |
508 | MaximizeMode maxmode = static_cast<MaximizeMode>( |
509 | ((info->state() & NET::MaxVert) ? MaximizeVertical : 0) | |
510 | ((info->state() & NET::MaxHoriz) ? MaximizeHorizontal : 0)); |
511 | MaximizeMode forced_maxmode = rules()->checkMaximize(maxmode, !isMapped); |
512 | |
513 | // Either hints were set to maximize, or is forced to maximize, |
514 | // or is forced to non-maximize and hints were set to maximize |
515 | if (forced_maxmode != MaximizeRestore || maxmode != MaximizeRestore) |
516 | maximize(forced_maxmode); |
517 | |
518 | // Read other initial states |
519 | setShade(rules()->checkShade(info->state() & NET::Shaded ? ShadeNormal : ShadeNone, !isMapped)); |
520 | setKeepAbove(rules()->checkKeepAbove(info->state() & NET::KeepAbove, !isMapped)); |
521 | setKeepBelow(rules()->checkKeepBelow(info->state() & NET::KeepBelow, !isMapped)); |
522 | setSkipTaskbar(rules()->checkSkipTaskbar(info->state() & NET::SkipTaskbar, !isMapped), true); |
523 | setSkipPager(rules()->checkSkipPager(info->state() & NET::SkipPager, !isMapped)); |
524 | setSkipSwitcher(rules()->checkSkipSwitcher(false, !isMapped)); |
525 | if (info->state() & NET::DemandsAttention) |
526 | demandAttention(); |
527 | if (info->state() & NET::Modal) |
528 | setModal(true); |
529 | if (fullscreen_mode != FullScreenHack) |
530 | setFullScreen(rules()->checkFullScreen(info->state() & NET::FullScreen, !isMapped), false); |
531 | } |
532 | |
533 | updateAllowedActions(true); |
534 | |
535 | // Set initial user time directly |
536 | m_userTime = readUserTimeMapTimestamp(asn_valid ? &asn_id : NULL, asn_valid ? &asn_data : NULL, session); |
537 | group()->updateUserTime(m_userTime); // And do what Client::updateUserTime() does |
538 | |
539 | // This should avoid flicker, because real restacking is done |
540 | // only after manage() finishes because of blocking, but the window is shown sooner |
541 | XLowerWindow(display(), frameId()); |
542 | if (session && session->stackingOrder != -1) { |
543 | sm_stacking_order = session->stackingOrder; |
544 | workspace()->restoreSessionStackingOrder(this); |
545 | } |
546 | |
547 | if (compositing()) |
548 | // Sending ConfigureNotify is done when setting mapping state below, |
549 | // Getting the first sync response means window is ready for compositing |
550 | sendSyncRequest(); |
551 | else |
552 | ready_for_painting = true; // set to true in case compositing is turned on later. bug #160393 |
553 | |
554 | if (isShown(true)) { |
555 | bool allow; |
556 | if (session) |
557 | allow = session->active && |
558 | (!workspace()->wasUserInteraction() || workspace()->activeClient() == NULL || |
559 | workspace()->activeClient()->isDesktop()); |
560 | else |
561 | allow = workspace()->allowClientActivation(this, userTime(), false); |
562 | |
563 | if (!(isMapped || session)) { |
564 | if (workspace()->sessionSaving()) { |
565 | /* |
566 | * If we get a new window during session saving, we assume it's some 'save file?' dialog |
567 | * which the user really needs to see (to know why logout's stalled). |
568 | * |
569 | * Given the current session management protocol, I can't see a nicer way of doing this. |
570 | * Someday I'd like to see a protocol that tells the windowmanager who's doing SessionInteract. |
571 | */ |
572 | needsSessionInteract = true; |
573 | //show the parent too |
574 | ClientList mainclients = mainClients(); |
575 | for (ClientList::ConstIterator it = mainclients.constBegin(); |
576 | it != mainclients.constEnd(); ++it) { |
577 | (*it)->setSessionInteract(true); |
578 | (*it)->unminimize(); |
579 | } |
580 | } else if (allow) { |
581 | // also force if activation is allowed |
582 | if (!isOnCurrentDesktop() && options->focusPolicyIsReasonable()) { |
583 | VirtualDesktopManager::self()->setCurrent(desktop()); |
584 | } |
585 | /*if (!isOnCurrentActivity()) { |
586 | workspace()->setCurrentActivity( activities().first() ); |
587 | } FIXME no such method*/ |
588 | } |
589 | } |
590 | |
591 | resetShowingDesktop(options->isShowDesktopIsMinimizeAll()); |
592 | |
593 | if (isOnCurrentDesktop() && !isMapped && !allow && (!session || session->stackingOrder < 0)) |
594 | workspace()->restackClientUnderActive(this); |
595 | |
596 | updateVisibility(); |
597 | |
598 | if (!isMapped) { |
599 | if (allow && isOnCurrentDesktop()) { |
600 | if (!isSpecialWindow()) |
601 | if (options->focusPolicyIsReasonable() && wantsTabFocus()) |
602 | workspace()->requestFocus(this); |
603 | } else if (!session && !isSpecialWindow()) |
604 | demandAttention(); |
605 | } |
606 | } else |
607 | updateVisibility(); |
608 | assert(mapping_state != Withdrawn); |
609 | m_managed = true; |
610 | blockGeometryUpdates(false); |
611 | |
612 | if (m_userTime == XCB_TIME_CURRENT_TIME || m_userTime == -1U) { |
613 | // No known user time, set something old |
614 | m_userTime = xTime() - 1000000; |
615 | if (m_userTime == XCB_TIME_CURRENT_TIME || m_userTime == -1U) // Let's be paranoid |
616 | m_userTime = xTime() - 1000000 + 10; |
617 | } |
618 | |
619 | //sendSyntheticConfigureNotify(); // Done when setting mapping state |
620 | |
621 | delete session; |
622 | |
623 | ungrabXServer(); |
624 | |
625 | client_rules.discardTemporary(); |
626 | applyWindowRules(); // Just in case |
627 | RuleBook::self()->discardUsed(this, false); // Remove ApplyNow rules |
628 | updateWindowRules(Rules::All); // Was blocked while !isManaged() |
629 | |
630 | updateCompositeBlocking(true); |
631 | |
632 | // TODO: there's a small problem here - isManaged() depends on the mapping state, |
633 | // but this client is not yet in Workspace's client list at this point, will |
634 | // be only done in addClient() |
635 | emit clientManaging(this); |
636 | return true; |
637 | } |
638 | |
639 | // Called only from manage() |
640 | void Client::embedClient(xcb_window_t w, const XWindowAttributes& attr) |
641 | { |
642 | assert(m_client == XCB_WINDOW_NONE); |
643 | assert(frameId() == XCB_WINDOW_NONE); |
644 | assert(m_wrapper == XCB_WINDOW_NONE); |
645 | m_client = w; |
646 | |
647 | const xcb_visualid_t visualid = XVisualIDFromVisual(attr.visual); |
648 | const uint32_t zero_value = 0; |
649 | |
650 | xcb_connection_t *conn = connection(); |
651 | |
652 | // We don't want the window to be destroyed when we quit |
653 | xcb_change_save_set(conn, XCB_SET_MODE_INSERT, m_client); |
654 | |
655 | xcb_change_window_attributes(conn, m_client, XCB_CW_EVENT_MASK, &zero_value); |
656 | xcb_unmap_window(conn, m_client); |
657 | xcb_configure_window(conn, m_client, XCB_CONFIG_WINDOW_BORDER_WIDTH, &zero_value); |
658 | |
659 | // Note: These values must match the order in the xcb_cw_t enum |
660 | const uint32_t cw_values[] = { |
661 | 0, // back_pixmap |
662 | 0, // border_pixel |
663 | static_cast<uint32_t>(attr.colormap), // colormap |
664 | Cursor::x11Cursor(Qt::ArrowCursor) |
665 | }; |
666 | |
667 | const uint32_t cw_mask = XCB_CW_BACK_PIXMAP | XCB_CW_BORDER_PIXEL | |
668 | XCB_CW_COLORMAP | XCB_CW_CURSOR; |
669 | |
670 | const uint32_t common_event_mask = XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE | |
671 | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | |
672 | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | |
673 | XCB_EVENT_MASK_BUTTON_MOTION | XCB_EVENT_MASK_POINTER_MOTION | |
674 | XCB_EVENT_MASK_KEYMAP_STATE | |
675 | XCB_EVENT_MASK_FOCUS_CHANGE | |
676 | XCB_EVENT_MASK_EXPOSURE | |
677 | XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT; |
678 | |
679 | const uint32_t frame_event_mask = common_event_mask | XCB_EVENT_MASK_PROPERTY_CHANGE; |
680 | const uint32_t wrapper_event_mask = common_event_mask | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY; |
681 | |
682 | const uint32_t client_event_mask = XCB_EVENT_MASK_FOCUS_CHANGE | XCB_EVENT_MASK_PROPERTY_CHANGE | |
683 | XCB_EVENT_MASK_COLOR_MAP_CHANGE | |
684 | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | |
685 | XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE; |
686 | |
687 | // Create the frame window |
688 | xcb_window_t frame = xcb_generate_id(conn); |
689 | xcb_create_window(conn, attr.depth, frame, rootWindow(), 0, 0, 1, 1, 0, |
690 | XCB_WINDOW_CLASS_INPUT_OUTPUT, visualid, cw_mask, cw_values); |
691 | |
692 | setWindowHandles(m_client, frame); |
693 | |
694 | // Create the wrapper window |
695 | xcb_window_t wrapperId = xcb_generate_id(conn); |
696 | xcb_create_window(conn, attr.depth, wrapperId, frame, 0, 0, 1, 1, 0, |
697 | XCB_WINDOW_CLASS_INPUT_OUTPUT, visualid, cw_mask, cw_values); |
698 | m_wrapper.reset(wrapperId); |
699 | |
700 | xcb_reparent_window(conn, m_client, m_wrapper, 0, 0); |
701 | |
702 | // We could specify the event masks when we create the windows, but the original |
703 | // Xlib code didn't. Let's preserve that behavior here for now so we don't end up |
704 | // receiving any unexpected events from the wrapper creation or the reparenting. |
705 | xcb_change_window_attributes(conn, frame, XCB_CW_EVENT_MASK, &frame_event_mask); |
706 | xcb_change_window_attributes(conn, m_wrapper, XCB_CW_EVENT_MASK, &wrapper_event_mask); |
707 | xcb_change_window_attributes(conn, m_client, XCB_CW_EVENT_MASK, &client_event_mask); |
708 | |
709 | updateMouseGrab(); |
710 | } |
711 | |
712 | // To accept "mainwindow#1" to "mainwindow#2" |
713 | static QByteArray truncatedWindowRole(QByteArray a) |
714 | { |
715 | int i = a.indexOf('#'); |
716 | if (i == -1) |
717 | return a; |
718 | QByteArray b(a); |
719 | b.truncate(i); |
720 | return b; |
721 | } |
722 | |
723 | Client* Client::findAutogroupCandidate() const |
724 | { |
725 | // Attempt to find a similar window to the input. If we find multiple possibilities that are in |
726 | // different groups then ignore all of them. This function is for automatic window grouping. |
727 | Client *found = NULL; |
728 | |
729 | // See if the window has a group ID to match with |
730 | QString wGId = rules()->checkAutogroupById(QString()); |
731 | if (!wGId.isEmpty()) { |
732 | foreach (Client *c, workspace()->clientList()) { |
733 | if (activities() != c->activities()) |
734 | continue; // don't cross activities |
735 | if (wGId == c->rules()->checkAutogroupById(QString())) { |
736 | if (found && found->tabGroup() != c->tabGroup()) { // We've found two, ignore both |
737 | found = NULL; |
738 | break; // Continue to the next test |
739 | } |
740 | found = c; |
741 | } |
742 | } |
743 | if (found) |
744 | return found; |
745 | } |
746 | |
747 | // If this is a transient window don't take a guess |
748 | if (isTransient()) |
749 | return NULL; |
750 | |
751 | // If we don't have an ID take a guess |
752 | if (rules()->checkAutogrouping(options->isAutogroupSimilarWindows())) { |
753 | QByteArray wRole = truncatedWindowRole(windowRole()); |
754 | foreach (Client *c, workspace()->clientList()) { |
755 | if (desktop() != c->desktop() || activities() != c->activities()) |
756 | continue; |
757 | QByteArray wRoleB = truncatedWindowRole(c->windowRole()); |
758 | if (resourceClass() == c->resourceClass() && // Same resource class |
759 | wRole == wRoleB && // Same window role |
760 | c->isNormalWindow()) { // Normal window TODO: Can modal windows be "normal"? |
761 | if (found && found->tabGroup() != c->tabGroup()) // We've found two, ignore both |
762 | return NULL; |
763 | found = c; |
764 | } |
765 | } |
766 | } |
767 | |
768 | return found; |
769 | } |
770 | |
771 | } // namespace |
772 | |