1/********************************************************************
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5Copyright (C) 1999, 2000 Matthias Ettrich <ettrich@kde.org>
6Copyright (C) 2003 Lubos Lunak <l.lunak@kde.org>
7
8This program is free software; you can redistribute it and/or modify
9it under the terms of the GNU General Public License as published by
10the Free Software Foundation; either version 2 of the License, or
11(at your option) any later version.
12
13This program is distributed in the hope that it will be useful,
14but WITHOUT ANY WARRANTY; without even the implied warranty of
15MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16GNU General Public License for more details.
17
18You should have received a copy of the GNU General Public License
19along 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
43namespace 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 */
51bool 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()
640void 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"
713static 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
723Client* 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