sm.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 #include "sm.h" 00013 00014 #include <tqsocketnotifier.h> 00015 #include <tqsessionmanager.h> 00016 #include <kdebug.h> 00017 #include <unistd.h> 00018 #include <stdlib.h> 00019 #include <pwd.h> 00020 #include <fixx11h.h> 00021 #include <tdeconfig.h> 00022 #include <tdeglobal.h> 00023 00024 #include "workspace.h" 00025 #include "client.h" 00026 00027 namespace KWinInternal 00028 { 00029 00030 bool SessionManaged::saveState( TQSessionManager& sm ) 00031 { 00032 // If the session manager is ksmserver, save stacking 00033 // order, active window, active desktop etc. in phase 1, 00034 // as ksmserver assures no interaction will be done 00035 // before the WM finishes phase 1. Saving in phase 2 is 00036 // too late, as possible user interaction may change some things. 00037 // Phase2 is still needed though (ICCCM 5.2) 00038 char* sm_vendor = SmcVendor( static_cast< SmcConn >( sm.handle())); 00039 bool ksmserver = qstrcmp( sm_vendor, "KDE" ) == 0; 00040 free( sm_vendor ); 00041 if ( !sm.isPhase2() ) 00042 { 00043 Workspace::self()->sessionSaveStarted(); 00044 if( ksmserver ) // save stacking order etc. before "save file?" etc. dialogs change it 00045 Workspace::self()->storeSession( kapp->sessionConfig(), SMSavePhase0 ); 00046 sm.release(); // Qt doesn't automatically release in this case (bug?) 00047 sm.requestPhase2(); 00048 return true; 00049 } 00050 Workspace::self()->storeSession( kapp->sessionConfig(), ksmserver ? SMSavePhase2 : SMSavePhase2Full ); 00051 kapp->sessionConfig()->sync(); 00052 return true; 00053 } 00054 00055 // I bet this is broken, just like everywhere else in KDE 00056 bool SessionManaged::commitData( TQSessionManager& sm ) 00057 { 00058 if ( !sm.isPhase2() ) 00059 Workspace::self()->sessionSaveStarted(); 00060 return true; 00061 } 00062 00063 // Workspace 00064 00070 void Workspace::storeSession( TDEConfig* config, SMSavePhase phase ) 00071 { 00072 config->setGroup("Session" ); 00073 int count = 0; 00074 int active_client = -1; 00075 for (ClientList::Iterator it = clients.begin(); it != clients.end(); ++it) 00076 { 00077 Client* c = (*it); 00078 TQCString sessionId = c->sessionId(); 00079 TQCString wmCommand = c->wmCommand(); 00080 if ( sessionId.isEmpty() ) 00081 // remember also applications that are not XSMP capable 00082 // and use the obsolete WM_COMMAND / WM_SAVE_YOURSELF 00083 if ( wmCommand.isEmpty() ) 00084 continue; 00085 count++; 00086 if( c->isActive()) 00087 active_client = count; 00088 TQString n = TQString::number(count); 00089 if( phase == SMSavePhase2 || phase == SMSavePhase2Full ) 00090 { 00091 config->writeEntry( TQString("sessionId")+n, sessionId.data() ); 00092 config->writeEntry( TQString("windowRole")+n, c->windowRole().data() ); 00093 config->writeEntry( TQString("wmCommand")+n, wmCommand.data() ); 00094 config->writeEntry( TQString("wmClientMachine")+n, c->wmClientMachine( true ).data() ); 00095 config->writeEntry( TQString("resourceName")+n, c->resourceName().data() ); 00096 config->writeEntry( TQString("resourceClass")+n, c->resourceClass().data() ); 00097 config->writeEntry( TQString("geometry")+n, TQRect( c->calculateGravitation(TRUE), c->clientSize() ) ); // FRAME 00098 config->writeEntry( TQString("restore")+n, c->geometryRestore() ); 00099 config->writeEntry( TQString("fsrestore")+n, c->geometryFSRestore() ); 00100 config->writeEntry( TQString("maximize")+n, (int) c->maximizeMode() ); 00101 config->writeEntry( TQString("fullscreen")+n, (int) c->fullScreenMode() ); 00102 config->writeEntry( TQString("desktop")+n, c->desktop() ); 00103 // the config entry is called "iconified" for back. comp. reasons 00104 // (tdeconf_update script for updating session files would be too complicated) 00105 config->writeEntry( TQString("iconified")+n, c->isMinimized() ); 00106 // the config entry is called "sticky" for back. comp. reasons 00107 config->writeEntry( TQString("sticky")+n, c->isOnAllDesktops() ); 00108 config->writeEntry( TQString("shaded")+n, c->isShade() ); 00109 config->writeEntry( TQString("shadowed")+n, c->isShadowed() ); 00110 // the config entry is called "staysOnTop" for back. comp. reasons 00111 config->writeEntry( TQString("staysOnTop")+n, c->keepAbove() ); 00112 config->writeEntry( TQString("keepBelow")+n, c->keepBelow() ); 00113 config->writeEntry( TQString("skipTaskbar")+n, c->skipTaskbar( true ) ); 00114 config->writeEntry( TQString("skipPager")+n, c->skipPager() ); 00115 config->writeEntry( TQString("userNoBorder")+n, c->isUserNoBorder() ); 00116 config->writeEntry( TQString("windowType")+n, windowTypeToTxt( c->windowType())); 00117 config->writeEntry( TQString("shortcut")+n, c->shortcut().toStringInternal()); 00118 } 00119 } 00120 // TODO store also stacking order 00121 if( phase == SMSavePhase0 ) 00122 { 00123 // it would be much simpler to save these values to the config file, 00124 // but both Qt and KDE treat phase1 and phase2 separately, 00125 // which results in different sessionkey and different config file :( 00126 session_active_client = active_client; 00127 session_desktop = currentDesktop(); 00128 } 00129 else if( phase == SMSavePhase2 ) 00130 { 00131 config->writeEntry( "count", count ); 00132 config->writeEntry( "active", session_active_client ); 00133 config->writeEntry( "desktop", session_desktop ); 00134 } 00135 else // SMSavePhase2Full 00136 { 00137 config->writeEntry( "count", count ); 00138 config->writeEntry( "active", session_active_client ); 00139 config->writeEntry( "desktop", currentDesktop()); 00140 } 00141 } 00142 00143 00149 void Workspace::loadSessionInfo() 00150 { 00151 session.clear(); 00152 TDEConfig* config = kapp->sessionConfig(); 00153 config->setGroup("Session" ); 00154 int count = config->readNumEntry( "count" ); 00155 int active_client = config->readNumEntry( "active" ); 00156 for ( int i = 1; i <= count; i++ ) 00157 { 00158 TQString n = TQString::number(i); 00159 SessionInfo* info = new SessionInfo; 00160 session.append( info ); 00161 info->sessionId = config->readEntry( TQString("sessionId")+n ).latin1(); 00162 info->windowRole = config->readEntry( TQString("windowRole")+n ).latin1(); 00163 info->wmCommand = config->readEntry( TQString("wmCommand")+n ).latin1(); 00164 info->wmClientMachine = config->readEntry( TQString("wmClientMachine")+n ).latin1(); 00165 info->resourceName = config->readEntry( TQString("resourceName")+n ).latin1(); 00166 info->resourceClass = config->readEntry( TQString("resourceClass")+n ).lower().latin1(); 00167 info->geometry = config->readRectEntry( TQString("geometry")+n ); 00168 info->restore = config->readRectEntry( TQString("restore")+n ); 00169 info->fsrestore = config->readRectEntry( TQString("fsrestore")+n ); 00170 info->maximized = config->readNumEntry( TQString("maximize")+n, 0 ); 00171 info->fullscreen = config->readNumEntry( TQString("fullscreen")+n, 0 ); 00172 info->desktop = config->readNumEntry( TQString("desktop")+n, 0 ); 00173 info->minimized = config->readBoolEntry( TQString("iconified")+n, FALSE ); 00174 info->onAllDesktops = config->readBoolEntry( TQString("sticky")+n, FALSE ); 00175 info->shaded = config->readBoolEntry( TQString("shaded")+n, FALSE ); 00176 info->shadowed = config->readBoolEntry( TQString("shadowed")+n, TRUE ); 00177 info->keepAbove = config->readBoolEntry( TQString("staysOnTop")+n, FALSE ); 00178 info->keepBelow = config->readBoolEntry( TQString("keepBelow")+n, FALSE ); 00179 info->skipTaskbar = config->readBoolEntry( TQString("skipTaskbar")+n, FALSE ); 00180 info->skipPager = config->readBoolEntry( TQString("skipPager")+n, FALSE ); 00181 info->userNoBorder = config->readBoolEntry( TQString("userNoBorder")+n, FALSE ); 00182 info->windowType = txtToWindowType( config->readEntry( TQString("windowType")+n ).latin1()); 00183 info->shortcut = config->readEntry( TQString("shortcut")+n ); 00184 info->active = ( active_client == i ); 00185 } 00186 } 00187 00197 SessionInfo* Workspace::takeSessionInfo( Client* c ) 00198 { 00199 SessionInfo *realInfo = 0; 00200 TQCString sessionId = c->sessionId(); 00201 TQCString windowRole = c->windowRole(); 00202 TQCString wmCommand = c->wmCommand(); 00203 TQCString wmClientMachine = c->wmClientMachine( true ); 00204 TQCString resourceName = c->resourceName(); 00205 TQCString resourceClass = c->resourceClass(); 00206 00207 // First search ``session'' 00208 if (! sessionId.isEmpty() ) 00209 { 00210 // look for a real session managed client (algorithm suggested by ICCCM) 00211 for (SessionInfo* info = session.first(); info && !realInfo; info = session.next() ) 00212 if ( info->sessionId == sessionId && sessionInfoWindowTypeMatch( c, info )) 00213 { 00214 if ( ! windowRole.isEmpty() ) 00215 { 00216 if ( info->windowRole == windowRole ) 00217 realInfo = session.take(); 00218 } 00219 else 00220 { 00221 if ( info->windowRole.isEmpty() && 00222 info->resourceName == resourceName && 00223 info->resourceClass == resourceClass ) 00224 realInfo = session.take(); 00225 } 00226 } 00227 } 00228 else 00229 { 00230 // look for a sessioninfo with matching features. 00231 for (SessionInfo* info = session.first(); info && !realInfo; info = session.next() ) 00232 if ( info->resourceName == resourceName && 00233 info->resourceClass == resourceClass && 00234 info->wmClientMachine == wmClientMachine && 00235 sessionInfoWindowTypeMatch( c, info )) 00236 if ( wmCommand.isEmpty() || info->wmCommand == wmCommand ) 00237 realInfo = session.take(); 00238 } 00239 00240 return realInfo; 00241 } 00242 00243 bool Workspace::sessionInfoWindowTypeMatch( Client* c, SessionInfo* info ) 00244 { 00245 if( info->windowType == -2 ) 00246 { // undefined (not really part of NET::WindowType) 00247 return !c->isSpecialWindow(); 00248 } 00249 return info->windowType == c->windowType(); 00250 } 00251 00252 // maybe needed later 00253 #if 0 00254 // TDEMainWindow's without name() given have WM_WINDOW_ROLE in the form 00255 // of <appname>-mainwindow#<number> 00256 // when comparing them for fake session info, it's probably better to check 00257 // them without the trailing number 00258 bool Workspace::windowRoleMatch( const TQCString& role1, const TQCString& role2 ) 00259 { 00260 if( role1.isEmpty() && role2.isEmpty()) 00261 return true; 00262 int pos1 = role1.find( '#' ); 00263 int pos2 = role2.find( '#' ); 00264 bool ret; 00265 if( pos1 < 0 || pos2 < 0 || pos1 != pos2 ) 00266 ret = role1 == role2; 00267 else 00268 ret = tqstrncmp( role1, role2, pos1 ) == 0; 00269 kdDebug() << "WR:" << role1 << ":" << pos1 << ":" << role2 << ":" << pos2 << ":::" << ret << endl; 00270 return ret; 00271 } 00272 #endif 00273 00274 static const char* const window_type_names[] = 00275 { 00276 "Unknown", "Normal" , "Desktop", "Dock", "Toolbar", "Menu", "Dialog", 00277 "Override", "TopMenu", "Utility", "Splash" 00278 }; 00279 // change also the two functions below when adding new entries 00280 00281 const char* Workspace::windowTypeToTxt( NET::WindowType type ) 00282 { 00283 if( type >= NET::Unknown && type <= NET::Splash ) 00284 return window_type_names[ type + 1 ]; // +1 (unknown==-1) 00285 if( type == -2 ) // undefined (not really part of NET::WindowType) 00286 return "Undefined"; 00287 kdFatal() << "Unknown Window Type" << endl; 00288 return NULL; 00289 } 00290 00291 NET::WindowType Workspace::txtToWindowType( const char* txt ) 00292 { 00293 for( int i = NET::Unknown; 00294 i <= NET::Splash; 00295 ++i ) 00296 if( qstrcmp( txt, window_type_names[ i + 1 ] ) == 0 ) // +1 00297 return static_cast< NET::WindowType >( i ); 00298 return static_cast< NET::WindowType >( -2 ); // undefined 00299 } 00300 00301 00302 00303 00304 // KWin's focus stealing prevention causes problems with user interaction 00305 // during session save, as it prevents possible dialogs from getting focus. 00306 // Therefore it's temporarily disabled during session saving. Start of 00307 // session saving can be detected in SessionManaged::saveState() above, 00308 // but Qt doesn't have API for saying when session saved finished (either 00309 // successfully, or was cancelled). Therefore, create another connection 00310 // to session manager, that will provide this information. 00311 // Similarly the remember feature of window-specific settings should be disabled 00312 // during KDE shutdown when windows may move e.g. because of Kicker going away 00313 // (struts changing). When session saving starts, it can be cancelled, in which 00314 // case the shutdown_cancelled callback is invoked, or it's a checkpoint that 00315 // is immediatelly followed by save_complete, or finally it's a shutdown that 00316 // is immediatelly followed by die callback. So getting save_yourself with shutdown 00317 // set disables window-specific settings remembering, getting shutdown_cancelled 00318 // re-enables, otherwise KWin will go away after die. 00319 static void save_yourself( SmcConn conn_P, SmPointer ptr, int, Bool shutdown, int, Bool ) 00320 { 00321 SessionSaveDoneHelper* session = reinterpret_cast< SessionSaveDoneHelper* >( ptr ); 00322 if( conn_P != session->connection()) 00323 return; 00324 if( shutdown ) 00325 Workspace::self()->disableRulesUpdates( true ); 00326 SmcSaveYourselfDone( conn_P, True ); 00327 } 00328 00329 static void die( SmcConn conn_P, SmPointer ptr ) 00330 { 00331 SessionSaveDoneHelper* session = reinterpret_cast< SessionSaveDoneHelper* >( ptr ); 00332 if( conn_P != session->connection()) 00333 return; 00334 // session->saveDone(); we will quit anyway 00335 session->close(); 00336 } 00337 00338 static void save_complete( SmcConn conn_P, SmPointer ptr ) 00339 { 00340 SessionSaveDoneHelper* session = reinterpret_cast< SessionSaveDoneHelper* >( ptr ); 00341 if( conn_P != session->connection()) 00342 return; 00343 session->saveDone(); 00344 } 00345 00346 static void shutdown_cancelled( SmcConn conn_P, SmPointer ptr ) 00347 { 00348 SessionSaveDoneHelper* session = reinterpret_cast< SessionSaveDoneHelper* >( ptr ); 00349 if( conn_P != session->connection()) 00350 return; 00351 Workspace::self()->disableRulesUpdates( false ); // re-enable 00352 // no need to differentiate between successful finish and cancel 00353 session->saveDone(); 00354 } 00355 00356 void SessionSaveDoneHelper::saveDone() 00357 { 00358 Workspace::self()->sessionSaveDone(); 00359 } 00360 00361 SessionSaveDoneHelper::SessionSaveDoneHelper() 00362 { 00363 SmcCallbacks calls; 00364 calls.save_yourself.callback = save_yourself; 00365 calls.save_yourself.client_data = reinterpret_cast< SmPointer >(this); 00366 calls.die.callback = die; 00367 calls.die.client_data = reinterpret_cast< SmPointer >(this); 00368 calls.save_complete.callback = save_complete; 00369 calls.save_complete.client_data = reinterpret_cast< SmPointer >(this); 00370 calls.shutdown_cancelled.callback = shutdown_cancelled; 00371 calls.shutdown_cancelled.client_data = reinterpret_cast< SmPointer >(this); 00372 char* id = NULL; 00373 char err[ 11 ]; 00374 conn = SmcOpenConnection( NULL, 0, 1, 0, 00375 SmcSaveYourselfProcMask | SmcDieProcMask | SmcSaveCompleteProcMask 00376 | SmcShutdownCancelledProcMask, &calls, NULL, &id, 10, err ); 00377 if( id != NULL ) 00378 free( id ); 00379 if( conn == NULL ) 00380 return; // no SM 00381 // set the required properties, mostly dummy values 00382 SmPropValue propvalue[ 5 ]; 00383 SmProp props[ 5 ]; 00384 propvalue[ 0 ].length = sizeof( int ); 00385 int value0 = SmRestartNever; // so that this extra SM connection doesn't interfere 00386 propvalue[ 0 ].value = &value0; 00387 props[ 0 ].name = const_cast< char* >( SmRestartStyleHint ); 00388 props[ 0 ].type = const_cast< char* >( SmCARD8 ); 00389 props[ 0 ].num_vals = 1; 00390 props[ 0 ].vals = &propvalue[ 0 ]; 00391 struct passwd* entry = getpwuid( geteuid() ); 00392 propvalue[ 1 ].length = entry != NULL ? strlen( entry->pw_name ) : 0; 00393 propvalue[ 1 ].value = (SmPointer)( entry != NULL ? entry->pw_name : "" ); 00394 props[ 1 ].name = const_cast< char* >( SmUserID ); 00395 props[ 1 ].type = const_cast< char* >( SmARRAY8 ); 00396 props[ 1 ].num_vals = 1; 00397 props[ 1 ].vals = &propvalue[ 1 ]; 00398 propvalue[ 2 ].length = 0; 00399 propvalue[ 2 ].value = (SmPointer)( "" ); 00400 props[ 2 ].name = const_cast< char* >( SmRestartCommand ); 00401 props[ 2 ].type = const_cast< char* >( SmLISTofARRAY8 ); 00402 props[ 2 ].num_vals = 1; 00403 props[ 2 ].vals = &propvalue[ 2 ]; 00404 propvalue[ 3 ].length = 0; 00405 propvalue[ 3 ].value = tqApp->argv()[ 0 ]; 00406 props[ 3 ].name = const_cast< char* >( SmProgram ); 00407 props[ 3 ].type = const_cast< char* >( SmARRAY8 ); 00408 props[ 3 ].num_vals = 1; 00409 props[ 3 ].vals = &propvalue[ 3 ]; 00410 propvalue[ 4 ].length = 0; 00411 propvalue[ 4 ].value = (SmPointer)( "" ); 00412 props[ 4 ].name = const_cast< char* >( SmCloneCommand ); 00413 props[ 4 ].type = const_cast< char* >( SmLISTofARRAY8 ); 00414 props[ 4 ].num_vals = 1; 00415 props[ 4 ].vals = &propvalue[ 4 ]; 00416 SmProp* p[ 5 ] = { &props[ 0 ], &props[ 1 ], &props[ 2 ], &props[ 3 ], &props[ 4 ] }; 00417 SmcSetProperties( conn, 5, p ); 00418 notifier = new TQSocketNotifier( IceConnectionNumber( SmcGetIceConnection( conn )), 00419 TQSocketNotifier::Read, TQT_TQOBJECT(this) ); 00420 connect( notifier, TQT_SIGNAL( activated( int )), TQT_SLOT( processData())); 00421 } 00422 00423 SessionSaveDoneHelper::~SessionSaveDoneHelper() 00424 { 00425 close(); 00426 } 00427 00428 void SessionSaveDoneHelper::close() 00429 { 00430 if( conn != NULL ) 00431 { 00432 delete notifier; 00433 SmcCloseConnection( conn, 0, NULL ); 00434 } 00435 conn = NULL; 00436 } 00437 00438 void SessionSaveDoneHelper::processData() 00439 { 00440 if( conn != NULL ) 00441 IceProcessMessages( SmcGetIceConnection( conn ), 0, 0 ); 00442 } 00443 00444 } // namespace 00445 00446 #include "sm.moc"