manage.cpp
00001 /***************************************************************** 00002 KWin - the KDE window manager 00003 This file is part of the KDE project. 00004 00005 Copyright (C) 1999, 2000 Matthias Ettrich <ettrich@kde.org> 00006 Copyright (C) 2003 Lubos Lunak <l.lunak@kde.org> 00007 00008 You can Freely distribute this program under the GNU General Public 00009 License. See the file "COPYING" for the exact licensing terms. 00010 ******************************************************************/ 00011 00012 /* 00013 00014 This file contains things relevant to handling incoming events. 00015 00016 */ 00017 00018 #include "client.h" 00019 00020 #include <kstartupinfo.h> 00021 #include <kglobal.h> 00022 #include <X11/extensions/shape.h> 00023 00024 #include "notifications.h" 00025 #include "rules.h" 00026 #include "group.h" 00027 00028 namespace KWinInternal 00029 { 00030 00036 bool Client::manage( Window w, bool isMapped ) 00037 { 00038 XWindowAttributes attr; 00039 if( !XGetWindowAttributes(qt_xdisplay(), w, &attr)) 00040 return false; 00041 00042 grabXServer(); 00043 00044 // from this place on, manage() mustn't return false 00045 postpone_geometry_updates = 1; 00046 pending_geometry_update = true; // force update when finishing with geometry changes 00047 00048 embedClient( w, attr ); 00049 00050 // SELI order all these things in some sane manner 00051 00052 bool init_minimize = false; 00053 XWMHints * hints = XGetWMHints(qt_xdisplay(), w ); 00054 if (hints && (hints->flags & StateHint) && hints->initial_state == IconicState) 00055 init_minimize = true; 00056 if (hints) 00057 XFree(hints); 00058 if( isMapped ) 00059 init_minimize = false; // if it's already mapped, ignore hint 00060 00061 unsigned long properties[ 2 ]; 00062 properties[ WinInfo::PROTOCOLS ] = 00063 NET::WMDesktop | 00064 NET::WMState | 00065 NET::WMWindowType | 00066 NET::WMStrut | 00067 NET::WMName | 00068 NET::WMIconGeometry | 00069 NET::WMIcon | 00070 NET::WMPid | 00071 NET::WMIconName | 00072 0; 00073 properties[ WinInfo::PROTOCOLS2 ] = 00074 NET::WM2UserTime | 00075 NET::WM2StartupId | 00076 NET::WM2ExtendedStrut | 00077 0; 00078 00079 info = new WinInfo( this, qt_xdisplay(), client, qt_xrootwin(), properties, 2 ); 00080 00081 cmap = attr.colormap; 00082 00083 XClassHint classHint; 00084 if ( XGetClassHint( qt_xdisplay(), client, &classHint ) ) 00085 { 00086 // Qt3.2 and older had this all lowercase, Qt3.3 capitalized resource class 00087 // force lowercase, so that workarounds listing resource classes still work 00088 resource_name = TQCString( classHint.res_name ).lower(); 00089 resource_class = TQCString( classHint.res_class ).lower(); 00090 XFree( classHint.res_name ); 00091 XFree( classHint.res_class ); 00092 } 00093 ignore_focus_stealing = options->checkIgnoreFocusStealing( this ); // TODO change to rules 00094 00095 window_role = staticWindowRole( w ); 00096 getWmClientLeader(); 00097 getWmClientMachine(); 00098 // first only read the caption text, so that setupWindowRules() can use it for matching, 00099 // and only then really set the caption using setCaption(), which checks for duplicates etc. 00100 // and also relies on rules already existing 00101 cap_normal = readName(); 00102 setupWindowRules( false ); 00103 setCaption( cap_normal, true ); 00104 00105 detectNoBorder(); 00106 detectShapable(); 00107 fetchIconicName(); 00108 getWMHints(); // needs to be done before readTransient() because of reading the group 00109 modal = ( info->state() & NET::Modal ) != 0; // needs to be valid before handling groups 00110 readTransient(); 00111 getIcons(); 00112 getWindowProtocols(); 00113 getWmNormalHints(); // get xSizeHint 00114 getMotifHints(); 00115 00116 // TODO try to obey all state information from info->state() 00117 00118 original_skip_taskbar = skip_taskbar = ( info->state() & NET::SkipTaskbar) != 0; 00119 skip_pager = ( info->state() & NET::SkipPager) != 0; 00120 00121 KStartupInfoId asn_id; 00122 KStartupInfoData asn_data; 00123 bool asn_valid = workspace()->checkStartupNotification( window(), asn_id, asn_data ); 00124 00125 workspace()->updateClientLayer( this ); 00126 00127 SessionInfo* session = workspace()->takeSessionInfo( this ); 00128 00129 if ( session ) 00130 { 00131 if ( session->minimized ) 00132 init_minimize = true; 00133 if( session->userNoBorder ) 00134 setUserNoBorder( true ); 00135 } 00136 00137 setShortcut( rules()->checkShortcut( session ? session->shortcut : TQString::null, true )); 00138 00139 init_minimize = rules()->checkMinimize( init_minimize, !isMapped ); 00140 if( rules()->checkNoBorder( false, !isMapped )) 00141 setUserNoBorder( true ); 00142 00143 checkAndSetInitialRuledOpacity(); 00144 00145 // initial desktop placement 00146 if ( session ) 00147 { 00148 desk = session->desktop; 00149 if( session->onAllDesktops ) 00150 desk = NET::OnAllDesktops; 00151 } 00152 else 00153 { 00154 // if this window is transient, ensure that it is opened on the 00155 // same window as its parent. this is necessary when an application 00156 // starts up on a different desktop than is currently displayed 00157 if( isTransient()) 00158 { 00159 ClientList mainclients = mainClients(); 00160 bool on_current = false; 00161 Client* maincl = NULL; 00162 // this is slightly duplicated from Placement::placeOnMainWindow() 00163 for( ClientList::ConstIterator it = mainclients.begin(); 00164 it != mainclients.end(); 00165 ++it ) 00166 { 00167 if( mainclients.count() > 1 && (*it)->isSpecialWindow()) 00168 continue; // don't consider toolbars etc when placing 00169 maincl = *it; 00170 if( (*it)->isOnCurrentDesktop()) 00171 on_current = true; 00172 } 00173 if( on_current ) 00174 desk = workspace()->currentDesktop(); 00175 else if( maincl != NULL ) 00176 desk = maincl->desktop(); 00177 } 00178 if ( info->desktop() ) 00179 desk = info->desktop(); // window had the initial desktop property, force it 00180 if( desktop() == 0 && asn_valid && asn_data.desktop() != 0 ) 00181 desk = asn_data.desktop(); 00182 } 00183 if ( desk == 0 ) // assume window wants to be visible on the current desktop 00184 desk = workspace()->currentDesktop(); 00185 desk = rules()->checkDesktop( desk, !isMapped ); 00186 if( desk != NET::OnAllDesktops ) // do range check 00187 desk = KMAX( 1, KMIN( workspace()->numberOfDesktops(), desk )); 00188 info->setDesktop( desk ); 00189 workspace()->updateOnAllDesktopsOfTransients( this ); // SELI 00190 // onAllDesktopsChange(); decoration doesn't exist here yet 00191 00192 TQRect geom( attr.x, attr.y, attr.width, attr.height ); 00193 bool placementDone = FALSE; 00194 00195 if ( session ) 00196 geom = session->geometry; 00197 00198 TQRect area; 00199 bool partial_keep_in_area = isMapped || session; 00200 if( isMapped || session ) 00201 area = workspace()->clientArea( FullArea, geom.center(), desktop()); 00202 else if( options->xineramaPlacementEnabled ) 00203 { 00204 int screen = options->xineramaPlacementScreen; 00205 if( screen == -1 ) // active screen 00206 screen = asn_data.xinerama() == -1 ? workspace()->activeScreen() : asn_data.xinerama(); 00207 area = workspace()->clientArea( PlacementArea, workspace()->screenGeometry( screen ).center(), desktop()); 00208 } 00209 else 00210 area = workspace()->clientArea( PlacementArea, TQCursor::pos(), desktop()); 00211 00212 if( int type = checkFullScreenHack( geom )) 00213 { 00214 fullscreen_mode = FullScreenHack; 00215 if( rules()->checkStrictGeometry( false )) 00216 { 00217 geom = type == 2 // 1 - it's xinerama-aware fullscreen hack, 2 - it's full area 00218 ? workspace()->clientArea( FullArea, geom.center(), desktop()) 00219 : workspace()->clientArea( ScreenArea, geom.center(), desktop()); 00220 } 00221 else 00222 geom = workspace()->clientArea( FullScreenArea, geom.center(), desktop()); 00223 placementDone = true; 00224 } 00225 00226 if ( isDesktop() ) 00227 { 00228 // desktops are treated slightly special 00229 geom = workspace()->clientArea( FullArea, geom.center(), desktop()); 00230 placementDone = true; 00231 } 00232 00233 bool usePosition = false; 00234 if ( isMapped || session || placementDone ) 00235 placementDone = true; // use geometry 00236 else if( isTransient() && !isUtility() && !isDialog() && !isSplash()) 00237 usePosition = true; 00238 else if( isTransient() && !hasNETSupport()) 00239 usePosition = true; 00240 else if( isDialog() && hasNETSupport()) 00241 // if the dialog is actually non-NETWM transient window, don't try to apply placement to it, 00242 // it breaks with too many things (xmms, display) 00243 { 00244 if( mainClients().count() >= 1 ) 00245 { 00246 #if 1 00247 // TODO #78082 - Ok, it seems there are after all some cases when an application has a good 00248 // reason to specify a position for its dialog. Too bad other WMs have never bothered 00249 // with placement for dialogs, so apps always specify positions for their dialogs, 00250 // including such silly positions like always centered on the screen or under mouse. 00251 // Using ignoring requested position in window-specific settings helps, but at least 00252 // for Qt apps this should work better. 00253 usePosition = true; 00254 #else 00255 ; // force using placement policy 00256 #endif 00257 } 00258 else 00259 usePosition = true; 00260 } 00261 else if( isSplash()) 00262 ; // force using placement policy 00263 else 00264 usePosition = true; 00265 if( !rules()->checkIgnoreGeometry( !usePosition )) 00266 { 00267 bool ignorePPosition = ( options->ignorePositionClasses.contains(TQString::fromLatin1(resourceClass()))); 00268 00269 if ( ( (xSizeHint.flags & PPosition) && !ignorePPosition ) || 00270 (xSizeHint.flags & USPosition) ) 00271 { 00272 placementDone = TRUE; 00273 // disobey xinerama placement option for now (#70943) 00274 area = workspace()->clientArea( PlacementArea, geom.center(), desktop()); 00275 } 00276 } 00277 if( true ) // size is always obeyed for now, only with constraints applied 00278 if ( (xSizeHint.flags & USSize) || (xSizeHint.flags & PSize) ) 00279 { 00280 // keep in mind that we now actually have a size :-) 00281 } 00282 00283 if (xSizeHint.flags & PMaxSize) 00284 geom.setSize( geom.size().boundedTo( 00285 rules()->checkMaxSize( TQSize(xSizeHint.max_width, xSizeHint.max_height ) ) ) ); 00286 if (xSizeHint.flags & PMinSize) 00287 geom.setSize( geom.size().expandedTo( 00288 rules()->checkMinSize( TQSize(xSizeHint.min_width, xSizeHint.min_height ) ) ) ); 00289 00290 if( isMovable()) 00291 { 00292 if( geom.x() > area.right() || geom.y() > area.bottom()) 00293 placementDone = FALSE; // weird, do not trust. 00294 } 00295 00296 if ( placementDone ) 00297 move( geom.x(), geom.y() ); // before gravitating 00298 00299 updateDecoration( false ); // also gravitates 00300 // TODO is CentralGravity right here, when resizing is done after gravitating? 00301 plainResize( rules()->checkSize( sizeForClientSize( geom.size()), !isMapped )); 00302 00303 TQPoint forced_pos = rules()->checkPosition( invalidPoint, !isMapped ); 00304 if( forced_pos != invalidPoint ) 00305 { 00306 move( forced_pos ); 00307 placementDone = true; 00308 // don't keep inside workarea if the window has specially configured position 00309 partial_keep_in_area = true; 00310 area = workspace()->clientArea( FullArea, geom.center(), desktop()); 00311 } 00312 if( !placementDone ) 00313 { // placement needs to be after setting size 00314 workspace()->place( this, area ); 00315 placementDone = TRUE; 00316 } 00317 00318 if(( !isSpecialWindow() || isToolbar()) && isMovable()) 00319 keepInArea( area, partial_keep_in_area ); 00320 00321 XShapeSelectInput( qt_xdisplay(), window(), ShapeNotifyMask ); 00322 is_shape = Shape::hasShape( window()); 00323 updateShape(); 00324 00325 //CT extra check for stupid jdk 1.3.1. But should make sense in general 00326 // if client has initial state set to Iconic and is transient with a parent 00327 // window that is not Iconic, set init_state to Normal 00328 if( init_minimize && isTransient()) 00329 { 00330 ClientList mainclients = mainClients(); 00331 for( ClientList::ConstIterator it = mainclients.begin(); 00332 it != mainclients.end(); 00333 ++it ) 00334 if( (*it)->isShown( true )) 00335 init_minimize = false; // SELI even e.g. for NET::Utility? 00336 } 00337 // if a dialog is shown for minimized window, minimize it too 00338 if( !init_minimize && isTransient() && mainClients().count() > 0 ) 00339 { 00340 bool visible_parent = false; 00341 ClientList mainclients = mainClients(); 00342 for( ClientList::ConstIterator it = mainclients.begin(); 00343 it != mainclients.end(); 00344 ++it ) 00345 if( (*it)->isShown( true )) 00346 visible_parent = true; 00347 if( !visible_parent ) 00348 { 00349 init_minimize = true; 00350 demandAttention(); 00351 } 00352 } 00353 00354 if( init_minimize ) 00355 minimize( true ); // no animation 00356 00357 // SELI this seems to be mainly for kstart and ksystraycmd 00358 // probably should be replaced by something better 00359 bool doNotShow = false; 00360 if ( workspace()->isNotManaged( caption() ) ) 00361 doNotShow = TRUE; 00362 00363 // other settings from the previous session 00364 if ( session ) 00365 { 00366 // session restored windows are not considered to be new windows WRT rules, 00367 // i.e. obey only forcing rules 00368 setKeepAbove( session->keepAbove ); 00369 setKeepBelow( session->keepBelow ); 00370 setSkipTaskbar( session->skipTaskbar, true ); 00371 setSkipPager( session->skipPager ); 00372 setShade( session->shaded ? ShadeNormal : ShadeNone ); 00373 setShadowed( session->shadowed ); 00374 if( session->maximized != MaximizeRestore ) 00375 { 00376 maximize( (MaximizeMode) session->maximized ); 00377 geom_restore = session->restore; 00378 } 00379 if( session->fullscreen == FullScreenHack ) 00380 ; // nothing, this should be already set again above 00381 else if( session->fullscreen != FullScreenNone ) 00382 { 00383 setFullScreen( true, false ); 00384 geom_fs_restore = session->fsrestore; 00385 } 00386 } 00387 else 00388 { 00389 geom_restore = geometry(); // remember restore geometry 00390 if ( isMaximizable() 00391 && ( width() >= area.width() || height() >= area.height() ) ) 00392 { 00393 // window is too large for the screen, maximize in the 00394 // directions necessary 00395 if ( width() >= area.width() && height() >= area.height() ) 00396 { 00397 maximize( Client::MaximizeFull ); 00398 geom_restore = TQRect(); // use placement when unmaximizing 00399 } 00400 else if ( width() >= area.width() ) 00401 { 00402 maximize( Client::MaximizeHorizontal ); 00403 geom_restore = TQRect(); // use placement when unmaximizing 00404 geom_restore.setY( y()); // but only for horizontal direction 00405 geom_restore.setHeight( height()); 00406 } 00407 else if ( height() >= area.height() ) 00408 { 00409 maximize( Client::MaximizeVertical ); 00410 geom_restore = TQRect(); // use placement when unmaximizing 00411 geom_restore.setX( x()); // but only for vertical direction 00412 geom_restore.setWidth( width()); 00413 } 00414 } 00415 // window may want to be maximized 00416 // done after checking that the window isn't larger than the workarea, so that 00417 // the restore geometry from the checks above takes precedence, and window 00418 // isn't restored larger than the workarea 00419 MaximizeMode maxmode = static_cast< MaximizeMode > 00420 ((( info->state() & NET::MaxVert ) ? MaximizeVertical : 0 ) 00421 | (( info->state() & NET::MaxHoriz ) ? MaximizeHorizontal : 0 )); 00422 MaximizeMode forced_maxmode = rules()->checkMaximize( maxmode, !isMapped ); 00423 // either hints were set to maximize, or is forced to maximize, 00424 // or is forced to non-maximize and hints were set to maximize 00425 if( forced_maxmode != MaximizeRestore || maxmode != MaximizeRestore ) 00426 maximize( forced_maxmode ); 00427 00428 // read other initial states 00429 setShade( rules()->checkShade( info->state() & NET::Shaded ? ShadeNormal : ShadeNone, !isMapped )); 00430 setKeepAbove( rules()->checkKeepAbove( info->state() & NET::KeepAbove, !isMapped )); 00431 setKeepBelow( rules()->checkKeepBelow( info->state() & NET::KeepBelow, !isMapped )); 00432 setSkipTaskbar( rules()->checkSkipTaskbar( info->state() & NET::SkipTaskbar, !isMapped ), true ); 00433 setSkipPager( rules()->checkSkipPager( info->state() & NET::SkipPager, !isMapped )); 00434 if( info->state() & NET::DemandsAttention ) 00435 demandAttention(); 00436 if( info->state() & NET::Modal ) 00437 setModal( true ); 00438 if( fullscreen_mode != FullScreenHack && isFullScreenable()) 00439 setFullScreen( rules()->checkFullScreen( info->state() & NET::FullScreen, !isMapped ), false ); 00440 } 00441 00442 updateAllowedActions( true ); 00443 00444 // TODO this should avoid flicker, because real restacking is done 00445 // only after manage() finishes, but the window is shown sooner 00446 // - keep it? 00447 XLowerWindow( qt_xdisplay(), frameId()); 00448 00449 // set initial user time directly 00450 user_time = readUserTimeMapTimestamp( asn_valid ? &asn_id : NULL, asn_valid ? &asn_data : NULL, session ); 00451 group()->updateUserTime( user_time ); // and do what Client::updateUserTime() does 00452 00453 if( isTopMenu()) // they're shown in Workspace::addClient() if their mainwindow 00454 hideClient( true ); // is the active one 00455 00456 if( isShown( true ) && !doNotShow ) 00457 { 00458 if( isDialog()) 00459 Notify::raise( Notify::TransNew ); 00460 if( isNormalWindow()) 00461 Notify::raise( Notify::New ); 00462 00463 bool allow; 00464 if( session ) 00465 allow = session->active 00466 && ( !workspace()->wasUserInteraction() 00467 || workspace()->activeClient() == NULL || workspace()->activeClient()->isDesktop()); 00468 else 00469 allow = workspace()->allowClientActivation( this, userTime(), false ); 00470 00471 // if session saving, force showing new windows (i.e. "save file?" dialogs etc.) 00472 // also force if activation is allowed 00473 if( !isOnCurrentDesktop() && !isMapped && !session && ( allow || workspace()->sessionSaving())) 00474 workspace()->setCurrentDesktop( desktop()); 00475 00476 bool belongs_to_desktop = false; 00477 for( ClientList::ConstIterator it = group()->members().begin(); 00478 it != group()->members().end(); 00479 ++it ) 00480 if( (*it)->isDesktop()) 00481 { 00482 belongs_to_desktop = true; 00483 break; 00484 } 00485 if( !belongs_to_desktop && workspace()->showingDesktop()) 00486 workspace()->resetShowingDesktop( options->showDesktopIsMinimizeAll ); 00487 00488 if( isOnCurrentDesktop() && !isMapped && !allow ) 00489 workspace()->restackClientUnderActive( this ); 00490 else 00491 workspace()->raiseClient( this ); 00492 00493 updateVisibility(); 00494 00495 if( !isMapped ) 00496 { 00497 if( allow && isOnCurrentDesktop()) 00498 { 00499 if( !isSpecialWindow()) 00500 if ( options->focusPolicyIsReasonable() && wantsTabFocus() ) 00501 workspace()->requestFocus( this ); 00502 } 00503 else 00504 { 00505 if( !session && !isSpecialWindow()) 00506 demandAttention(); 00507 } 00508 } 00509 } 00510 else if( !doNotShow ) // if( !isShown( true ) && !doNotShow ) 00511 { 00512 updateVisibility(); 00513 } 00514 else // doNotShow 00515 { // SELI HACK !!! 00516 hideClient( true ); 00517 setMappingState( IconicState ); 00518 } 00519 assert( mappingState() != WithdrawnState ); 00520 00521 if( user_time == CurrentTime || user_time == -1U ) // no known user time, set something old 00522 { 00523 user_time = GET_QT_X_TIME() - 1000000; 00524 if( user_time == CurrentTime || user_time == -1U ) // let's be paranoid 00525 user_time = GET_QT_X_TIME() - 1000000 + 10; 00526 } 00527 00528 updateWorkareaDiffs(); 00529 00530 // sendSyntheticConfigureNotify(); done when setting mapping state 00531 00532 delete session; 00533 00534 ungrabXServer(); 00535 00536 client_rules.discardTemporary(); 00537 applyWindowRules(); // just in case 00538 workspace()->discardUsedWindowRules( this, false ); // remove ApplyNow rules 00539 updateWindowRules(); // was blocked while !isManaged() 00540 00541 // TODO there's a small problem here - isManaged() depends on the mapping state, 00542 // but this client is not yet in Workspace's client list at this point, will 00543 // be only done in addClient() 00544 return true; 00545 } 00546 00547 // called only from manage() 00548 void Client::embedClient( Window w, const XWindowAttributes &attr ) 00549 { 00550 assert( client == None ); 00551 assert( frame == None ); 00552 assert( wrapper == None ); 00553 client = w; 00554 // we don't want the window to be destroyed when we are destroyed 00555 XAddToSaveSet( qt_xdisplay(), client ); 00556 XSelectInput( qt_xdisplay(), client, NoEventMask ); 00557 XUnmapWindow( qt_xdisplay(), client ); 00558 XWindowChanges wc; // set the border width to 0 00559 wc.border_width = 0; // TODO possibly save this, and also use it for initial configuring of the window 00560 XConfigureWindow( qt_xdisplay(), client, CWBorderWidth, &wc ); 00561 00562 XSetWindowAttributes swa; 00563 swa.colormap = attr.colormap; 00564 swa.background_pixmap = None; 00565 swa.border_pixel = 0; 00566 00567 frame = XCreateWindow( qt_xdisplay(), qt_xrootwin(), 0, 0, 1, 1, 0, 00568 attr.depth, InputOutput, attr.visual, 00569 CWColormap | CWBackPixmap | CWBorderPixel, &swa ); 00570 wrapper = XCreateWindow( qt_xdisplay(), frame, 0, 0, 1, 1, 0, 00571 attr.depth, InputOutput, attr.visual, 00572 CWColormap | CWBackPixmap | CWBorderPixel, &swa ); 00573 00574 XDefineCursor( qt_xdisplay(), frame, tqarrowCursor.handle()); 00575 // some apps are stupid and don't define their own cursor - set the arrow one for them 00576 XDefineCursor( qt_xdisplay(), wrapper, tqarrowCursor.handle()); 00577 XReparentWindow( qt_xdisplay(), client, wrapper, 0, 0 ); 00578 XSelectInput( qt_xdisplay(), frame, 00579 KeyPressMask | KeyReleaseMask | 00580 ButtonPressMask | ButtonReleaseMask | 00581 KeymapStateMask | 00582 ButtonMotionMask | 00583 PointerMotionMask | 00584 EnterWindowMask | LeaveWindowMask | 00585 FocusChangeMask | 00586 ExposureMask | 00587 PropertyChangeMask | 00588 StructureNotifyMask | SubstructureRedirectMask ); 00589 XSelectInput( qt_xdisplay(), wrapper, ClientWinMask | SubstructureNotifyMask ); 00590 XSelectInput( qt_xdisplay(), client, 00591 FocusChangeMask | 00592 PropertyChangeMask | 00593 ColormapChangeMask | 00594 EnterWindowMask | LeaveWindowMask | 00595 KeyPressMask | KeyReleaseMask 00596 ); 00597 updateMouseGrab(); 00598 } 00599 00600 } // namespace