/******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ // This file contains things relevant to handling incoming events. #include "client.h" #include #include #include #include "notifications.h" #include #include "rules.h" #include "group.h" #include "scripting/workspaceproxy.h" namespace KWin { /** * Manages the clients. This means handling the very first maprequest: * reparenting, initial geometry, initial state, placement, etc. * Returns false if KWin is not going to manage this window. */ bool Client::manage( Window w, bool isMapped ) { StackingUpdatesBlocker stacking_blocker( workspace() ); //Scripting call. Does not use a signal/slot mechanism //as ensuring connections was a bit difficult between //so many clients and the workspace SWrapper::WorkspaceProxy* ws_wrap = SWrapper::WorkspaceProxy::instance(); if(ws_wrap != 0) { ws_wrap->sl_clientManaging(this); } grabXServer(); XWindowAttributes attr; if( !XGetWindowAttributes( display(), w, &attr )) { ungrabXServer(); return false; } // From this place on, manage() must not return false block_geometry_updates = 1; pending_geometry_update = PendingGeometryForced; // Force update when finishing with geometry changes embedClient( w, attr ); vis = attr.visual; bit_depth = attr.depth; // SELI TODO: Order all these things in some sane manner bool init_minimize = false; XWMHints* hints = XGetWMHints( display(), w ); if( hints && ( hints->flags & StateHint ) && hints->initial_state == IconicState ) init_minimize = true; if( hints ) XFree( hints ); if( isMapped ) init_minimize = false; // If it's already mapped, ignore hint unsigned long properties[2]; properties[WinInfo::PROTOCOLS] = NET::WMDesktop | NET::WMState | NET::WMWindowType | NET::WMStrut | NET::WMName | NET::WMIconGeometry | NET::WMIcon | NET::WMPid | NET::WMIconName | 0; properties[WinInfo::PROTOCOLS2] = NET::WM2UserTime | NET::WM2StartupId | NET::WM2ExtendedStrut | NET::WM2Opacity | NET::WM2FullscreenMonitors | NET::WM2FrameOverlap | 0; info = new WinInfo( this, display(), client, rootWindow(), properties, 2 ); cmap = attr.colormap; getResourceClass(); getWindowRole(); getWmClientLeader(); getWmClientMachine(); getSyncCounter(); // First only read the caption text, so that setupWindowRules() can use it for matching, // and only then really set the caption using setCaption(), which checks for duplicates etc. // and also relies on rules already existing cap_normal = readName(); setupWindowRules( false ); ignore_focus_stealing = options->checkIgnoreFocusStealing( this ); // TODO: Change to rules setCaption( cap_normal, true ); if( Extensions::shapeAvailable() ) XShapeSelectInput( display(), window(), ShapeNotifyMask ); detectShape( window() ); detectNoBorder(); fetchIconicName(); getWMHints(); // Needs to be done before readTransient() because of reading the group modal = ( info->state() & NET::Modal ) != 0; // Needs to be valid before handling groups readTransient(); getIcons(); getWindowProtocols(); getWmNormalHints(); // Get xSizeHint getMotifHints(); // TODO: Try to obey all state information from info->state() original_skip_taskbar = skip_taskbar = ( info->state() & NET::SkipTaskbar ) != 0; skip_pager = ( info->state() & NET::SkipPager ) != 0; setupCompositing(); KStartupInfoId asn_id; KStartupInfoData asn_data; bool asn_valid = workspace()->checkStartupNotification( window(), asn_id, asn_data ); workspace()->updateClientLayer( this ); SessionInfo* session = workspace()->takeSessionInfo( this ); if( session ) { init_minimize = session->minimized; noborder = session->noBorder; } setShortcut( rules()->checkShortcut( session ? session->shortcut : QString(), true )); init_minimize = rules()->checkMinimize( init_minimize, !isMapped ); noborder = rules()->checkNoBorder( noborder, !isMapped ); checkActivities(); // Initial desktop placement if( session ) { desk = session->desktop; if( session->onAllDesktops ) desk = NET::OnAllDesktops; } else { // If this window is transient, ensure that it is opened on the // same window as its parent. this is necessary when an application // starts up on a different desktop than is currently displayed //FIXME do the same for activities too if( isTransient() ) { ClientList mainclients = mainClients(); bool on_current = false; bool on_all = false; Client* maincl = NULL; // This is slightly duplicated from Placement::placeOnMainWindow() for( ClientList::ConstIterator it = mainclients.constBegin(); it != mainclients.constEnd(); ++it ) { if( mainclients.count() > 1 && (*it)->isSpecialWindow() ) continue; // Don't consider toolbars etc when placing maincl = *it; if( (*it)->isOnCurrentDesktop() ) on_current = true; if( (*it)->isOnAllDesktops() ) on_all = true; } if( on_all ) desk = NET::OnAllDesktops; else if( on_current ) desk = workspace()->currentDesktop(); else if( maincl != NULL ) desk = maincl->desktop(); } if( info->desktop() ) desk = info->desktop(); // Window had the initial desktop property, force it if( desktop() == 0 && asn_valid && asn_data.desktop() != 0 ) desk = asn_data.desktop(); } if( desk == 0 ) // Assume window wants to be visible on the current desktop desk = workspace()->currentDesktop(); desk = rules()->checkDesktop( desk, !isMapped ); if( desk != NET::OnAllDesktops ) // Do range check desk = qMax( 1, qMin( workspace()->numberOfDesktops(), desk )); info->setDesktop( desk ); workspace()->updateOnAllDesktopsOfTransients( this ); // SELI TODO //onAllDesktopsChange(); // Decoration doesn't exist here yet QRect geom( attr.x, attr.y, attr.width, attr.height ); bool placementDone = false; if ( session ) geom = session->geometry; QRect area; bool partial_keep_in_area = isMapped || session; if( isMapped || session ) area = workspace()->clientArea( FullArea, geom.center(), desktop() ); else if( options->xineramaPlacementEnabled ) { int screen = options->xineramaPlacementScreen; if( screen == -1 ) // Active screen screen = asn_data.xinerama() == -1 ? workspace()->activeScreen() : asn_data.xinerama(); area = workspace()->clientArea( PlacementArea, workspace()->screenGeometry( screen ).center(), desktop()); } else area = workspace()->clientArea( PlacementArea, cursorPos(), desktop() ); if( int type = checkFullScreenHack( geom )) { fullscreen_mode = FullScreenHack; if( rules()->checkStrictGeometry( false )) { geom = type == 2 // 1 = It's xinerama-aware fullscreen hack, 2 = It's full area ? workspace()->clientArea( FullArea, geom.center(), desktop() ) : workspace()->clientArea( ScreenArea, geom.center(), desktop() ); } else geom = workspace()->clientArea( FullScreenArea, geom.center(), desktop() ); placementDone = true; } if( isDesktop() ) // KWin doesn't manage desktop windows placementDone = true; bool usePosition = false; if ( isMapped || session || placementDone ) placementDone = true; // Use geometry else if( isTransient() && !isUtility() && !isDialog() && !isSplash() ) usePosition = true; else if( isTransient() && !hasNETSupport() ) usePosition = true; else if( isDialog() && hasNETSupport() ) { // If the dialog is actually non-NETWM transient window, don't try to apply placement to it, // it breaks with too many things (xmms, display) if( mainClients().count() >= 1 ) { #if 1 // #78082 - Ok, it seems there are after all some cases when an application has a good // reason to specify a position for its dialog. Too bad other WMs have never bothered // with placement for dialogs, so apps always specify positions for their dialogs, // including such silly positions like always centered on the screen or under mouse. // Using ignoring requested position in window-specific settings helps, and now // there's also _NET_WM_FULL_PLACEMENT. usePosition = true; #else ; // Force using placement policy #endif } else usePosition = true; } else if( isSplash() ) ; // Force using placement policy else usePosition = true; if( !rules()->checkIgnoreGeometry( !usePosition )) { bool ignorePPosition = options->ignorePositionClasses.contains( QString::fromLatin1( resourceClass() )); if((( xSizeHint.flags & PPosition ) && !ignorePPosition ) || ( xSizeHint.flags & USPosition )) { placementDone = true; // Disobey xinerama placement option for now (#70943) area = workspace()->clientArea( PlacementArea, geom.center(), desktop() ); } } //if( true ) // Size is always obeyed for now, only with constraints applied // if(( xSizeHint.flags & USSize ) || ( xSizeHint.flags & PSize )) // { // // Keep in mind that we now actually have a size :-) // } if( xSizeHint.flags & PMaxSize ) geom.setSize( geom.size().boundedTo( rules()->checkMaxSize( QSize( xSizeHint.max_width, xSizeHint.max_height )))); if( xSizeHint.flags & PMinSize ) geom.setSize( geom.size().expandedTo( rules()->checkMinSize( QSize( xSizeHint.min_width, xSizeHint.min_height )))); if( isMovable() && ( geom.x() > area.right() || geom.y() > area.bottom() )) placementDone = false; // Weird, do not trust. if( placementDone ) move( geom.x(), geom.y() ); // Before gravitating // Create client group if the window will have a decoration bool dontKeepInArea = false; if( !noBorder() ) { client_group = NULL; // Automatically add to previous groups on session restore if( session && session->clientGroupClient && session->clientGroupClient != this ) session->clientGroupClient->clientGroup()->add( this, -1, true ); else if( isMapped ) // If the window is already mapped (Restarted KWin) add any windows that already have the // same geometry to the same client group. (May incorrectly handle maximized windows) foreach( ClientGroup* group, workspace()->clientGroups ) if( geom == QRect( group->visible()->pos(), group->visible()->clientSize() ) && desk == group->visible()->desktop() && group->visible()->maximizeMode() != MaximizeFull ) { group->add( this, -1, true ); break; } if( !client_group && !isMapped && !session ) { // Attempt to automatically group similar windows const Client* similar = workspace()->findSimilarClient( this ); if( similar && similar->clientGroup() && !similar->noBorder() ) { geom = QRect( similar->pos() + similar->clientPos(), similar->clientSize() ); updateDecoration( false ); similar->clientGroup()->add( this, -1, rules()->checkAutogroupInForeground( options->autogroupInForeground )); // Don't move entire group geom = QRect( similar->pos() + similar->clientPos(), similar->clientSize() ); placementDone = true; dontKeepInArea = true; } } if( !client_group ) client_group = new ClientGroup( this ); } updateDecoration( false ); // Also gravitates // TODO: Is CentralGravity right here, when resizing is done after gravitating? plainResize( rules()->checkSize( sizeForClientSize( geom.size() ), !isMapped )); QPoint forced_pos = rules()->checkPosition( invalidPoint, !isMapped ); if( forced_pos != invalidPoint ) { move( forced_pos ); placementDone = true; // Don't keep inside workarea if the window has specially configured position partial_keep_in_area = true; area = workspace()->clientArea( FullArea, geom.center(), desktop() ); } if( !placementDone ) { // Placement needs to be after setting size workspace()->place( this, area ); placementDone = true; } if(( !isSpecialWindow() || isToolbar() ) && isMovable() && !dontKeepInArea ) keepInArea( area, partial_keep_in_area ); updateShape(); // CT: Extra check for stupid jdk 1.3.1. But should make sense in general // if client has initial state set to Iconic and is transient with a parent // window that is not Iconic, set init_state to Normal if( init_minimize && isTransient() ) { ClientList mainclients = mainClients(); for( ClientList::ConstIterator it = mainclients.constBegin(); it != mainclients.constEnd(); ++it ) if( (*it)->isShown( true )) init_minimize = false; // SELI TODO: Even e.g. for NET::Utility? } // If a dialog is shown for minimized window, minimize it too if( !init_minimize && isTransient() && mainClients().count() > 0 ) { bool visible_parent = false; // Use allMainClients(), to include also main clients of group transients // that have been optimized out in Client::checkGroupTransients() ClientList mainclients = allMainClients(); for( ClientList::ConstIterator it = mainclients.constBegin(); it != mainclients.constEnd(); ++it ) if( (*it)->isShown( true )) visible_parent = true; if( !visible_parent ) { init_minimize = true; demandAttention(); } } if( init_minimize ) minimize( true ); // No animation // SELI TODO: This seems to be mainly for kstart and ksystraycmd // probably should be replaced by something better bool doNotShow = false; if ( workspace()->isNotManaged( caption() )) doNotShow = true; // Other settings from the previous session if( session ) { // Session restored windows are not considered to be new windows WRT rules, // I.e. obey only forcing rules setKeepAbove( session->keepAbove ); setKeepBelow( session->keepBelow ); setSkipTaskbar( session->skipTaskbar, true ); setSkipPager( session->skipPager ); setSkipSwitcher( session->skipSwitcher ); setShade( session->shaded ? ShadeNormal : ShadeNone ); setOpacity( session->opacity ); if( session->maximized != MaximizeRestore ) { maximize( MaximizeMode( session->maximized )); geom_restore = session->restore; } if( session->fullscreen == FullScreenHack ) ; // Nothing, this should be already set again above else if( session->fullscreen != FullScreenNone ) { setFullScreen( true, false ); geom_fs_restore = session->fsrestore; } } else { geom_restore = geometry(); // Remember restore geometry if( isMaximizable() && ( width() >= area.width() || height() >= area.height() )) { // Window is too large for the screen, maximize in the // directions necessary if( width() >= area.width() && height() >= area.height() ) { maximize( Client::MaximizeFull ); geom_restore = QRect(); // Use placement when unmaximizing } else if( width() >= area.width() ) { maximize( Client::MaximizeHorizontal ); geom_restore = QRect(); // Use placement when unmaximizing geom_restore.setY( y() ); // But only for horizontal direction geom_restore.setHeight( height() ); } else if( height() >= area.height() ) { maximize( Client::MaximizeVertical ); geom_restore = QRect(); // Use placement when unmaximizing geom_restore.setX( x() ); // But only for vertical direction geom_restore.setWidth( width() ); } } // Window may want to be maximized // done after checking that the window isn't larger than the workarea, so that // the restore geometry from the checks above takes precedence, and window // isn't restored larger than the workarea MaximizeMode maxmode = static_cast( (( info->state() & NET::MaxVert ) ? MaximizeVertical : 0 ) | (( info->state() & NET::MaxHoriz ) ? MaximizeHorizontal : 0 )); MaximizeMode forced_maxmode = rules()->checkMaximize( maxmode, !isMapped ); // Either hints were set to maximize, or is forced to maximize, // or is forced to non-maximize and hints were set to maximize if( forced_maxmode != MaximizeRestore || maxmode != MaximizeRestore ) maximize( forced_maxmode ); // Read other initial states setShade( rules()->checkShade( info->state() & NET::Shaded ? ShadeNormal : ShadeNone, !isMapped )); setKeepAbove( rules()->checkKeepAbove( info->state() & NET::KeepAbove, !isMapped )); setKeepBelow( rules()->checkKeepBelow( info->state() & NET::KeepBelow, !isMapped )); setSkipTaskbar( rules()->checkSkipTaskbar( info->state() & NET::SkipTaskbar, !isMapped ), true ); setSkipPager( rules()->checkSkipPager( info->state() & NET::SkipPager, !isMapped )); setSkipSwitcher( rules()->checkSkipSwitcher( false, !isMapped )); if( info->state() & NET::DemandsAttention ) demandAttention(); if( info->state() & NET::Modal ) setModal( true ); if( fullscreen_mode != FullScreenHack && isFullScreenable() ) setFullScreen( rules()->checkFullScreen( info->state() & NET::FullScreen, !isMapped ), false ); } updateAllowedActions( true ); // Set initial user time directly user_time = readUserTimeMapTimestamp( asn_valid ? &asn_id : NULL, asn_valid ? &asn_data : NULL, session ); group()->updateUserTime( user_time ); // And do what Client::updateUserTime() does if( isTopMenu()) // They're shown in Workspace::addClient() if their mainwindow hideClient( true ); // Is the active one // This should avoid flicker, because real restacking is done // only after manage() finishes because of blocking, but the window is shown sooner XLowerWindow( display(), frameId() ); if( session && session->stackingOrder != -1 ) { sm_stacking_order = session->stackingOrder; workspace()->restoreSessionStackingOrder( this ); } if( compositing() ) // Sending ConfigureNotify is done when setting mapping state below, // Getting the first sync response means window is ready for compositing sendSyncRequest(); if( isShown( true ) && !doNotShow ) { if( isDialog() ) Notify::raise( Notify::TransNew ); if( isNormalWindow() ) Notify::raise( Notify::New ); bool allow; if( session ) allow = session->active && ( !workspace()->wasUserInteraction() || workspace()->activeClient() == NULL || workspace()->activeClient()->isDesktop() ); else allow = workspace()->allowClientActivation( this, userTime(), false ); // If session saving, force showing new windows (i.e. "save file?" dialogs etc.) // also force if activation is allowed if( !isOnCurrentDesktop() && !isMapped && !session && ( allow || workspace()->sessionSaving() )) workspace()->setCurrentDesktop( desktop() ); bool belongs_to_desktop = false; for( ClientList::ConstIterator it = group()->members().constBegin(); it != group()->members().constEnd(); ++it ) if( (*it)->isDesktop() ) { belongs_to_desktop = true; break; } if( !belongs_to_desktop && workspace()->showingDesktop() ) workspace()->resetShowingDesktop( options->showDesktopIsMinimizeAll ); if( isOnCurrentDesktop() && !isMapped && !allow && ( !session || session->stackingOrder < 0 )) workspace()->restackClientUnderActive( this ); updateVisibility(); if( !isMapped ) { if( allow && isOnCurrentDesktop() ) { if( !isSpecialWindow() ) if ( options->focusPolicyIsReasonable() && wantsTabFocus() ) workspace()->requestFocus( this ); } else if( !session && !isSpecialWindow()) demandAttention(); } } else if( !doNotShow ) // if( !isShown( true ) && !doNotShow ) updateVisibility(); else // doNotShow hideClient( true ); // SELI HACK !!! assert( mapping_state != Withdrawn ); blockGeometryUpdates( false ); if( user_time == CurrentTime || user_time == -1U ) { // No known user time, set something old user_time = xTime() - 1000000; if( user_time == CurrentTime || user_time == -1U ) // Let's be paranoid user_time = xTime() - 1000000 + 10; } //sendSyntheticConfigureNotify(); // Done when setting mapping state delete session; ungrabXServer(); client_rules.discardTemporary(); applyWindowRules(); // Just in case workspace()->discardUsedWindowRules( this, false ); // Remove ApplyNow rules updateWindowRules(); // Was blocked while !isManaged() // TODO: there's a small problem here - isManaged() depends on the mapping state, // but this client is not yet in Workspace's client list at this point, will // be only done in addClient() return true; } // Called only from manage() void Client::embedClient( Window w, const XWindowAttributes& attr ) { assert( client == None ); assert( frameId() == None ); assert( wrapper == None ); client = w; // We don't want the window to be destroyed when we are destroyed XAddToSaveSet( display(), client ); XSelectInput( display(), client, NoEventMask ); XUnmapWindow( display(), client ); XWindowChanges wc; // Set the border width to 0 wc.border_width = 0; // TODO: Possibly save this, and also use it for initial configuring of the window XConfigureWindow( display(), client, CWBorderWidth, &wc ); XSetWindowAttributes swa; swa.colormap = attr.colormap; swa.background_pixmap = None; swa.border_pixel = 0; Window frame = XCreateWindow( display(), rootWindow(), 0, 0, 1, 1, 0, attr.depth, InputOutput, attr.visual, CWColormap | CWBackPixmap | CWBorderPixel, &swa ); setWindowHandles( client, frame ); wrapper = XCreateWindow( display(), frame, 0, 0, 1, 1, 0, attr.depth, InputOutput, attr.visual, CWColormap | CWBackPixmap | CWBorderPixel, &swa ); XDefineCursor( display(), frame, QCursor( Qt::ArrowCursor ).handle() ); // Some apps are stupid and don't define their own cursor - set the arrow one for them XDefineCursor( display(), wrapper, QCursor( Qt::ArrowCursor ).handle() ); XReparentWindow( display(), client, wrapper, 0, 0 ); XSelectInput( display(), frame, KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | KeymapStateMask | ButtonMotionMask | PointerMotionMask | EnterWindowMask | LeaveWindowMask | FocusChangeMask | ExposureMask | PropertyChangeMask | StructureNotifyMask | SubstructureRedirectMask ); XSelectInput( display(), wrapper, ClientWinMask | SubstructureNotifyMask ); XSelectInput( display(), client, FocusChangeMask | PropertyChangeMask | ColormapChangeMask | EnterWindowMask | LeaveWindowMask | KeyPressMask | KeyReleaseMask ); updateMouseGrab(); } } // namespace