From 4aed2c8219774f5d797760606b8489a92ddc5163 Mon Sep 17 00:00:00 2001 From: toma Date: Wed, 25 Nov 2009 17:56:58 +0000 Subject: Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features. BUG:215923 git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdebase@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da --- ksmserver/KSMServerInterface.h | 27 ++ ksmserver/LICENSE | 16 + ksmserver/Makefile.am | 50 +++ ksmserver/README | 177 ++++++++ ksmserver/client.cpp | 190 ++++++++ ksmserver/client.h | 60 +++ ksmserver/configure.in.in | 4 + ksmserver/global.h | 13 + ksmserver/ksmserver.upd | 6 + ksmserver/legacy.cpp | 402 +++++++++++++++++ ksmserver/main.cpp | 247 +++++++++++ ksmserver/move_session_config.sh | 32 ++ ksmserver/server.cpp | 922 +++++++++++++++++++++++++++++++++++++++ ksmserver/server.h | 219 ++++++++++ ksmserver/server2.h | 16 + ksmserver/shutdown.cpp | 563 ++++++++++++++++++++++++ ksmserver/shutdowndlg.cpp | 278 ++++++++++++ ksmserver/shutdowndlg.h | 88 ++++ ksmserver/shutdownkonq.png | Bin 0 -> 38334 bytes ksmserver/startup.cpp | 442 +++++++++++++++++++ ksmserver/test.cpp | 27 ++ 21 files changed, 3779 insertions(+) create mode 100644 ksmserver/KSMServerInterface.h create mode 100644 ksmserver/LICENSE create mode 100644 ksmserver/Makefile.am create mode 100644 ksmserver/README create mode 100644 ksmserver/client.cpp create mode 100644 ksmserver/client.h create mode 100644 ksmserver/configure.in.in create mode 100644 ksmserver/global.h create mode 100644 ksmserver/ksmserver.upd create mode 100644 ksmserver/legacy.cpp create mode 100644 ksmserver/main.cpp create mode 100755 ksmserver/move_session_config.sh create mode 100644 ksmserver/server.cpp create mode 100644 ksmserver/server.h create mode 100644 ksmserver/server2.h create mode 100644 ksmserver/shutdown.cpp create mode 100644 ksmserver/shutdowndlg.cpp create mode 100644 ksmserver/shutdowndlg.h create mode 100644 ksmserver/shutdownkonq.png create mode 100644 ksmserver/startup.cpp create mode 100644 ksmserver/test.cpp (limited to 'ksmserver') diff --git a/ksmserver/KSMServerInterface.h b/ksmserver/KSMServerInterface.h new file mode 100644 index 000000000..904b50927 --- /dev/null +++ b/ksmserver/KSMServerInterface.h @@ -0,0 +1,27 @@ +#ifndef KSMSERVER_INTERFACE_H +#define KSMSERVER_INTERFACE_H + +#include +#include + +class KSMServerInterface : virtual public DCOPObject +{ + K_DCOP + +k_dcop: + virtual void logout(int, int, int ) = 0; + virtual void restoreSessionInternal() = 0; + virtual void restoreSessionDoneInternal() = 0; + virtual QStringList sessionList() = 0; + + virtual QString currentSession() = 0; + virtual void saveCurrentSession() = 0; + virtual void saveCurrentSessionAs( QString ) = 0; + + virtual void autoStart2() = 0; + + virtual void suspendStartup( QCString ) = 0; + virtual void resumeStartup( QCString ) = 0; +}; + +#endif diff --git a/ksmserver/LICENSE b/ksmserver/LICENSE new file mode 100644 index 000000000..d28a48f92 --- /dev/null +++ b/ksmserver/LICENSE @@ -0,0 +1,16 @@ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/ksmserver/Makefile.am b/ksmserver/Makefile.am new file mode 100644 index 000000000..00ce998c8 --- /dev/null +++ b/ksmserver/Makefile.am @@ -0,0 +1,50 @@ +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +SUBDIRS = . + +INCLUDES= -I$(top_srcdir)/kdmlib $(all_includes) + +bin_PROGRAMS = +lib_LTLIBRARIES = +kdeinit_LTLIBRARIES = ksmserver.la +noinst_HEADERS = global.h server.h + +ksmserver_la_METASOURCES = AUTO +# Order is important for --enable-final! +ksmserver_la_SOURCES = main.cpp server.cpp shutdowndlg.cpp \ + legacy.cpp startup.cpp shutdown.cpp client.cpp \ + KSMServerInterface.skel server.skel + +ksmserver_la_LDFLAGS = $(all_libraries) -avoid-version -module +ksmserver_la_LIBADD = ../kdmlib/libdmctl.la $(LIB_KDEUI) + +picsdir = $(kde_datadir)/ksmserver/pics +pics_DATA = shutdownkonq.png + +update_DATA = ksmserver.upd +update_SCRIPTS = move_session_config.sh +updatedir = $(kde_datadir)/kconf_update + + +EXTRA_PROGRAMS = testsh +testsh_SOURCES = test.cpp +testsh_LDFLAGS = $(all_libraries) $(KDE_RPATH) +testsh_LDADD = $(LIB_KDEUI) shutdowndlg.lo ../kdmlib/libdmctl.la + +messages: + $(XGETTEXT) *.cpp -o $(podir)/ksmserver.pot diff --git a/ksmserver/README b/ksmserver/README new file mode 100644 index 000000000..1c74111ae --- /dev/null +++ b/ksmserver/README @@ -0,0 +1,177 @@ +KDE session manager (ksmserver) +-------------------------------- + +Matthias Ettrich +Lubos Lunak + +ksmserver is KDE's new session management server. It talks the +standard X11R6 session management protocol (XSMP). Thus, in theory, +it should be compatible with all session managment compliant X11R6 +applications. Unfortunately, there aren't that many of them. To be +precise, I have never seen a single commercial application that +supports it and even within the official X11R6 distribution, 'xclock' +is the only exception. Nevertheless we've chosen to support XSMP +despites the complexity of the protocol in order to provide KDE users +more interoperability with applications that were not explicitely +written with KDE in mind. XSMP, as an official X standard, promised to +be more likely to be supported by third parties than even a superior +KDE-specific protocol. Let's see whether we were right and more +applications will actually talk XSMP. At least all KDE applications do +now. + +Here's a short overview on how session management works. + +Starting the server +------------------- + +The server is usually started from the 'startkde' script. It supports the following options: + + -r, --restore Restores the previous session if available + -w, --windowmanager Starts 'wm' in case no other window manager is + participating in the session. Default is 'kwin' + +The default 'startkde' launches 'ksmserver --restore'. The +'windowmanager' option is useful for users that prefer a window +manager other than kwin. Since the window manager has to participate +in the session (it has to remember window positions and states), it is +usually restarted when the session is restored. To be *really* sure +that this happens, even if the wm might have crashed during the +previous session, ksmserver ensures that. The option specifies, which +windowmanager shall be launched for sure. But again: if the stored +session contains a window manager, the restored one will be used, not +the specified one. As a special feature, ksmserver always starts the +specified window manager first, which results in a much nicer startup +sequence (less flashy). + +KDE startup sequence +-------------------- + +Ksmserver controls the second part of the KDE startup sequence, +after it gets control from the startkde script, which controls +the first part of the startup sequence. All code related to startup +should be in startup.cpp and going down in that source file should +follow the startup order (but note that this is just a documentation +which may get outdated, so in case of doubts the source wins ;) ). + +The startkde scripts already launches kdeinit, which in turns launches +KDE daemons like dcopserver, klauncher and kded. Kded loads autoloaded +kded modules, i.e. those that have X-KDE-Kded-autoload=true in .desktop +files. The exact way autoloading works is controlled by X-KDE-Kded-phase=, +which may be 0, 1 or 2 (the default). Kded phase 0 means the module is +always loaded by kded, even outside of KDE session. It should used only +by kded modules which must be always running. Kded phase 1 modules are +loaded right after kded startup, but only during KDE startup, i.e. it is +for modules that are always needed by the KDE session. Phase 2 modules +will be loaded later. + +Startkde also launches kcminit, which performs initialization done by kcontrol +modules. There are three kcminit phases, 0, 1 and 2, controlled +by X-KDE-Init-Phase= in the .desktop file, which defaults to 1. Phase 0 kcminit +modules should be only those that really need to be run early in the startup +process (and those should probably actually use kstartupconfig in startkde +to be done even before kdeinit and daemons). After executing phase 0 +modules kcminit returns and waits. + +When ksmserver is launched, the first thing it does is launching +the window manager, as the WM is necessary before any windows are possibly +shown. When the WM is ready, ksmserver tells klauncher to perform autostart +phase 0 ($KDEHOME/share/autostart). There are 3 autostart phases, 0, 1 and 2, +defined by X-KDE-autostart-phase, defaulting to 2. Phase 0 is reserved only +for the actual visible base components of KDE, i.e. KDesktop and Kicker, +in order to make the startup appear visually faster. Both KDesktop and Kicker +use DCOP calls suspendStartup() and resumeStartup() to make ksmserver stay +waiting for autostart phase 0 until both KDesktop and Kicker are ready. + +Next step is telling the waiting kcminit to perform phase 1 - all kcminit +modules that should be executed before KDE startup is considered done. +After that ksmserver tells klauncher to perform autostart phase 1, +i.e. launching normal components of KDE that should be available right +after KDE startup, and after this session restore is performed, +i.e. launching all applications that were running during last session +saving (usually logout). + +By this time KDE session is considered to be more or less ready and +ksmserver does the knotify startkde event (i.e. plays the login sound). +It also tells klauncher to perform autostart phase 2, kded to load all +remaining autoload (i.e. kded phase 2) modules, kcminit to execute +kcminit phase 2 (kcontrol modules that do initialization that can wait, +like launching daemons) and kdesktop to execute the user Autostart folder. + +Technical note: There's a reason why kded modules and items in autostart +default to the latest phase. Before you explicitly use a different phase, +read and understand what's above. You should also consider whether something +really needs to be launched during KDE startup and can't be loaded on-demand +when really needed. Abusing the phases will result in public spanking +for making KDE startup slower. + + +Establishing the connection +--------------------------- + +As required by the XSMP specification, the session management server +propagates its network address in the SESSION_MANAGER environment +variable. Probably not the best way to do it, but it's the way it +is. All KDE (and plain Qt) applications simply read this variable and +try to establish a connection to an XSMP server at this address. If +the variable is undefined, nothing happens. + +This means, if you want to start a program that should not participate +in the session, simply undefine SESSION_MANAGER in your terminal +window and launch the application. If you want to see an application +desparately trying to connect to something, try setting it to some +bogus value. + +In addition, ksmserver propagates both its network address and its +process id in ~/.kde/socket-$HOSTNAME/KSMserver-$DISPLAY. A +utility function KApplication::propagateSessionManager() reads this +setting and sets SESSION_MANAGER accordingly, so that child processes +can pick it up. The function is called by clients that are started +outside the session ( i.e. before ksmserver is started), but want to +launch other processes that should participate in the session. +Examples are kdesktop or kicker, see below. + +Authorization +------------- + +XSMP is, just like DCOP, built on top of the Inter Client Exchange +(ICE) protocol, which comes standard as a part of X11R6 and later. +Authorization is done using 'iceauth', with MIT-MAGIC-COOKIE as used +by X. In order to be able to access the session management server, you +need to be able to read ~/.ICEauthority. For security reasons, we do +not provide any host-based authorization (neither does DCOP, btw.). + + +Requesting a shutdown +--------------------- + +If an application wants to request a shutdown (i.e. a logout), it does +this via an SmcRequestSaveYourself message to the server. In KDE, a +utility function KApplication::requestShutDown() does exactly +this. It's for example called by KDE's panel or by the context menu of +the desktop. + + +User Interface +-------------- + +ksmserver has a very straight-forward user interface. It mainly asks +the question "Shutdown KDE Session?" and provides two obvious command +buttons "Yes" and "Cancel". The interesting bit is the additonal +checkbox that says "Restore session when logging in next time". The +checkbox remembers state within session, so simply use whatever you +prefer. For those who remember, this was one of the main questions +with KDE-1.x ("How to get rid of session managment?"). With KDE-2.x, +most users will probably prepare a session once, store it with the +checkbox enabled and keep the checkbox disabled in the future. This +way you get a proper and clean 'homesession' each time. + + +Troubleshooting +--------------- + +If you experience trouble like 'logout does not work anymore' or 'I +cannot start new applications', as a result of a previous crash, +ensure that ksmserver is indeed not running anymore and remove the +file ~/.kde/socket-$HOSTNAME/KSMserver-$DISPLAY. Shouldn't be necessry, +but one never knows. + diff --git a/ksmserver/client.cpp b/ksmserver/client.cpp new file mode 100644 index 000000000..784de496c --- /dev/null +++ b/ksmserver/client.cpp @@ -0,0 +1,190 @@ +/***************************************************************** +ksmserver - the KDE session management server + +Copyright (C) 2000 Matthias Ettrich +Copyright (C) 2005 Lubos Lunak + +relatively small extensions by Oswald Buddenhagen + +some code taken from the dcopserver (part of the KDE libraries), which is +Copyright (c) 1999 Matthias Ettrich +Copyright (c) 1999 Preston Brown + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +******************************************************************/ + + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "client.h" + +#include +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif +#include + +#include + +#include "server.h" + +KSMClient::KSMClient( SmsConn conn) +{ + smsConn = conn; + id = 0; + resetState(); +} + +KSMClient::~KSMClient() +{ + for ( SmProp* prop = properties.first(); prop; prop = properties.next() ) + SmFreeProperty( prop ); + if (id) free((void*)id); +} + +SmProp* KSMClient::property( const char* name ) const +{ + for ( QPtrListIterator it( properties ); it.current(); ++it ) { + if ( !qstrcmp( it.current()->name, name ) ) + return it.current(); + } + return 0; +} + +void KSMClient::resetState() +{ + saveYourselfDone = false; + pendingInteraction = false; + waitForPhase2 = false; + wasPhase2 = false; +} + +/* + * This fakes SmsGenerateClientID() in case we can't read our own hostname. + * In this case SmsGenerateClientID() returns NULL, but we really want a + * client ID, so we fake one. + */ +static KStaticDeleter smy_addr; +static char * safeSmsGenerateClientID( SmsConn /*c*/ ) +{ +// Causes delays with misconfigured network :-/. +// char *ret = SmsGenerateClientID(c); + char* ret = NULL; + if (!ret) { + static QString *my_addr = 0; + if (!my_addr) { +// qWarning("Can't get own host name. Your system is severely misconfigured\n"); + smy_addr.setObject(my_addr,new QString); + + /* Faking our IP address, the 0 below is "unknown" address format + (1 would be IP, 2 would be DEC-NET format) */ + char hostname[ 256 ]; + if( gethostname( hostname, 255 ) != 0 ) + my_addr->sprintf("0%.8x", KApplication::random()); + else { + // create some kind of hash for the hostname + int addr[ 4 ] = { 0, 0, 0, 0 }; + int pos = 0; + for( unsigned int i = 0; + i < strlen( hostname ); + ++i, ++pos ) + addr[ pos % 4 ] += hostname[ i ]; + *my_addr = "0"; + for( int i = 0; + i < 4; + ++i ) + *my_addr += QString::number( addr[ i ], 16 ); + } + } + /* Needs to be malloc(), to look the same as libSM */ + ret = (char *)malloc(1+my_addr->length()+13+10+4+1 + /*safeness*/ 10); + static int sequence = 0; + + if (ret == NULL) + return NULL; + + sprintf(ret, "1%s%.13ld%.10d%.4d", my_addr->latin1(), (long)time(NULL), + getpid(), sequence); + sequence = (sequence + 1) % 10000; + } + return ret; +} + +void KSMClient::registerClient( const char* previousId ) +{ + id = previousId; + if ( !id ) + id = safeSmsGenerateClientID( smsConn ); + SmsRegisterClientReply( smsConn, (char*) id ); + SmsSaveYourself( smsConn, SmSaveLocal, false, SmInteractStyleNone, false ); + SmsSaveComplete( smsConn ); + KSMServer::self()->clientRegistered( previousId ); +} + + +QString KSMClient::program() const +{ + SmProp* p = property( SmProgram ); + if ( !p || qstrcmp( p->type, SmARRAY8) || p->num_vals < 1) + return QString::null; + return QString::fromLatin1( (const char*) p->vals[0].value ); +} + +QStringList KSMClient::restartCommand() const +{ + QStringList result; + SmProp* p = property( SmRestartCommand ); + if ( !p || qstrcmp( p->type, SmLISTofARRAY8) || p->num_vals < 1) + return result; + for ( int i = 0; i < p->num_vals; i++ ) + result +=QString::fromLatin1( (const char*) p->vals[i].value ); + return result; +} + +QStringList KSMClient::discardCommand() const +{ + QStringList result; + SmProp* p = property( SmDiscardCommand ); + if ( !p || qstrcmp( p->type, SmLISTofARRAY8) || p->num_vals < 1) + return result; + for ( int i = 0; i < p->num_vals; i++ ) + result +=QString::fromLatin1( (const char*) p->vals[i].value ); + return result; +} + +int KSMClient::restartStyleHint() const +{ + SmProp* p = property( SmRestartStyleHint ); + if ( !p || qstrcmp( p->type, SmCARD8) || p->num_vals < 1) + return SmRestartIfRunning; + return *((int*)p->vals[0].value); +} + +QString KSMClient::userId() const +{ + SmProp* p = property( SmUserID ); + if ( !p || qstrcmp( p->type, SmARRAY8) || p->num_vals < 1) + return QString::null; + return QString::fromLatin1( (const char*) p->vals[0].value ); +} + + diff --git a/ksmserver/client.h b/ksmserver/client.h new file mode 100644 index 000000000..46dc28fa2 --- /dev/null +++ b/ksmserver/client.h @@ -0,0 +1,60 @@ +/***************************************************************** +ksmserver - the KDE session management server + +Copyright (C) 2000 Matthias Ettrich +******************************************************************/ + +#ifndef CLIENT_H +#define CLIENT_H + +// needed to avoid clash with INT8 defined in X11/Xmd.h on solaris +#define QT_CLEAN_NAMESPACE 1 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "server2.h" + +class KSMListener; +class KSMConnection; +class KSMClient +{ +public: + KSMClient( SmsConn ); + ~KSMClient(); + + void registerClient( const char* previousId = 0 ); + SmsConn connection() const { return smsConn; } + + void resetState(); + uint saveYourselfDone : 1; + uint pendingInteraction : 1; + uint waitForPhase2 : 1; + uint wasPhase2 : 1; + + QPtrList properties; + SmProp* property( const char* name ) const; + + QString program() const; + QStringList restartCommand() const; + QStringList discardCommand() const; + int restartStyleHint() const; + QString userId() const; + const char* clientId() { return id ? id : ""; } + +private: + const char* id; + SmsConn smsConn; +}; + +#endif diff --git a/ksmserver/configure.in.in b/ksmserver/configure.in.in new file mode 100644 index 000000000..aef963e2f --- /dev/null +++ b/ksmserver/configure.in.in @@ -0,0 +1,4 @@ +ac_save_LIBS="$LIBS" +LIBS="$LIBS $X_LDFLAGS -lICE" +AC_CHECK_FUNCS(_IceTransNoListen) +LIBS="$ac_save_LIBS" diff --git a/ksmserver/global.h b/ksmserver/global.h new file mode 100644 index 000000000..326587ac6 --- /dev/null +++ b/ksmserver/global.h @@ -0,0 +1,13 @@ +/***************************************************************** +ksmserver - the KDE session management server + +Copyright (C) 2000 Matthias Ettrich +******************************************************************/ + +#ifndef GLOBAL_H +#define GLOBAL_H + +#define KSMVendorString "KDE" +#define KSMReleaseString "1.0" + +#endif diff --git a/ksmserver/ksmserver.upd b/ksmserver/ksmserver.upd new file mode 100644 index 000000000..38bf38471 --- /dev/null +++ b/ksmserver/ksmserver.upd @@ -0,0 +1,6 @@ +# Move session config files from $KDEHOME/share/config to $KDEHOME/share/config/session +Id=kde3 +File=ksmserverrc +Group=Session +Options=overwrite +Script=move_session_config.sh,sh diff --git a/ksmserver/legacy.cpp b/ksmserver/legacy.cpp new file mode 100644 index 000000000..ca198a212 --- /dev/null +++ b/ksmserver/legacy.cpp @@ -0,0 +1,402 @@ +/***************************************************************** +ksmserver - the KDE session management server + +Copyright (C) 2000 Matthias Ettrich +Copyright (C) 2005 Lubos Lunak + +relatively small extensions by Oswald Buddenhagen + +some code taken from the dcopserver (part of the KDE libraries), which is +Copyright (c) 1999 Matthias Ettrich +Copyright (c) 1999 Preston Brown + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +******************************************************************/ + + +#ifdef HAVE_CONFIG_H +#include +#endif + +#ifdef HAVE_SYS_TIME_H +#include +#endif + +#include "server.h" + +#ifdef HAVE_SYS_TIME_H +#include +#endif + +#include + +#include + +#include +#include +#include + +#include +#include +#include + + +/* + * Legacy session management + */ +const int WM_SAVE_YOURSELF_TIMEOUT = 4000; + +static WindowMap* windowMapPtr = 0; + +static Atom wm_save_yourself = None; +static Atom wm_protocols = None; +static Atom wm_client_leader = None; + +extern Time qt_x_time; + +static int winsErrorHandler(Display *, XErrorEvent *ev) +{ + if (windowMapPtr) { + WindowMap::Iterator it = windowMapPtr->find(ev->resourceid); + if (it != windowMapPtr->end()) + (*it).type = SM_ERROR; + } + return 0; +} + +void KSMServer::performLegacySessionSave() +{ + kdDebug( 1218 ) << "Saving legacy session apps" << endl; + // Setup error handler + legacyWindows.clear(); + windowMapPtr = &legacyWindows; + XErrorHandler oldHandler = XSetErrorHandler(winsErrorHandler); + // Compute set of leader windows that need legacy session management + // and determine which style (WM_COMMAND or WM_SAVE_YOURSELF) + KWinModule module; + if( wm_save_yourself == (Atom)None ) { + Atom atoms[ 3 ]; + const char* const names[] + = { "WM_SAVE_YOURSELF", "WM_PROTOCOLS", "WM_CLIENT_LEADER" }; + XInternAtoms( qt_xdisplay(), const_cast< char** >( names ), 3, + False, atoms ); + wm_save_yourself = atoms[ 0 ]; + wm_protocols = atoms[ 1 ]; + wm_client_leader = atoms[ 2 ]; + } + for ( QValueList::ConstIterator it = module.windows().begin(); + it != module.windows().end(); ++it) { + WId leader = windowWmClientLeader( *it ); + if (!legacyWindows.contains(leader) && windowSessionId( *it, leader ).isEmpty()) { + SMType wtype = SM_WMCOMMAND; + int nprotocols = 0; + Atom *protocols = 0; + if( XGetWMProtocols(qt_xdisplay(), leader, &protocols, &nprotocols)) { + for (int i=0; i 0) { + if (XPending(newdisplay)) { + /* Process pending event */ + XNextEvent(newdisplay, &ev); + if ( ( ev.xany.type == UnmapNotify ) || + ( ev.xany.type == PropertyNotify && ev.xproperty.atom == XA_WM_COMMAND ) ) { + WindowMap::Iterator it = legacyWindows.find( ev.xany.window ); + if ( it != legacyWindows.end() && (*it).type != SM_WMCOMMAND ) { + awaiting_replies -= 1; + if ( (*it).type != SM_ERROR ) + (*it).type = SM_WMCOMMAND; + } + } + } else { + /* Check timeout */ + int msecs = start.elapsed(); + if (msecs >= WM_SAVE_YOURSELF_TIMEOUT) + break; + /* Wait for more events */ + fd_set fds; + FD_ZERO(&fds); + int fd = ConnectionNumber(newdisplay); + FD_SET(fd, &fds); + struct timeval tmwait; + tmwait.tv_sec = (WM_SAVE_YOURSELF_TIMEOUT - msecs) / 1000; + tmwait.tv_usec = ((WM_SAVE_YOURSELF_TIMEOUT - msecs) % 1000) * 1000; + ::select(fd+1, &fds, NULL, &fds, &tmwait); + } + } + // Terminate work in new display + XAllowEvents(newdisplay, ReplayPointer, CurrentTime); + XAllowEvents(newdisplay, ReplayKeyboard, CurrentTime); + XSync(newdisplay, False); + XCloseDisplay(newdisplay); + // Restore old error handler + XSync(qt_xdisplay(), False); + XSetErrorHandler(oldHandler); + for (WindowMap::Iterator it = legacyWindows.begin(); it != legacyWindows.end(); ++it) { + if ( (*it).type != SM_ERROR) { + WId w = it.key(); + (*it).wmCommand = windowWmCommand(w); + (*it).wmClientMachine = windowWmClientMachine(w); + } + } + kdDebug( 1218 ) << "Done saving " << legacyWindows.count() << " legacy session apps" << endl; +} + +/*! + Stores legacy session management data +*/ +void KSMServer::storeLegacySession( KConfig* config ) +{ + // Write LegacySession data + config->deleteGroup( "Legacy" + sessionGroup ); + KConfigGroupSaver saver( config, "Legacy" + sessionGroup ); + int count = 0; + for (WindowMap::ConstIterator it = legacyWindows.begin(); it != legacyWindows.end(); ++it) { + if ( (*it).type != SM_ERROR) { + if( excludeApps.contains( (*it).wmclass1.lower()) + || excludeApps.contains( (*it).wmclass2.lower())) + continue; + if ( !(*it).wmCommand.isEmpty() && !(*it).wmClientMachine.isEmpty() ) { + count++; + QString n = QString::number(count); + config->writeEntry( QString("command")+n, (*it).wmCommand ); + config->writeEntry( QString("clientMachine")+n, (*it).wmClientMachine ); + } + } + } + config->writeEntry( "count", count ); +} + +/*! + Restores legacy session management data (i.e. restart applications) +*/ +void KSMServer::restoreLegacySession( KConfig* config ) +{ + if( config->hasGroup( "Legacy" + sessionGroup )) { + KConfigGroupSaver saver( config, "Legacy" + sessionGroup ); + restoreLegacySessionInternal( config ); + } else if( wm == "kwin" ) { // backwards comp. - get it from kwinrc + KConfigGroupSaver saver( config, sessionGroup ); + int count = config->readNumEntry( "count", 0 ); + for ( int i = 1; i <= count; i++ ) { + QString n = QString::number(i); + if ( config->readEntry( QString("program")+n ) != wm ) + continue; + QStringList restartCommand = + config->readListEntry( QString("restartCommand")+n ); + for( QStringList::ConstIterator it = restartCommand.begin(); + it != restartCommand.end(); + ++it ) { + if( (*it) == "-session" ) { + ++it; + if( it != restartCommand.end()) { + KConfig cfg( "session/" + wm + "_" + (*it), true ); + cfg.setGroup( "LegacySession" ); + restoreLegacySessionInternal( &cfg, ' ' ); + } + } + } + } + } +} + +void KSMServer::restoreLegacySessionInternal( KConfig* config, char sep ) +{ + int count = config->readNumEntry( "count" ); + for ( int i = 1; i <= count; i++ ) { + QString n = QString::number(i); + QStringList wmCommand = config->readListEntry( QString("command")+n, sep ); + if( wmCommand.isEmpty()) + continue; + if( isWM( wmCommand.first())) + continue; + startApplication( wmCommand, + config->readEntry( QString("clientMachine")+n ), + config->readEntry( QString("userId")+n )); + } +} + +static QCString getQCStringProperty(WId w, Atom prop) +{ + Atom type; + int format, status; + unsigned long nitems = 0; + unsigned long extra = 0; + unsigned char *data = 0; + QCString result = ""; + status = XGetWindowProperty( qt_xdisplay(), w, prop, 0, 10000, + FALSE, XA_STRING, &type, &format, + &nitems, &extra, &data ); + if ( status == Success) { + if( data ) + result = (char*)data; + XFree(data); + } + return result; +} + +static QStringList getQStringListProperty(WId w, Atom prop) +{ + Atom type; + int format, status; + unsigned long nitems = 0; + unsigned long extra = 0; + unsigned char *data = 0; + QStringList result; + + status = XGetWindowProperty( qt_xdisplay(), w, prop, 0, 10000, + FALSE, XA_STRING, &type, &format, + &nitems, &extra, &data ); + if ( status == Success) { + if (!data) + return result; + for (int i=0; i<(int)nitems; i++) { + result << QString::fromLatin1( (const char*)data + i ); + while(data[i]) i++; + } + XFree(data); + } + return result; +} + +QStringList KSMServer::windowWmCommand(WId w) +{ + QStringList ret = getQStringListProperty(w, XA_WM_COMMAND); + // hacks here + if( ret.count() == 1 ) { + QString command = ret.first(); + // Mozilla is launched using wrapper scripts, so it's launched using "mozilla", + // but the actual binary is "mozilla-bin" or "/mozilla-bin", and that's what + // will be also in WM_COMMAND - using this "mozilla-bin" doesn't work at all though + if( command.endsWith( "mozilla-bin" )) + return QStringList() << "mozilla"; + if( command.endsWith( "firefox-bin" )) + return QStringList() << "firefox"; + if( command.endsWith( "thunderbird-bin" )) + return QStringList() << "thunderbird"; + if( command.endsWith( "sunbird-bin" )) + return QStringList() << "sunbird"; + } + return ret; +} + +QString KSMServer::windowWmClientMachine(WId w) +{ + QCString result = getQCStringProperty(w, XA_WM_CLIENT_MACHINE); + if (result.isEmpty()) { + result = "localhost"; + } else { + // special name for the local machine (localhost) + char hostnamebuf[80]; + if (gethostname (hostnamebuf, sizeof hostnamebuf) >= 0) { + hostnamebuf[sizeof(hostnamebuf)-1] = 0; + if (result == hostnamebuf) + result = "localhost"; + if(char *dot = strchr(hostnamebuf, '.')) { + *dot = '\0'; + if(result == hostnamebuf) + result = "localhost"; + } + } + } + return QString::fromLatin1(result); +} + +WId KSMServer::windowWmClientLeader(WId w) +{ + Atom type; + int format, status; + unsigned long nitems = 0; + unsigned long extra = 0; + unsigned char *data = 0; + Window result = w; + status = XGetWindowProperty( qt_xdisplay(), w, wm_client_leader, 0, 10000, + FALSE, XA_WINDOW, &type, &format, + &nitems, &extra, &data ); + if (status == Success ) { + if (data && nitems > 0) + result = *((Window*) data); + XFree(data); + } + return result; +} + + +/* + Returns sessionId for this client, + taken either from its window or from the leader window. + */ +extern Atom qt_sm_client_id; +QCString KSMServer::windowSessionId(WId w, WId leader) +{ + QCString result = getQCStringProperty(w, qt_sm_client_id); + if (result.isEmpty() && leader != (WId)None && leader != w) + result = getQCStringProperty(leader, qt_sm_client_id); + return result; +} diff --git a/ksmserver/main.cpp b/ksmserver/main.cpp new file mode 100644 index 000000000..8602e1b02 --- /dev/null +++ b/ksmserver/main.cpp @@ -0,0 +1,247 @@ +/***************************************************************** +ksmserver - the KDE session management server + +Copyright (C) 2000 Matthias Ettrich +******************************************************************/ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include "server.h" + + +static const char version[] = "0.4"; +static const char description[] = I18N_NOOP( "The reliable KDE session manager that talks the standard X11R6 \nsession management protocol (XSMP)." ); + +static const KCmdLineOptions options[] = +{ + { "r", 0, 0 }, + { "restore", I18N_NOOP("Restores the saved user session if available"), 0}, + { "w", 0, 0 }, + { "windowmanager ", I18N_NOOP("Starts 'wm' in case no other window manager is \nparticipating in the session. Default is 'kwin'"), 0}, + { "nolocal", I18N_NOOP("Also allow remote connections"), 0}, + KCmdLineLastOption +}; + +extern KSMServer* the_server; + +void IoErrorHandler ( IceConn iceConn) +{ + the_server->ioError( iceConn ); +} + +bool writeTest(QCString path) +{ + path += "/XXXXXX"; + int fd = mkstemp(path.data()); + if (fd == -1) + return false; + if (write(fd, "Hello World\n", 12) == -1) + { + int save_errno = errno; + close(fd); + unlink(path.data()); + errno = save_errno; + return false; + } + close(fd); + unlink(path.data()); + return true; +} + +void sanity_check( int argc, char* argv[] ) +{ + QCString msg; + QCString path = getenv("HOME"); + QCString readOnly = getenv("KDE_HOME_READONLY"); + if (path.isEmpty()) + { + msg = "$HOME not set!"; + } + if (msg.isEmpty() && access(path.data(), W_OK)) + { + if (errno == ENOENT) + msg = "$HOME directory (%s) does not exist."; + else if (readOnly.isEmpty()) + msg = "No write access to $HOME directory (%s)."; + } + if (msg.isEmpty() && access(path.data(), R_OK)) + { + if (errno == ENOENT) + msg = "$HOME directory (%s) does not exist."; + else + msg = "No read access to $HOME directory (%s)."; + } + if (msg.isEmpty() && readOnly.isEmpty() && !writeTest(path)) + { + if (errno == ENOSPC) + msg = "$HOME directory (%s) is out of disk space."; + else + msg = "Writing to the $HOME directory (%s) failed with\n " + "the error '"+QCString(strerror(errno))+"'"; + } + if (msg.isEmpty()) + { + path = getenv("ICEAUTHORITY"); + if (path.isEmpty()) + { + path = getenv("HOME"); + path += "/.ICEauthority"; + } + + if (access(path.data(), W_OK) && (errno != ENOENT)) + msg = "No write access to '%s'."; + else if (access(path.data(), R_OK) && (errno != ENOENT)) + msg = "No read access to '%s'."; + } + if (msg.isEmpty()) + { + path = DCOPClient::dcopServerFile(); + if (access(path.data(), R_OK) && (errno == ENOENT)) + { + // Check iceauth + if (DCOPClient::iceauthPath().isEmpty()) + msg = "Could not find 'iceauth' in path."; + } + } + if (msg.isEmpty()) + { + path = getenv("KDETMP"); + if (path.isEmpty()) + path = "/tmp"; + if (!writeTest(path)) + { + if (errno == ENOSPC) + msg = "Temp directory (%s) is out of disk space."; + else + msg = "Writing to the temp directory (%s) failed with\n " + "the error '"+QCString(strerror(errno))+"'"; + } + } + if (msg.isEmpty() && (path != "/tmp")) + { + path = "/tmp"; + if (!writeTest(path)) + { + if (errno == ENOSPC) + msg = "Temp directory (%s) is out of disk space."; + else + msg = "Writing to the temp directory (%s) failed with\n " + "the error '"+QCString(strerror(errno))+"'"; + } + } + if (msg.isEmpty()) + { + path += ".ICE-unix"; + if (access(path.data(), W_OK) && (errno != ENOENT)) + msg = "No write access to '%s'."; + else if (access(path.data(), R_OK) && (errno != ENOENT)) + msg = "No read access to '%s'."; + } + if (!msg.isEmpty()) + { + const char *msg_pre = + "The following installation problem was detected\n" + "while trying to start KDE:" + "\n\n "; + const char *msg_post = "\n\nKDE is unable to start.\n"; + fputs(msg_pre, stderr); + fprintf(stderr, msg.data(), path.data()); + fputs(msg_post, stderr); + + QApplication a(argc, argv); + QCString qmsg(256+path.length()); + qmsg.sprintf(msg.data(), path.data()); + qmsg = msg_pre+qmsg+msg_post; + QMessageBox::critical(0, "KDE Installation Problem!", + QString::fromLatin1(qmsg.data())); + exit(255); + } +} + +extern "C" KDE_EXPORT int kdemain( int argc, char* argv[] ) +{ + sanity_check(argc, argv); + + KAboutData aboutData( "ksmserver", I18N_NOOP("The KDE Session Manager"), + version, description, KAboutData::License_BSD, + "(C) 2000, The KDE Developers"); + aboutData.addAuthor("Matthias Ettrich",0, "ettrich@kde.org"); + aboutData.addAuthor("Luboš Luňák", I18N_NOOP( "Maintainer" ), "l.lunak@kde.org" ); + + KCmdLineArgs::init(argc, argv, &aboutData); + KCmdLineArgs::addCmdLineOptions( options ); + + putenv((char*)"SESSION_MANAGER="); + KApplication a(false, true); // Disable styles until we need them. + fcntl(ConnectionNumber(qt_xdisplay()), F_SETFD, 1); + + + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + + kapp->dcopClient()->registerAs("ksmserver", false); + if (!kapp->dcopClient()->isRegistered()) + { + qWarning("Could not register with DCOPServer. Aborting."); + return 1; + } + + QCString wm = args->getOption("windowmanager"); + if ( wm.isEmpty() ) + wm = "kwin"; + + bool only_local = args->isSet("local"); +#ifndef HAVE__ICETRANSNOLISTEN + /* this seems strange, but the default is only_local, so if !only_local + * the option --nolocal was given, and we warn (the option --nolocal + * does nothing on this platform, as here the default is reversed) + */ + if (!only_local) { + qWarning("--[no]local is not supported on your platform. Sorry."); + } + only_local = false; +#endif + + KSMServer *server = new KSMServer( QString::fromLatin1(wm), only_local); + kapp->dcopClient()->setDefaultObject( server->objId() ); + + IceSetIOErrorHandler( IoErrorHandler ); + + KConfig *config = KGlobal::config(); + config->setGroup( "General" ); + + int realScreenCount = ScreenCount( qt_xdisplay() ); + bool screenCountChanged = + ( config->readNumEntry( "screenCount", realScreenCount ) != realScreenCount ); + + QString loginMode = config->readEntry( "loginMode", "restorePreviousLogout" ); + + if ( args->isSet("restore") && ! screenCountChanged ) + server->restoreSession( SESSION_BY_USER ); + else if ( loginMode == "default" || screenCountChanged ) + server->startDefaultSession(); + else if ( loginMode == "restorePreviousLogout" ) + server->restoreSession( SESSION_PREVIOUS_LOGOUT ); + else if ( loginMode == "restoreSavedSession" ) + server->restoreSession( SESSION_BY_USER ); + else + server->startDefaultSession(); + return a.exec(); +} + diff --git a/ksmserver/move_session_config.sh b/ksmserver/move_session_config.sh new file mode 100755 index 000000000..fb8b52526 --- /dev/null +++ b/ksmserver/move_session_config.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +prefix=`kde-config --localprefix` +source="${prefix}/share/config" +dest="${prefix}/share/config/session" + +# move session config files + +if [ -n "$prefix" -a -d "$source" ]; then + while [ ! -d "$dest" ]; do + dir="$dest" + while [ ! -d `dirname "$dir"` ]; do + dir=`dirname "$dir"` + done + mkdir "$dir" || exit 1 + done + + files=`eval ls -1 "$source/*:[0-9a-f]*" 2> /dev/null` + if [ -n "$files" ]; then + for i in $files; do + origfile=`basename "$i"` + newfile=`echo "$origfile" | sed -e 's^:^_^'` + if [ -n "$newfile" -a ! -e "$dest/$newfile" ]; then + mv "$source/$origfile" "$dest/$newfile" + fi + done + fi +fi + +# update references in ksmserverrc + +sed -e 's^share/config/\([^/:]*\):^share/config/session/\1_^' diff --git a/ksmserver/server.cpp b/ksmserver/server.cpp new file mode 100644 index 000000000..2fcb83785 --- /dev/null +++ b/ksmserver/server.cpp @@ -0,0 +1,922 @@ +/***************************************************************** +ksmserver - the KDE session management server + +Copyright (C) 2000 Matthias Ettrich +Copyright (C) 2005 Lubos Lunak + +relatively small extensions by Oswald Buddenhagen + +some code taken from the dcopserver (part of the KDE libraries), which is +Copyright (c) 1999 Matthias Ettrich +Copyright (c) 1999 Preston Brown + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +******************************************************************/ + + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_LIMITS_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "server.h" +#include "global.h" +#include "client.h" + +#include "server.moc" + +#include + +#include + +KSMServer* the_server = 0; + +KSMServer* KSMServer::self() +{ + return the_server; +} + +/*! Utility function to execute a command on the local machine. Used + * to restart applications. + */ +void KSMServer::startApplication( QStringList command, const QString& clientMachine, + const QString& userId ) +{ + if ( command.isEmpty() ) + return; + if ( !userId.isEmpty()) { + struct passwd* pw = getpwuid( getuid()); + if( pw != NULL && userId != QString::fromLocal8Bit( pw->pw_name )) { + command.prepend( "--" ); + command.prepend( userId ); + command.prepend( "-u" ); + command.prepend( "kdesu" ); + } + } + if ( !clientMachine.isEmpty() && clientMachine != "localhost" ) { + command.prepend( clientMachine ); + command.prepend( xonCommand ); // "xon" by default + } + int n = command.count(); + QCString app = command[0].latin1(); + QValueList argList; + for ( int i=1; i < n; i++) + argList.append( QCString(command[i].latin1())); + DCOPRef( launcher ).send( "exec_blind", app, DCOPArg( argList, "QValueList" ) ); +} + +/*! Utility function to execute a command on the local machine. Used + * to discard session data + */ +void KSMServer::executeCommand( const QStringList& command ) +{ + if ( command.isEmpty() ) + return; + KProcess proc; + for ( QStringList::ConstIterator it = command.begin(); + it != command.end(); ++it ) + proc << (*it).latin1(); + proc.start( KProcess::Block ); +} + +IceAuthDataEntry *authDataEntries = 0; +static KTempFile *remAuthFile = 0; + +static IceListenObj *listenObjs = 0; +int numTransports = 0; +static bool only_local = 0; + +static Bool HostBasedAuthProc ( char* /*hostname*/) +{ + if (only_local) + return true; + else + return false; +} + + +Status KSMRegisterClientProc ( + SmsConn /* smsConn */, + SmPointer managerData, + char * previousId +) +{ + KSMClient* client = (KSMClient*) managerData; + client->registerClient( previousId ); + return 1; +} + +void KSMInteractRequestProc ( + SmsConn /* smsConn */, + SmPointer managerData, + int dialogType +) +{ + the_server->interactRequest( (KSMClient*) managerData, dialogType ); +} + +void KSMInteractDoneProc ( + SmsConn /* smsConn */, + SmPointer managerData, + Bool cancelShutdown +) +{ + the_server->interactDone( (KSMClient*) managerData, cancelShutdown ); +} + +void KSMSaveYourselfRequestProc ( + SmsConn smsConn , + SmPointer /* managerData */, + int saveType, + Bool shutdown, + int interactStyle, + Bool fast, + Bool global +) +{ + if ( shutdown ) { + the_server->shutdown( fast ? + KApplication::ShutdownConfirmNo : + KApplication::ShutdownConfirmDefault, + KApplication::ShutdownTypeDefault, + KApplication::ShutdownModeDefault ); + } else if ( !global ) { + SmsSaveYourself( smsConn, saveType, false, interactStyle, fast ); + SmsSaveComplete( smsConn ); + } + // else checkpoint only, ksmserver does not yet support this + // mode. Will come for KDE 3.1 +} + +void KSMSaveYourselfPhase2RequestProc ( + SmsConn /* smsConn */, + SmPointer managerData +) +{ + the_server->phase2Request( (KSMClient*) managerData ); +} + +void KSMSaveYourselfDoneProc ( + SmsConn /* smsConn */, + SmPointer managerData, + Bool success +) +{ + the_server->saveYourselfDone( (KSMClient*) managerData, success ); +} + +void KSMCloseConnectionProc ( + SmsConn smsConn, + SmPointer managerData, + int count, + char ** reasonMsgs +) +{ + the_server->deleteClient( ( KSMClient* ) managerData ); + if ( count ) + SmFreeReasons( count, reasonMsgs ); + IceConn iceConn = SmsGetIceConnection( smsConn ); + SmsCleanUp( smsConn ); + IceSetShutdownNegotiation (iceConn, False); + IceCloseConnection( iceConn ); +} + +void KSMSetPropertiesProc ( + SmsConn /* smsConn */, + SmPointer managerData, + int numProps, + SmProp ** props +) +{ + KSMClient* client = ( KSMClient* ) managerData; + for ( int i = 0; i < numProps; i++ ) { + SmProp *p = client->property( props[i]->name ); + if ( p ) { + client->properties.removeRef( p ); + SmFreeProperty( p ); + } + client->properties.append( props[i] ); + if ( !qstrcmp( props[i]->name, SmProgram ) ) + the_server->clientSetProgram( client ); + } + + if ( numProps ) + free( props ); + +} + +void KSMDeletePropertiesProc ( + SmsConn /* smsConn */, + SmPointer managerData, + int numProps, + char ** propNames +) +{ + KSMClient* client = ( KSMClient* ) managerData; + for ( int i = 0; i < numProps; i++ ) { + SmProp *p = client->property( propNames[i] ); + if ( p ) { + client->properties.removeRef( p ); + SmFreeProperty( p ); + } + } +} + +void KSMGetPropertiesProc ( + SmsConn smsConn, + SmPointer managerData +) +{ + KSMClient* client = ( KSMClient* ) managerData; + SmProp** props = new SmProp*[client->properties.count()]; + int i = 0; + for ( SmProp* prop = client->properties.first(); prop; prop = client->properties.next() ) + props[i++] = prop; + + SmsReturnProperties( smsConn, i, props ); + delete [] props; +} + + +class KSMListener : public QSocketNotifier +{ +public: + KSMListener( IceListenObj obj ) + : QSocketNotifier( IceGetListenConnectionNumber( obj ), + QSocketNotifier::Read, 0, 0) +{ + listenObj = obj; +} + + IceListenObj listenObj; +}; + +class KSMConnection : public QSocketNotifier +{ + public: + KSMConnection( IceConn conn ) + : QSocketNotifier( IceConnectionNumber( conn ), + QSocketNotifier::Read, 0, 0 ) + { + iceConn = conn; + } + + IceConn iceConn; +}; + + +/* for printing hex digits */ +static void fprintfhex (FILE *fp, unsigned int len, char *cp) +{ + static const char hexchars[] = "0123456789abcdef"; + + for (; len > 0; len--, cp++) { + unsigned char s = *cp; + putc(hexchars[s >> 4], fp); + putc(hexchars[s & 0x0f], fp); + } +} + +/* + * We use temporary files which contain commands to add/remove entries from + * the .ICEauthority file. + */ +static void write_iceauth (FILE *addfp, FILE *removefp, IceAuthDataEntry *entry) +{ + fprintf (addfp, + "add %s \"\" %s %s ", + entry->protocol_name, + entry->network_id, + entry->auth_name); + fprintfhex (addfp, entry->auth_data_length, entry->auth_data); + fprintf (addfp, "\n"); + + fprintf (removefp, + "remove protoname=%s protodata=\"\" netid=%s authname=%s\n", + entry->protocol_name, + entry->network_id, + entry->auth_name); +} + + +#define MAGIC_COOKIE_LEN 16 + +Status SetAuthentication_local (int count, IceListenObj *listenObjs) +{ + int i; + for (i = 0; i < count; i ++) { + char *prot = IceGetListenConnectionString(listenObjs[i]); + if (!prot) continue; + char *host = strchr(prot, '/'); + char *sock = 0; + if (host) { + *host=0; + host++; + sock = strchr(host, ':'); + if (sock) { + *sock = 0; + sock++; + } + } + kdDebug( 1218 ) << "KSMServer: SetAProc_loc: conn " << (unsigned)i << ", prot=" << prot << ", file=" << sock << endl; + if (sock && !strcmp(prot, "local")) { + chmod(sock, 0700); + } + IceSetHostBasedAuthProc (listenObjs[i], HostBasedAuthProc); + free(prot); + } + return 1; +} + +Status SetAuthentication (int count, IceListenObj *listenObjs, + IceAuthDataEntry **authDataEntries) +{ + KTempFile addAuthFile; + addAuthFile.setAutoDelete(true); + + remAuthFile = new KTempFile; + remAuthFile->setAutoDelete(true); + + if ((addAuthFile.status() != 0) || (remAuthFile->status() != 0)) + return 0; + + if ((*authDataEntries = (IceAuthDataEntry *) malloc ( + count * 2 * sizeof (IceAuthDataEntry))) == NULL) + return 0; + + for (int i = 0; i < numTransports * 2; i += 2) { + (*authDataEntries)[i].network_id = + IceGetListenConnectionString (listenObjs[i/2]); + (*authDataEntries)[i].protocol_name = (char *) "ICE"; + (*authDataEntries)[i].auth_name = (char *) "MIT-MAGIC-COOKIE-1"; + + (*authDataEntries)[i].auth_data = + IceGenerateMagicCookie (MAGIC_COOKIE_LEN); + (*authDataEntries)[i].auth_data_length = MAGIC_COOKIE_LEN; + + (*authDataEntries)[i+1].network_id = + IceGetListenConnectionString (listenObjs[i/2]); + (*authDataEntries)[i+1].protocol_name = (char *) "XSMP"; + (*authDataEntries)[i+1].auth_name = (char *) "MIT-MAGIC-COOKIE-1"; + + (*authDataEntries)[i+1].auth_data = + IceGenerateMagicCookie (MAGIC_COOKIE_LEN); + (*authDataEntries)[i+1].auth_data_length = MAGIC_COOKIE_LEN; + + write_iceauth (addAuthFile.fstream(), remAuthFile->fstream(), &(*authDataEntries)[i]); + write_iceauth (addAuthFile.fstream(), remAuthFile->fstream(), &(*authDataEntries)[i+1]); + + IceSetPaAuthData (2, &(*authDataEntries)[i]); + + IceSetHostBasedAuthProc (listenObjs[i/2], HostBasedAuthProc); + } + addAuthFile.close(); + remAuthFile->close(); + + QString iceAuth = KGlobal::dirs()->findExe("iceauth"); + if (iceAuth.isEmpty()) + { + qWarning("KSMServer: could not find iceauth"); + return 0; + } + + KProcess p; + p << iceAuth << "source" << addAuthFile.name(); + p.start(KProcess::Block); + + return (1); +} + +/* + * Free up authentication data. + */ +void FreeAuthenticationData(int count, IceAuthDataEntry *authDataEntries) +{ + /* Each transport has entries for ICE and XSMP */ + if (only_local) + return; + + for (int i = 0; i < count * 2; i++) { + free (authDataEntries[i].network_id); + free (authDataEntries[i].auth_data); + } + + free (authDataEntries); + + QString iceAuth = KGlobal::dirs()->findExe("iceauth"); + if (iceAuth.isEmpty()) + { + qWarning("KSMServer: could not find iceauth"); + return; + } + + KProcess p; + p << iceAuth << "source" << remAuthFile->name(); + p.start(KProcess::Block); + + delete remAuthFile; + remAuthFile = 0; +} + +static int Xio_ErrorHandler( Display * ) +{ + qWarning("ksmserver: Fatal IO error: client killed"); + + // Don't do anything that might require the X connection + if (the_server) + { + KSMServer *server = the_server; + the_server = 0; + server->cleanUp(); + // Don't delete server!! + } + + exit(0); // Don't report error, it's not our fault. +} + + +void KSMServer::setupXIOErrorHandler() +{ + XSetIOErrorHandler(Xio_ErrorHandler); +} + +static void sighandler(int sig) +{ + if (sig == SIGHUP) { + signal(SIGHUP, sighandler); + return; + } + + if (the_server) + { + KSMServer *server = the_server; + the_server = 0; + server->cleanUp(); + delete server; + } + + if (kapp) + kapp->quit(); + //::exit(0); +} + + +void KSMWatchProc ( IceConn iceConn, IcePointer client_data, Bool opening, IcePointer* watch_data) +{ + KSMServer* ds = ( KSMServer*) client_data; + + if (opening) { + *watch_data = (IcePointer) ds->watchConnection( iceConn ); + } + else { + ds->removeConnection( (KSMConnection*) *watch_data ); + } +} + +static Status KSMNewClientProc ( SmsConn conn, SmPointer manager_data, + unsigned long* mask_ret, SmsCallbacks* cb, char** failure_reason_ret) +{ + *failure_reason_ret = 0; + + void* client = ((KSMServer*) manager_data )->newClient( conn ); + + cb->register_client.callback = KSMRegisterClientProc; + cb->register_client.manager_data = client; + cb->interact_request.callback = KSMInteractRequestProc; + cb->interact_request.manager_data = client; + cb->interact_done.callback = KSMInteractDoneProc; + cb->interact_done.manager_data = client; + cb->save_yourself_request.callback = KSMSaveYourselfRequestProc; + cb->save_yourself_request.manager_data = client; + cb->save_yourself_phase2_request.callback = KSMSaveYourselfPhase2RequestProc; + cb->save_yourself_phase2_request.manager_data = client; + cb->save_yourself_done.callback = KSMSaveYourselfDoneProc; + cb->save_yourself_done.manager_data = client; + cb->close_connection.callback = KSMCloseConnectionProc; + cb->close_connection.manager_data = client; + cb->set_properties.callback = KSMSetPropertiesProc; + cb->set_properties.manager_data = client; + cb->delete_properties.callback = KSMDeletePropertiesProc; + cb->delete_properties.manager_data = client; + cb->get_properties.callback = KSMGetPropertiesProc; + cb->get_properties.manager_data = client; + + *mask_ret = SmsRegisterClientProcMask | + SmsInteractRequestProcMask | + SmsInteractDoneProcMask | + SmsSaveYourselfRequestProcMask | + SmsSaveYourselfP2RequestProcMask | + SmsSaveYourselfDoneProcMask | + SmsCloseConnectionProcMask | + SmsSetPropertiesProcMask | + SmsDeletePropertiesProcMask | + SmsGetPropertiesProcMask; + return 1; +} + + +#ifdef HAVE__ICETRANSNOLISTEN +extern "C" int _IceTransNoListen(const char * protocol); +#endif + +KSMServer::KSMServer( const QString& windowManager, bool _only_local ) + : DCOPObject("ksmserver"), sessionGroup( "" ) +{ + the_server = this; + clean = false; + wm = windowManager; + + shutdownType = KApplication::ShutdownTypeNone; + + state = Idle; + dialogActive = false; + saveSession = false; + wmPhase1WaitingCount = 0; + KConfig* config = KGlobal::config(); + config->setGroup("General" ); + clientInteracting = 0; + xonCommand = config->readEntry( "xonCommand", "xon" ); + + connect( &knotifyTimeoutTimer, SIGNAL( timeout()), SLOT( knotifyTimeout())); + connect( &startupSuspendTimeoutTimer, SIGNAL( timeout()), SLOT( startupSuspendTimeout())); + connect( &pendingShutdown, SIGNAL( timeout()), SLOT( pendingShutdownTimeout())); + + only_local = _only_local; +#ifdef HAVE__ICETRANSNOLISTEN + if (only_local) + _IceTransNoListen("tcp"); +#else + only_local = false; +#endif + + launcher = KApplication::launcher(); + + char errormsg[256]; + if (!SmsInitialize ( (char*) KSMVendorString, (char*) KSMReleaseString, + KSMNewClientProc, + (SmPointer) this, + HostBasedAuthProc, 256, errormsg ) ) { + + qWarning("KSMServer: could not register XSM protocol"); + } + + if (!IceListenForConnections (&numTransports, &listenObjs, + 256, errormsg)) + { + qWarning("KSMServer: Error listening for connections: %s", errormsg); + qWarning("KSMServer: Aborting."); + exit(1); + } + + { + // publish available transports. + QCString fName = QFile::encodeName(locateLocal("socket", "KSMserver")); + QCString display = ::getenv("DISPLAY"); + // strip the screen number from the display + display.replace(QRegExp("\\.[0-9]+$"), ""); + int i; + while( (i = display.find(':')) >= 0) + display[i] = '_'; + + fName += "_"+display; + FILE *f; + f = ::fopen(fName.data(), "w+"); + if (!f) + { + qWarning("KSMServer: can't open %s: %s", fName.data(), strerror(errno)); + qWarning("KSMServer: Aborting."); + exit(1); + } + char* session_manager = IceComposeNetworkIdList(numTransports, listenObjs); + fprintf(f, "%s\n%i\n", session_manager, getpid()); + fclose(f); + setenv( "SESSION_MANAGER", session_manager, true ); + // Pass env. var to kdeinit. + DCOPRef( launcher ).send( "setLaunchEnv", "SESSION_MANAGER", (const char*) session_manager ); + } + + if (only_local) { + if (!SetAuthentication_local(numTransports, listenObjs)) + qFatal("KSMSERVER: authentication setup failed."); + } else { + if (!SetAuthentication(numTransports, listenObjs, &authDataEntries)) + qFatal("KSMSERVER: authentication setup failed."); + } + + IceAddConnectionWatch (KSMWatchProc, (IcePointer) this); + + listener.setAutoDelete( true ); + KSMListener* con; + for ( int i = 0; i < numTransports; i++) { + con = new KSMListener( listenObjs[i] ); + listener.append( con ); + connect( con, SIGNAL( activated(int) ), this, SLOT( newConnection(int) ) ); + } + + signal(SIGHUP, sighandler); + signal(SIGTERM, sighandler); + signal(SIGINT, sighandler); + signal(SIGPIPE, SIG_IGN); + + connect( &protectionTimer, SIGNAL( timeout() ), this, SLOT( protectionTimeout() ) ); + connect( &restoreTimer, SIGNAL( timeout() ), this, SLOT( tryRestoreNext() ) ); + connect( kapp, SIGNAL( shutDown() ), this, SLOT( cleanUp() ) ); +} + +KSMServer::~KSMServer() +{ + the_server = 0; + cleanUp(); +} + +void KSMServer::cleanUp() +{ + if (clean) return; + clean = true; + IceFreeListenObjs (numTransports, listenObjs); + + QCString fName = QFile::encodeName(locateLocal("socket", "KSMserver")); + QCString display = ::getenv("DISPLAY"); + // strip the screen number from the display + display.replace(QRegExp("\\.[0-9]+$"), ""); + int i; + while( (i = display.find(':')) >= 0) + display[i] = '_'; + + fName += "_"+display; + ::unlink(fName.data()); + + FreeAuthenticationData(numTransports, authDataEntries); + signal(SIGTERM, SIG_DFL); + signal(SIGINT, SIG_DFL); + + DM().shutdown( shutdownType, shutdownMode, bootOption ); +} + + + +void* KSMServer::watchConnection( IceConn iceConn ) +{ + KSMConnection* conn = new KSMConnection( iceConn ); + connect( conn, SIGNAL( activated(int) ), this, SLOT( processData(int) ) ); + return (void*) conn; +} + +void KSMServer::removeConnection( KSMConnection* conn ) +{ + delete conn; +} + + +/*! + Called from our IceIoErrorHandler + */ +void KSMServer::ioError( IceConn /*iceConn*/ ) +{ +} + +void KSMServer::processData( int /*socket*/ ) +{ + IceConn iceConn = ((KSMConnection*)sender())->iceConn; + IceProcessMessagesStatus status = IceProcessMessages( iceConn, 0, 0 ); + if ( status == IceProcessMessagesIOError ) { + IceSetShutdownNegotiation( iceConn, False ); + QPtrListIterator it ( clients ); + while ( it.current() &&SmsGetIceConnection( it.current()->connection() ) != iceConn ) + ++it; + if ( it.current() ) { + SmsConn smsConn = it.current()->connection(); + deleteClient( it.current() ); + SmsCleanUp( smsConn ); + } + (void) IceCloseConnection( iceConn ); + } +} + +KSMClient* KSMServer::newClient( SmsConn conn ) +{ + KSMClient* client = new KSMClient( conn ); + clients.append( client ); + return client; +} + +void KSMServer::deleteClient( KSMClient* client ) +{ + if ( clients.findRef( client ) == -1 ) // paranoia + return; + clients.removeRef( client ); + if ( client == clientInteracting ) { + clientInteracting = 0; + handlePendingInteractions(); + } + delete client; + if ( state == Shutdown || state == Checkpoint ) + completeShutdownOrCheckpoint(); + if ( state == Killing ) + completeKilling(); + if ( state == KillingWM ) + completeKillingWM(); +} + +void KSMServer::newConnection( int /*socket*/ ) +{ + IceAcceptStatus status; + IceConn iceConn = IceAcceptConnection( ((KSMListener*)sender())->listenObj, &status); + IceSetShutdownNegotiation( iceConn, False ); + IceConnectStatus cstatus; + while ((cstatus = IceConnectionStatus (iceConn))==IceConnectPending) { + (void) IceProcessMessages( iceConn, 0, 0 ); + } + + if (cstatus != IceConnectAccepted) { + if (cstatus == IceConnectIOError) + kdDebug( 1218 ) << "IO error opening ICE Connection!" << endl; + else + kdDebug( 1218 ) << "ICE Connection rejected!" << endl; + (void )IceCloseConnection (iceConn); + } +} + + +QString KSMServer::currentSession() +{ + if ( sessionGroup.startsWith( "Session: " ) ) + return sessionGroup.mid( 9 ); + return ""; // empty, not null, since used for KConfig::setGroup +} + +void KSMServer::discardSession() +{ + KConfig* config = KGlobal::config(); + config->setGroup( sessionGroup ); + int count = config->readNumEntry( "count", 0 ); + for ( KSMClient* c = clients.first(); c; c = clients.next() ) { + QStringList discardCommand = c->discardCommand(); + if ( discardCommand.isEmpty()) + continue; + // check that non of the old clients used the exactly same + // discardCommand before we execute it. This used to be the + // case up to KDE and Qt < 3.1 + int i = 1; + while ( i <= count && + config->readPathListEntry( QString("discardCommand") + QString::number(i) ) != discardCommand ) + i++; + if ( i <= count ) + executeCommand( discardCommand ); + } +} + +void KSMServer::storeSession() +{ + KConfig* config = KGlobal::config(); + config->reparseConfiguration(); // config may have changed in the KControl module + config->setGroup("General" ); + excludeApps = QStringList::split( QRegExp( "[,:]" ), config->readEntry( "excludeApps" ).lower()); + config->setGroup( sessionGroup ); + int count = config->readNumEntry( "count" ); + for ( int i = 1; i <= count; i++ ) { + QStringList discardCommand = config->readPathListEntry( QString("discardCommand") + QString::number(i) ); + if ( discardCommand.isEmpty()) + continue; + // check that non of the new clients uses the exactly same + // discardCommand before we execute it. This used to be the + // case up to KDE and Qt < 3.1 + KSMClient* c = clients.first(); + while ( c && discardCommand != c->discardCommand() ) + c = clients.next(); + if ( c ) + continue; + executeCommand( discardCommand ); + } + config->deleteGroup( sessionGroup ); //### does not work with global config object... + config->setGroup( sessionGroup ); + count = 0; + + if ( !wm.isEmpty() ) { + // put the wm first + for ( KSMClient* c = clients.first(); c; c = clients.next() ) + if ( c->program() == wm ) { + clients.prepend( clients.take() ); + break; + } + } + + for ( KSMClient* c = clients.first(); c; c = clients.next() ) { + int restartHint = c->restartStyleHint(); + if (restartHint == SmRestartNever) + continue; + QString program = c->program(); + QStringList restartCommand = c->restartCommand(); + if (program.isEmpty() && restartCommand.isEmpty()) + continue; + if (excludeApps.contains( program.lower())) + continue; + + count++; + QString n = QString::number(count); + config->writeEntry( QString("program")+n, program ); + config->writeEntry( QString("clientId")+n, c->clientId() ); + config->writeEntry( QString("restartCommand")+n, restartCommand ); + config->writePathEntry( QString("discardCommand")+n, c->discardCommand() ); + config->writeEntry( QString("restartStyleHint")+n, restartHint ); + config->writeEntry( QString("userId")+n, c->userId() ); + config->writeEntry( QString("wasWm")+n, isWM( c )); + } + config->writeEntry( "count", count ); + + config->setGroup("General"); + config->writeEntry( "screenCount", ScreenCount(qt_xdisplay())); + + storeLegacySession( config ); + config->sync(); +} + + +QStringList KSMServer::sessionList() +{ + QStringList sessions = "default"; + KConfig* config = KGlobal::config(); + QStringList groups = config->groupList(); + for ( QStringList::ConstIterator it = groups.begin(); it != groups.end(); it++ ) + if ( (*it).startsWith( "Session: " ) ) + sessions << (*it).mid( 9 ); + return sessions; +} + +bool KSMServer::isWM( const KSMClient* client ) const +{ + return isWM( client->program()); +} + +bool KSMServer::isWM( const QString& program ) const +{ + // KWin relies on ksmserver's special treatment in phase1, + // therefore make sure it's recognized even if ksmserver + // was initially started with different WM, and kwin replaced + // it later + return program == wm || program == "kwin"; +} + +bool KSMServer::defaultSession() const +{ + return sessionGroup.isEmpty(); +} diff --git a/ksmserver/server.h b/ksmserver/server.h new file mode 100644 index 000000000..feb6004ef --- /dev/null +++ b/ksmserver/server.h @@ -0,0 +1,219 @@ +/***************************************************************** +ksmserver - the KDE session management server + +Copyright (C) 2000 Matthias Ettrich +******************************************************************/ + +#ifndef SERVER_H +#define SERVER_H + +// needed to avoid clash with INT8 defined in X11/Xmd.h on solaris +#define QT_CLEAN_NAMESPACE 1 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "server2.h" + +#include "KSMServerInterface.h" + +#define SESSION_PREVIOUS_LOGOUT "saved at previous logout" +#define SESSION_BY_USER "saved by user" + +typedef QValueList QCStringList; +class KSMListener; +class KSMConnection; +class KSMClient; + +enum SMType { SM_ERROR, SM_WMCOMMAND, SM_WMSAVEYOURSELF }; +struct SMData + { + SMType type; + QStringList wmCommand; + QString wmClientMachine; + QString wmclass1, wmclass2; + }; +typedef QMap WindowMap; + +class KSMServer : public QObject, public KSMServerInterface +{ +Q_OBJECT +K_DCOP +k_dcop: + void notifySlot(QString,QString,QString,QString,QString,int,int,int,int); + void logoutSoundFinished(int,int); + void autoStart0Done(); + void autoStart1Done(); + void autoStart2Done(); + void kcmPhase1Done(); + void kcmPhase2Done(); +public: + KSMServer( const QString& windowManager, bool only_local ); + ~KSMServer(); + + static KSMServer* self(); + + void* watchConnection( IceConn iceConn ); + void removeConnection( KSMConnection* conn ); + + KSMClient* newClient( SmsConn ); + void deleteClient( KSMClient* client ); + + // callbacks + void saveYourselfDone( KSMClient* client, bool success ); + void interactRequest( KSMClient* client, int dialogType ); + void interactDone( KSMClient* client, bool cancelShutdown ); + void phase2Request( KSMClient* client ); + + // error handling + void ioError( IceConn iceConn ); + + // notification + void clientSetProgram( KSMClient* client ); + void clientRegistered( const char* previousId ); + + // public API + void restoreSession( QString sessionName ); + void startDefaultSession(); + void shutdown( KApplication::ShutdownConfirm confirm, + KApplication::ShutdownType sdtype, + KApplication::ShutdownMode sdmode ); + + virtual void suspendStartup( QCString app ); + virtual void resumeStartup( QCString app ); + +public slots: + void cleanUp(); + +private slots: + void newConnection( int socket ); + void processData( int socket ); + void restoreSessionInternal(); + void restoreSessionDoneInternal(); + + void protectionTimeout(); + void timeoutQuit(); + void timeoutWMQuit(); + void knotifyTimeout(); + void kcmPhase1Timeout(); + void kcmPhase2Timeout(); + void pendingShutdownTimeout(); + + void autoStart0(); + void autoStart1(); + void autoStart2(); + void tryRestoreNext(); + void startupSuspendTimeout(); + +private: + void handlePendingInteractions(); + void completeShutdownOrCheckpoint(); + void startKilling(); + void performStandardKilling(); + void completeKilling(); + void killWM(); + void completeKillingWM(); + void cancelShutdown( KSMClient* c ); + void killingCompleted(); + + void discardSession(); + void storeSession(); + + void startProtection(); + void endProtection(); + + void startApplication( QStringList command, + const QString& clientMachine = QString::null, + const QString& userId = QString::null ); + void executeCommand( const QStringList& command ); + + bool isWM( const KSMClient* client ) const; + bool isWM( const QString& program ) const; + bool defaultSession() const; // empty session + void setupXIOErrorHandler(); + + void performLegacySessionSave(); + void storeLegacySession( KConfig* config ); + void restoreLegacySession( KConfig* config ); + void restoreLegacySessionInternal( KConfig* config, char sep = ',' ); + QStringList windowWmCommand(WId w); + QString windowWmClientMachine(WId w); + WId windowWmClientLeader(WId w); + QCString windowSessionId(WId w, WId leader); + + bool checkStartupSuspend(); + void finishStartup(); + void resumeStartupInternal(); + + // public dcop interface + void logout( int, int, int ); + QStringList sessionList(); + QString currentSession(); + void saveCurrentSession(); + void saveCurrentSessionAs( QString ); + + private: + QPtrList listener; + QPtrList clients; + + enum State + { + Idle, + LaunchingWM, AutoStart0, KcmInitPhase1, AutoStart1, Restoring, FinishingStartup, // startup + Shutdown, Checkpoint, Killing, KillingWM, WaitingForKNotify // shutdown + }; + State state; + bool dialogActive; + bool saveSession; + int wmPhase1WaitingCount; + int saveType; + QMap< QCString, int > startupSuspendCount; + + KApplication::ShutdownType shutdownType; + KApplication::ShutdownMode shutdownMode; + QString bootOption; + + bool clean; + KSMClient* clientInteracting; + QString wm; + QString sessionGroup; + QString sessionName; + QCString launcher; + QTimer protectionTimer; + QTimer restoreTimer; + QString xonCommand; + int logoutSoundEvent; + QTimer knotifyTimeoutTimer; + QTimer startupSuspendTimeoutTimer; + bool waitAutoStart2; + bool waitKcmInit2; + QTimer pendingShutdown; + KApplication::ShutdownConfirm pendingShutdown_confirm; + KApplication::ShutdownType pendingShutdown_sdtype; + KApplication::ShutdownMode pendingShutdown_sdmode; + + // ksplash interface + void upAndRunning( const QString& msg ); + void publishProgress( int progress, bool max = false ); + + // sequential startup + int appsToStart; + int lastAppStarted; + QString lastIdStarted; + + QStringList excludeApps; + + WindowMap legacyWindows; +}; + +#endif diff --git a/ksmserver/server2.h b/ksmserver/server2.h new file mode 100644 index 000000000..e16c9575c --- /dev/null +++ b/ksmserver/server2.h @@ -0,0 +1,16 @@ +// This is in a separate file only because dcopidl doesn't handle +// the extern "C" { ... } construct. + +#define INT32 QINT32 +#include +#include +#include +extern "C" { +#include +#include +#include +#include +#include +} + +#include diff --git a/ksmserver/shutdown.cpp b/ksmserver/shutdown.cpp new file mode 100644 index 000000000..1c3906e44 --- /dev/null +++ b/ksmserver/shutdown.cpp @@ -0,0 +1,563 @@ +/***************************************************************** +ksmserver - the KDE session management server + +Copyright (C) 2000 Matthias Ettrich +Copyright (C) 2005 Lubos Lunak + +relatively small extensions by Oswald Buddenhagen + +some code taken from the dcopserver (part of the KDE libraries), which is +Copyright (c) 1999 Matthias Ettrich +Copyright (c) 1999 Preston Brown + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +******************************************************************/ + + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_LIMITS_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "server.h" +#include "global.h" +#include "shutdowndlg.h" +#include "client.h" + +void KSMServer::logout( int confirm, int sdtype, int sdmode ) +{ + shutdown( (KApplication::ShutdownConfirm)confirm, + (KApplication::ShutdownType)sdtype, + (KApplication::ShutdownMode)sdmode ); +} + +void KSMServer::shutdown( KApplication::ShutdownConfirm confirm, + KApplication::ShutdownType sdtype, KApplication::ShutdownMode sdmode ) +{ + pendingShutdown.stop(); + if( dialogActive ) + return; + if( state >= Shutdown ) // already performing shutdown + return; + if( state != Idle ) // performing startup + { + // perform shutdown as soon as startup is finished, in order to avoid saving partial session + if( !pendingShutdown.isActive()) + { + pendingShutdown.start( 1000 ); + pendingShutdown_confirm = confirm; + pendingShutdown_sdtype = sdtype; + pendingShutdown_sdmode = sdmode; + } + return; + } + + KConfig *config = KGlobal::config(); + config->reparseConfiguration(); // config may have changed in the KControl module + + config->setGroup("General" ); + bool logoutConfirmed = + (confirm == KApplication::ShutdownConfirmYes) ? false : + (confirm == KApplication::ShutdownConfirmNo) ? true : + !config->readBoolEntry( "confirmLogout", true ); + bool maysd = false; + if (config->readBoolEntry( "offerShutdown", true ) && DM().canShutdown()) + maysd = true; + if (!maysd) { + if (sdtype != KApplication::ShutdownTypeNone && + sdtype != KApplication::ShutdownTypeDefault && + logoutConfirmed) + return; /* unsupported fast shutdown */ + sdtype = KApplication::ShutdownTypeNone; + } else if (sdtype == KApplication::ShutdownTypeDefault) + sdtype = (KApplication::ShutdownType) + config->readNumEntry( "shutdownType", (int)KApplication::ShutdownTypeNone ); + if (sdmode == KApplication::ShutdownModeDefault) + sdmode = KApplication::ShutdownModeInteractive; + + dialogActive = true; + QString bopt; + if ( !logoutConfirmed ) { + KSMShutdownFeedback::start(); // make the screen gray + logoutConfirmed = + KSMShutdownDlg::confirmShutdown( maysd, sdtype, bopt ); + // ###### We can't make the screen remain gray while talking to the apps, + // because this prevents interaction ("do you want to save", etc.) + // TODO: turn the feedback widget into a list of apps to be closed, + // with an indicator of the current status for each. + KSMShutdownFeedback::stop(); // make the screen become normal again + } + + if ( logoutConfirmed ) { + + shutdownType = sdtype; + shutdownMode = sdmode; + bootOption = bopt; + + // shall we save the session on logout? + saveSession = ( config->readEntry( "loginMode", "restorePreviousLogout" ) == "restorePreviousLogout" ); + + if ( saveSession ) + sessionGroup = QString("Session: ") + SESSION_PREVIOUS_LOGOUT; + + // Set the real desktop background to black so that exit looks + // clean regardless of what was on "our" desktop. + kapp->desktop()->setBackgroundColor( Qt::black ); + state = Shutdown; + wmPhase1WaitingCount = 0; + saveType = saveSession?SmSaveBoth:SmSaveGlobal; + performLegacySessionSave(); + startProtection(); + for ( KSMClient* c = clients.first(); c; c = clients.next() ) { + c->resetState(); + // Whoever came with the idea of phase 2 got it backwards + // unfortunately. Window manager should be the very first + // one saving session data, not the last one, as possible + // user interaction during session save may alter + // window positions etc. + // Moreover, KWin's focus stealing prevention would lead + // to undesired effects while session saving (dialogs + // wouldn't be activated), so it needs be assured that + // KWin will turn it off temporarily before any other + // user interaction takes place. + // Therefore, make sure the WM finishes its phase 1 + // before others a chance to change anything. + // KWin will check if the session manager is ksmserver, + // and if yes it will save in phase 1 instead of phase 2. + if( isWM( c )) { + ++wmPhase1WaitingCount; + SmsSaveYourself( c->connection(), saveType, + true, SmInteractStyleAny, false ); + } + + } + if( wmPhase1WaitingCount == 0 ) { // no WM, simply start them all + for ( KSMClient* c = clients.first(); c; c = clients.next() ) + SmsSaveYourself( c->connection(), saveType, + true, SmInteractStyleAny, false ); + } + if ( clients.isEmpty() ) + completeShutdownOrCheckpoint(); + } + dialogActive = false; +} + +void KSMServer::pendingShutdownTimeout() +{ + shutdown( pendingShutdown_confirm, pendingShutdown_sdtype, pendingShutdown_sdmode ); +} + +void KSMServer::saveCurrentSession() +{ + if ( state != Idle || dialogActive ) + return; + + if ( currentSession().isEmpty() || currentSession() == SESSION_PREVIOUS_LOGOUT ) + sessionGroup = QString("Session: ") + SESSION_BY_USER; + + state = Checkpoint; + wmPhase1WaitingCount = 0; + saveType = SmSaveLocal; + saveSession = true; + performLegacySessionSave(); + for ( KSMClient* c = clients.first(); c; c = clients.next() ) { + c->resetState(); + if( isWM( c )) { + ++wmPhase1WaitingCount; + SmsSaveYourself( c->connection(), saveType, false, SmInteractStyleNone, false ); + } + } + if( wmPhase1WaitingCount == 0 ) { + for ( KSMClient* c = clients.first(); c; c = clients.next() ) + SmsSaveYourself( c->connection(), saveType, false, SmInteractStyleNone, false ); + } + if ( clients.isEmpty() ) + completeShutdownOrCheckpoint(); +} + +void KSMServer::saveCurrentSessionAs( QString session ) +{ + if ( state != Idle || dialogActive ) + return; + sessionGroup = "Session: " + session; + saveCurrentSession(); +} + +// callbacks +void KSMServer::saveYourselfDone( KSMClient* client, bool success ) +{ + if ( state == Idle ) { + // State saving when it's not shutdown or checkpoint. Probably + // a shutdown was cancelled and the client is finished saving + // only now. Discard the saved state in order to avoid + // the saved data building up. + QStringList discard = client->discardCommand(); + if( !discard.isEmpty()) + executeCommand( discard ); + return; + } + if ( success ) { + client->saveYourselfDone = true; + completeShutdownOrCheckpoint(); + } else { + // fake success to make KDE's logout not block with broken + // apps. A perfect ksmserver would display a warning box at + // the very end. + client->saveYourselfDone = true; + completeShutdownOrCheckpoint(); + } + startProtection(); + if( isWM( client ) && !client->wasPhase2 && wmPhase1WaitingCount > 0 ) { + --wmPhase1WaitingCount; + // WM finished its phase1, save the rest + if( wmPhase1WaitingCount == 0 ) { + for ( KSMClient* c = clients.first(); c; c = clients.next() ) + if( !isWM( c )) + SmsSaveYourself( c->connection(), saveType, saveType != SmSaveLocal, + saveType != SmSaveLocal ? SmInteractStyleAny : SmInteractStyleNone, + false ); + } + } +} + +void KSMServer::interactRequest( KSMClient* client, int /*dialogType*/ ) +{ + if ( state == Shutdown ) + client->pendingInteraction = true; + else + SmsInteract( client->connection() ); + + handlePendingInteractions(); +} + +void KSMServer::interactDone( KSMClient* client, bool cancelShutdown_ ) +{ + if ( client != clientInteracting ) + return; // should not happen + clientInteracting = 0; + if ( cancelShutdown_ ) + cancelShutdown( client ); + else + handlePendingInteractions(); +} + + +void KSMServer::phase2Request( KSMClient* client ) +{ + client->waitForPhase2 = true; + client->wasPhase2 = true; + completeShutdownOrCheckpoint(); + if( isWM( client ) && wmPhase1WaitingCount > 0 ) { + --wmPhase1WaitingCount; + // WM finished its phase1 and requests phase2, save the rest + if( wmPhase1WaitingCount == 0 ) { + for ( KSMClient* c = clients.first(); c; c = clients.next() ) + if( !isWM( c )) + SmsSaveYourself( c->connection(), saveType, saveType != SmSaveLocal, + saveType != SmSaveLocal ? SmInteractStyleAny : SmInteractStyleNone, + false ); + } + } +} + +void KSMServer::handlePendingInteractions() +{ + if ( clientInteracting ) + return; + + for ( KSMClient* c = clients.first(); c; c = clients.next() ) { + if ( c->pendingInteraction ) { + clientInteracting = c; + c->pendingInteraction = false; + break; + } + } + if ( clientInteracting ) { + endProtection(); + SmsInteract( clientInteracting->connection() ); + } else { + startProtection(); + } +} + + +void KSMServer::cancelShutdown( KSMClient* c ) +{ + kdDebug( 1218 ) << "Client " << c->program() << " (" << c->clientId() << ") canceled shutdown." << endl; + KNotifyClient::event( 0, "cancellogout", i18n( "Logout canceled by '%1'" ).arg( c->program())); + clientInteracting = 0; + for ( KSMClient* c = clients.first(); c; c = clients.next() ) { + SmsShutdownCancelled( c->connection() ); + if( c->saveYourselfDone ) { + // Discard also saved state. + QStringList discard = c->discardCommand(); + if( !discard.isEmpty()) + executeCommand( discard ); + } + } + state = Idle; +} + +void KSMServer::startProtection() +{ + protectionTimer.start( 10000, true ); +} + +void KSMServer::endProtection() +{ + protectionTimer.stop(); +} + +/* + Internal protection slot, invoked when clients do not react during + shutdown. + */ +void KSMServer::protectionTimeout() +{ + if ( ( state != Shutdown && state != Checkpoint ) || clientInteracting ) + return; + + for ( KSMClient* c = clients.first(); c; c = clients.next() ) { + if ( !c->saveYourselfDone && !c->waitForPhase2 ) { + kdDebug( 1218 ) << "protectionTimeout: client " << c->program() << "(" << c->clientId() << ")" << endl; + c->saveYourselfDone = true; + } + } + completeShutdownOrCheckpoint(); + startProtection(); +} + +void KSMServer::completeShutdownOrCheckpoint() +{ + if ( state != Shutdown && state != Checkpoint ) + return; + + for ( KSMClient* c = clients.first(); c; c = clients.next() ) { + if ( !c->saveYourselfDone && !c->waitForPhase2 ) + return; // not done yet + } + + // do phase 2 + bool waitForPhase2 = false; + for ( KSMClient* c = clients.first(); c; c = clients.next() ) { + if ( !c->saveYourselfDone && c->waitForPhase2 ) { + c->waitForPhase2 = false; + SmsSaveYourselfPhase2( c->connection() ); + waitForPhase2 = true; + } + } + if ( waitForPhase2 ) + return; + + if ( saveSession ) + storeSession(); + else + discardSession(); + + if ( state == Shutdown ) { + bool waitForKNotify = true; + if( !kapp->dcopClient()->connectDCOPSignal( "knotify", "", + "notifySignal(QString,QString,QString,QString,QString,int,int,int,int)", + "ksmserver", "notifySlot(QString,QString,QString,QString,QString,int,int,int,int)", false )) { + waitForKNotify = false; + } + if( !kapp->dcopClient()->connectDCOPSignal( "knotify", "", + "playingFinished(int,int)", + "ksmserver", "logoutSoundFinished(int,int)", false )) { + waitForKNotify = false; + } + // event() can return -1 if KNotifyClient short-circuits and avoids KNotify + logoutSoundEvent = KNotifyClient::event( 0, "exitkde" ); // KDE says good bye + if( logoutSoundEvent <= 0 ) + waitForKNotify = false; + if( waitForKNotify ) { + state = WaitingForKNotify; + knotifyTimeoutTimer.start( 20000, true ); + return; + } + startKilling(); + } else if ( state == Checkpoint ) { + for ( KSMClient* c = clients.first(); c; c = clients.next() ) { + SmsSaveComplete( c->connection()); + } + state = Idle; + } +} + +void KSMServer::startKilling() +{ + knotifyTimeoutTimer.stop(); + // kill all clients + state = Killing; + for ( KSMClient* c = clients.first(); c; c = clients.next() ) { + if( isWM( c )) // kill the WM as the last one in order to reduce flicker + continue; + kdDebug( 1218 ) << "completeShutdown: client " << c->program() << "(" << c->clientId() << ")" << endl; + SmsDie( c->connection() ); + } + + kdDebug( 1218 ) << " We killed all clients. We have now clients.count()=" << + clients.count() << endl; + completeKilling(); + QTimer::singleShot( 10000, this, SLOT( timeoutQuit() ) ); +} + +void KSMServer::completeKilling() +{ + kdDebug( 1218 ) << "KSMServer::completeKilling clients.count()=" << + clients.count() << endl; + if( state == Killing ) { + bool wait = false; + for( KSMClient* c = clients.first(); c; c = clients.next()) { + if( isWM( c )) + continue; + wait = true; // still waiting for clients to go away + } + if( wait ) + return; + killWM(); + } +} + +void KSMServer::killWM() +{ + state = KillingWM; + bool iswm = false; + for ( KSMClient* c = clients.first(); c; c = clients.next() ) { + if( isWM( c )) { + iswm = true; + kdDebug( 1218 ) << "killWM: client " << c->program() << "(" << c->clientId() << ")" << endl; + SmsDie( c->connection() ); + } + } + if( iswm ) { + completeKillingWM(); + QTimer::singleShot( 5000, this, SLOT( timeoutWMQuit() ) ); + } + else + killingCompleted(); +} + +void KSMServer::completeKillingWM() +{ + kdDebug( 1218 ) << "KSMServer::completeKillingWM clients.count()=" << + clients.count() << endl; + if( state == KillingWM ) { + if( clients.isEmpty()) + killingCompleted(); + } +} + +// shutdown is fully complete +void KSMServer::killingCompleted() +{ + kapp->quit(); +} + +// called when KNotify performs notification for logout (not when sound is finished though) +void KSMServer::notifySlot(QString event ,QString app,QString,QString,QString,int present,int,int,int) +{ + if( state != WaitingForKNotify ) + return; + if( event != "exitkde" || app != "ksmserver" ) + return; + if( present & KNotifyClient::Sound ) // logoutSoundFinished() will be called + return; + startKilling(); +} + +// This is stupid. The normal DCOP signal connected to notifySlot() above should be simply +// emitted in KNotify only after the sound is finished playing. +void KSMServer::logoutSoundFinished( int event, int ) +{ + if( state != WaitingForKNotify ) + return; + if( event != logoutSoundEvent ) + return; + startKilling(); +} + +void KSMServer::knotifyTimeout() +{ + if( state != WaitingForKNotify ) + return; + startKilling(); +} + +void KSMServer::timeoutQuit() +{ + for (KSMClient *c = clients.first(); c; c = clients.next()) { + kdWarning( 1218 ) << "SmsDie timeout, client " << c->program() << "(" << c->clientId() << ")" << endl; + } + killWM(); +} + +void KSMServer::timeoutWMQuit() +{ + if( state == KillingWM ) { + kdWarning( 1218 ) << "SmsDie WM timeout" << endl; + } + killingCompleted(); +} diff --git a/ksmserver/shutdowndlg.cpp b/ksmserver/shutdowndlg.cpp new file mode 100644 index 000000000..06bc03c4c --- /dev/null +++ b/ksmserver/shutdowndlg.cpp @@ -0,0 +1,278 @@ +/***************************************************************** +ksmserver - the KDE session management server + +Copyright (C) 2000 Matthias Ettrich +******************************************************************/ + +#include + +#include "shutdowndlg.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "shutdowndlg.moc" + +KSMShutdownFeedback * KSMShutdownFeedback::s_pSelf = 0L; + +KSMShutdownFeedback::KSMShutdownFeedback() + : QWidget( 0L, "feedbackwidget", WType_Popup ), + m_currentY( 0 ) +{ + setBackgroundMode( QWidget::NoBackground ); + setGeometry( QApplication::desktop()->geometry() ); + QTimer::singleShot( 10, this, SLOT( slotPaintEffect() ) ); + m_root.resize( width(), height() ); +} + + +void KSMShutdownFeedback::slotPaintEffect() +{ + if ( m_currentY >= height() ) { + if ( backgroundMode() == QWidget::NoBackground ) { + setBackgroundMode( QWidget::NoBackground ); + setBackgroundPixmap( m_root ); + } + return; + } + + KPixmap pixmap; + pixmap = QPixmap::grabWindow( qt_xrootwin(), 0, m_currentY, width(), 10 ); + QImage image = pixmap.convertToImage(); + KImageEffect::blend( Qt::black, image, 0.4 ); + KImageEffect::toGray( image, true ); + pixmap.convertFromImage( image ); + bitBlt( this, 0, m_currentY, &pixmap ); + bitBlt( &m_root, 0, m_currentY, &pixmap ); + m_currentY += 10; + QTimer::singleShot( 1, this, SLOT( slotPaintEffect() ) ); +} + +////// + +KSMShutdownDlg::KSMShutdownDlg( QWidget* parent, + bool maysd, KApplication::ShutdownType sdtype ) + : QDialog( parent, 0, TRUE, WType_Popup ), targets(0) + // this is a WType_Popup on purpose. Do not change that! Not + // having a popup here has severe side effects. +{ + QVBoxLayout* vbox = new QVBoxLayout( this ); + QFrame* frame = new QFrame( this ); + frame->setFrameStyle( QFrame::StyledPanel | QFrame::Raised ); + frame->setLineWidth( style().pixelMetric( QStyle::PM_DefaultFrameWidth, frame ) ); + vbox->addWidget( frame ); + vbox = new QVBoxLayout( frame, 2 * KDialog::marginHint(), + 2 * KDialog::spacingHint() ); + + QLabel* label = new QLabel( i18n("End Session for \"%1\"").arg(KUser().loginName()), frame ); + QFont fnt = label->font(); + fnt.setBold( true ); + fnt.setPointSize( fnt.pointSize() * 3 / 2 ); + label->setFont( fnt ); + vbox->addWidget( label, 0, AlignHCenter ); + + QHBoxLayout* hbox = new QHBoxLayout( vbox, 2 * KDialog::spacingHint() ); + + // konqy + QFrame* lfrm = new QFrame( frame ); + lfrm->setFrameStyle( QFrame::Panel | QFrame::Sunken ); + hbox->addWidget( lfrm, AlignCenter ); + + QLabel* icon = new QLabel( lfrm ); + icon->setPixmap( UserIcon( "shutdownkonq" ) ); + lfrm->setFixedSize( icon->sizeHint()); + icon->setFixedSize( icon->sizeHint()); + + // right column (buttons) + QVBoxLayout* buttonlay = new QVBoxLayout( hbox, 2 * KDialog::spacingHint() ); + buttonlay->setAlignment( Qt::AlignHCenter ); + + buttonlay->addStretch( 1 ); + + // End session + KPushButton* btnLogout = new KPushButton( KGuiItem( i18n("&End Current Session"), "undo"), frame ); + QFont btnFont = btnLogout->font(); + buttonlay->addWidget( btnLogout ); + connect(btnLogout, SIGNAL(clicked()), SLOT(slotLogout())); + + if (maysd) { + + // Shutdown + KPushButton* btnHalt = new KPushButton( KGuiItem( i18n("&Turn Off Computer"), "exit"), frame ); + btnHalt->setFont( btnFont ); + buttonlay->addWidget( btnHalt ); + connect(btnHalt, SIGNAL(clicked()), SLOT(slotHalt())); + if ( sdtype == KApplication::ShutdownTypeHalt ) + btnHalt->setFocus(); + + // Reboot + KSMDelayedPushButton* btnReboot = new KSMDelayedPushButton( KGuiItem( i18n("&Restart Computer"), "reload"), frame ); + btnReboot->setFont( btnFont ); + buttonlay->addWidget( btnReboot ); + + connect(btnReboot, SIGNAL(clicked()), SLOT(slotReboot())); + if ( sdtype == KApplication::ShutdownTypeReboot ) + btnReboot->setFocus(); + + int def, cur; + if ( DM().bootOptions( rebootOptions, def, cur ) ) { + targets = new QPopupMenu( frame ); + if ( cur == -1 ) + cur = def; + + int index = 0; + for (QStringList::ConstIterator it = rebootOptions.begin(); it != rebootOptions.end(); ++it, ++index) + { + QString label = (*it); + label=label.replace('&',"&&"); + if (index == cur) + targets->insertItem( label + i18n("current option in boot loader", " (current)"), index); + else + targets->insertItem( label, index ); + } + + btnReboot->setPopup(targets); + connect( targets, SIGNAL(activated(int)), SLOT(slotReboot(int)) ); + } + } + + buttonlay->addStretch( 1 ); + + // Separator + buttonlay->addWidget( new KSeparator( frame ) ); + + // Back to Desktop + KPushButton* btnBack = new KPushButton( KStdGuiItem::cancel(), frame ); + buttonlay->addWidget( btnBack ); + connect(btnBack, SIGNAL(clicked()), SLOT(reject())); + +} + + +void KSMShutdownDlg::slotLogout() +{ + m_shutdownType = KApplication::ShutdownTypeNone; + accept(); +} + + +void KSMShutdownDlg::slotReboot() +{ + // no boot option selected -> current + m_bootOption = QString::null; + m_shutdownType = KApplication::ShutdownTypeReboot; + accept(); +} + +void KSMShutdownDlg::slotReboot(int opt) +{ + if (int(rebootOptions.size()) > opt) + m_bootOption = rebootOptions[opt]; + m_shutdownType = KApplication::ShutdownTypeReboot; + accept(); +} + + +void KSMShutdownDlg::slotHalt() +{ + m_bootOption = QString::null; + m_shutdownType = KApplication::ShutdownTypeHalt; + accept(); +} + + +bool KSMShutdownDlg::confirmShutdown( bool maysd, KApplication::ShutdownType& sdtype, QString& bootOption ) +{ + kapp->enableStyles(); + KSMShutdownDlg* l = new KSMShutdownDlg( 0, + //KSMShutdownFeedback::self(), + maysd, sdtype ); + + // Show dialog (will save the background in showEvent) + QSize sh = l->sizeHint(); + QRect rect = KGlobalSettings::desktopGeometry(QCursor::pos()); + + l->move(rect.x() + (rect.width() - sh.width())/2, + rect.y() + (rect.height() - sh.height())/2); + bool result = l->exec(); + sdtype = l->m_shutdownType; + bootOption = l->m_bootOption; + + delete l; + + kapp->disableStyles(); + return result; +} + +KSMDelayedPushButton::KSMDelayedPushButton( const KGuiItem &item, + QWidget *parent, + const char *name) + : KPushButton( item, parent, name), pop(0), popt(0) +{ + connect(this, SIGNAL(pressed()), SLOT(slotPressed())); + connect(this, SIGNAL(released()), SLOT(slotReleased())); + popt = new QTimer(this); + connect(popt, SIGNAL(timeout()), SLOT(slotTimeout())); +} + +void KSMDelayedPushButton::setPopup(QPopupMenu *p) +{ + pop = p; + setIsMenuButton(p != 0); +} + +void KSMDelayedPushButton::slotPressed() +{ + if (pop) + popt->start(QApplication::startDragTime()); +} + +void KSMDelayedPushButton::slotReleased() +{ + popt->stop(); +} + +void KSMDelayedPushButton::slotTimeout() +{ + QPoint bl = mapToGlobal(rect().bottomLeft()); + QWidget *par = (QWidget*)parent(); + QPoint br = par->mapToGlobal(par->rect().bottomRight()); + pop->popup( bl ); + popt->stop(); + setDown(false); +} diff --git a/ksmserver/shutdowndlg.h b/ksmserver/shutdowndlg.h new file mode 100644 index 000000000..9fcb77c51 --- /dev/null +++ b/ksmserver/shutdowndlg.h @@ -0,0 +1,88 @@ +/***************************************************************** +ksmserver - the KDE session management server + +Copyright (C) 2000 Matthias Ettrich +******************************************************************/ + +#ifndef SHUTDOWNDLG_H +#define SHUTDOWNDLG_H + +#include +#include +#include +class QPushButton; +class QVButtonGroup; +class QPopupMenu; +class QTimer; + +#include + +// The (singleton) widget that makes the desktop gray. +class KSMShutdownFeedback : public QWidget +{ + Q_OBJECT + +public: + static void start() { s_pSelf = new KSMShutdownFeedback(); s_pSelf->show(); } + static void stop() { delete s_pSelf; s_pSelf = 0L; } + static KSMShutdownFeedback * self() { return s_pSelf; } + +protected: + ~KSMShutdownFeedback() {} + +private slots: + void slotPaintEffect(); + +private: + static KSMShutdownFeedback * s_pSelf; + KSMShutdownFeedback(); + int m_currentY; + QPixmap m_root; +}; + + +// The confirmation dialog +class KSMShutdownDlg : public QDialog +{ + Q_OBJECT + +public: + static bool confirmShutdown( bool maysd, KApplication::ShutdownType& sdtype, QString& bopt ); + +public slots: + void slotLogout(); + void slotHalt(); + void slotReboot(); + void slotReboot(int); + +protected: + ~KSMShutdownDlg() {}; + +private: + KSMShutdownDlg( QWidget* parent, bool maysd, KApplication::ShutdownType sdtype ); + KApplication::ShutdownType m_shutdownType; + QString m_bootOption; + QPopupMenu *targets; + QStringList rebootOptions; +}; + +class KSMDelayedPushButton : public KPushButton +{ + Q_OBJECT + +public: + + KSMDelayedPushButton( const KGuiItem &item, QWidget *parent, const char *name = 0 ); + void setPopup( QPopupMenu *pop); + +private slots: + void slotTimeout(); + void slotPressed(); + void slotReleased(); + +private: + QPopupMenu *pop; + QTimer *popt; +}; + +#endif diff --git a/ksmserver/shutdownkonq.png b/ksmserver/shutdownkonq.png new file mode 100644 index 000000000..fca48fac9 Binary files /dev/null and b/ksmserver/shutdownkonq.png differ diff --git a/ksmserver/startup.cpp b/ksmserver/startup.cpp new file mode 100644 index 000000000..9bc984a79 --- /dev/null +++ b/ksmserver/startup.cpp @@ -0,0 +1,442 @@ +/***************************************************************** +ksmserver - the KDE session management server + +Copyright (C) 2000 Matthias Ettrich +Copyright (C) 2005 Lubos Lunak + +relatively small extensions by Oswald Buddenhagen + +some code taken from the dcopserver (part of the KDE libraries), which is +Copyright (c) 1999 Matthias Ettrich +Copyright (c) 1999 Preston Brown + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +******************************************************************/ + + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_LIMITS_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "server.h" +#include "global.h" +#include "client.h" + +#include + +/*! Restores the previous session. Ensures the window manager is + running (if specified). + */ +void KSMServer::restoreSession( QString sessionName ) +{ + if( state != Idle ) + return; + state = LaunchingWM; + + kdDebug( 1218 ) << "KSMServer::restoreSession " << sessionName << endl; + upAndRunning( "restore session"); + KConfig* config = KGlobal::config(); + + sessionGroup = "Session: " + sessionName; + + config->setGroup( sessionGroup ); + int count = config->readNumEntry( "count" ); + appsToStart = count; + + QValueList wmCommands; + if ( !wm.isEmpty() ) { + for ( int i = 1; i <= count; i++ ) { + QString n = QString::number(i); + if ( wm == config->readEntry( QString("program")+n ) ) { + wmCommands << config->readListEntry( QString("restartCommand")+n ); + } + } + } + if ( wmCommands.isEmpty() ) + wmCommands << ( QStringList() << wm ); + + publishProgress( appsToStart, true ); + connectDCOPSignal( launcher, launcher, "autoStart0Done()", + "autoStart0Done()", true); + connectDCOPSignal( launcher, launcher, "autoStart1Done()", + "autoStart1Done()", true); + connectDCOPSignal( launcher, launcher, "autoStart2Done()", + "autoStart2Done()", true); + upAndRunning( "ksmserver" ); + + if ( !wmCommands.isEmpty() ) { + // when we have a window manager, we start it first and give + // it some time before launching other processes. Results in a + // visually more appealing startup. + for (uint i = 0; i < wmCommands.count(); i++) + startApplication( wmCommands[i] ); + QTimer::singleShot( 4000, this, SLOT( autoStart0() ) ); + } else { + autoStart0(); + } +} + +/*! + Starts the default session. + + Currently, that's the window manager only (if specified). + */ +void KSMServer::startDefaultSession() +{ + if( state != Idle ) + return; + + state = LaunchingWM; + sessionGroup = ""; + publishProgress( 0, true ); + upAndRunning( "ksmserver" ); + connectDCOPSignal( launcher, launcher, "autoStart0Done()", + "autoStart0Done()", true); + connectDCOPSignal( launcher, launcher, "autoStart1Done()", + "autoStart1Done()", true); + connectDCOPSignal( launcher, launcher, "autoStart2Done()", + "autoStart2Done()", true); + startApplication( wm ); + QTimer::singleShot( 4000, this, SLOT( autoStart0() ) ); +} + + +void KSMServer::clientSetProgram( KSMClient* client ) +{ + if ( !wm.isEmpty() && client->program() == wm ) + autoStart0(); +} + +void KSMServer::autoStart0() +{ + if( state != LaunchingWM ) + return; + if( !checkStartupSuspend()) + return; + state = AutoStart0; + DCOPRef( launcher ).send( "autoStart", (int) 0 ); +} + +void KSMServer::autoStart0Done() +{ + if( state != AutoStart0 ) + return; + disconnectDCOPSignal( launcher, launcher, "autoStart0Done()", + "autoStart0Done()"); + if( !checkStartupSuspend()) + return; + kdDebug( 1218 ) << "Autostart 0 done" << endl; + upAndRunning( "kdesktop" ); + upAndRunning( "kicker" ); + connectDCOPSignal( "kcminit", "kcminit", "phase1Done()", + "kcmPhase1Done()", true); + state = KcmInitPhase1; + QTimer::singleShot( 10000, this, SLOT( kcmPhase1Timeout())); // protection + DCOPRef( "kcminit", "kcminit" ).send( "runPhase1" ); +} + +void KSMServer::kcmPhase1Done() +{ + if( state != KcmInitPhase1 ) + return; + kdDebug( 1218 ) << "Kcminit phase 1 done" << endl; + disconnectDCOPSignal( "kcminit", "kcminit", "phase1Done()", + "kcmPhase1Done()" ); + autoStart1(); +} + +void KSMServer::kcmPhase1Timeout() +{ + if( state != KcmInitPhase1 ) + return; + kdDebug( 1218 ) << "Kcminit phase 1 timeout" << endl; + kcmPhase1Done(); +} + +void KSMServer::autoStart1() +{ + if( state != KcmInitPhase1 ) + return; + state = AutoStart1; + DCOPRef( launcher ).send( "autoStart", (int) 1 ); +} + +void KSMServer::autoStart1Done() +{ + if( state != AutoStart1 ) + return; + disconnectDCOPSignal( launcher, launcher, "autoStart1Done()", + "autoStart1Done()"); + if( !checkStartupSuspend()) + return; + kdDebug( 1218 ) << "Autostart 1 done" << endl; + lastAppStarted = 0; + lastIdStarted = QString::null; + state = Restoring; + if( defaultSession()) { + autoStart2(); + return; + } + tryRestoreNext(); +} + +void KSMServer::clientRegistered( const char* previousId ) +{ + if ( previousId && lastIdStarted == previousId ) + tryRestoreNext(); +} + +void KSMServer::tryRestoreNext() +{ + if( state != Restoring ) + return; + restoreTimer.stop(); + KConfig* config = KGlobal::config(); + config->setGroup( sessionGroup ); + + while ( lastAppStarted < appsToStart ) { + publishProgress ( appsToStart - lastAppStarted ); + lastAppStarted++; + QString n = QString::number(lastAppStarted); + QStringList restartCommand = config->readListEntry( QString("restartCommand")+n ); + if ( restartCommand.isEmpty() || + (config->readNumEntry( QString("restartStyleHint")+n ) == SmRestartNever)) { + continue; + } + if ( wm == config->readEntry( QString("program")+n ) ) + continue; // wm already started + if( config->readBoolEntry( QString( "wasWm" )+n, false )) + continue; // it was wm before, but not now, don't run it (some have --replace in command :( ) + startApplication( restartCommand, + config->readEntry( QString("clientMachine")+n ), + config->readEntry( QString("userId")+n )); + lastIdStarted = config->readEntry( QString("clientId")+n ); + if ( !lastIdStarted.isEmpty() ) { + restoreTimer.start( 2000, TRUE ); + return; // we get called again from the clientRegistered handler + } + } + + appsToStart = 0; + lastIdStarted = QString::null; + publishProgress( 0 ); + + autoStart2(); +} + +void KSMServer::autoStart2() +{ + if( state != Restoring ) + return; + if( !checkStartupSuspend()) + return; + state = FinishingStartup; + waitAutoStart2 = true; + waitKcmInit2 = true; + DCOPRef( launcher ).send( "autoStart", (int) 2 ); + DCOPRef( "kded", "kded" ).send( "loadSecondPhase" ); + DCOPRef( "kdesktop", "KDesktopIface" ).send( "runAutoStart" ); + connectDCOPSignal( "kcminit", "kcminit", "phase2Done()", + "kcmPhase2Done()", true); + QTimer::singleShot( 10000, this, SLOT( kcmPhase2Timeout())); // protection + DCOPRef( "kcminit", "kcminit" ).send( "runPhase2" ); + if( !defaultSession()) + restoreLegacySession( KGlobal::config()); + KNotifyClient::event( 0, "startkde" ); // this is the time KDE is up, more or less +} + +void KSMServer::autoStart2Done() +{ + if( state != FinishingStartup ) + return; + disconnectDCOPSignal( launcher, launcher, "autoStart2Done()", + "autoStart2Done()"); + kdDebug( 1218 ) << "Autostart 2 done" << endl; + waitAutoStart2 = false; + finishStartup(); +} + +void KSMServer::kcmPhase2Done() +{ + if( state != FinishingStartup ) + return; + kdDebug( 1218 ) << "Kcminit phase 2 done" << endl; + disconnectDCOPSignal( "kcminit", "kcminit", "phase2Done()", + "kcmPhase2Done()"); + waitKcmInit2 = false; + finishStartup(); +} + +void KSMServer::kcmPhase2Timeout() +{ + if( !waitKcmInit2 ) + return; + kdDebug( 1218 ) << "Kcminit phase 2 timeout" << endl; + kcmPhase2Done(); +} + +void KSMServer::finishStartup() +{ + if( state != FinishingStartup ) + return; + if( waitAutoStart2 || waitKcmInit2 ) + return; + + upAndRunning( "session ready" ); + DCOPRef( "knotify" ).send( "sessionReady" ); // knotify startup optimization + + state = Idle; + + setupXIOErrorHandler(); // From now on handle X errors as normal shutdown. +} + +bool KSMServer::checkStartupSuspend() +{ + if( startupSuspendCount.isEmpty()) + return true; + // wait for the phase to finish + if( !startupSuspendTimeoutTimer.isActive()) + startupSuspendTimeoutTimer.start( 10000, true ); + return false; +} + +void KSMServer::suspendStartup( QCString app ) +{ + if( !startupSuspendCount.contains( app )) + startupSuspendCount[ app ] = 0; + ++startupSuspendCount[ app ]; +} + +void KSMServer::resumeStartup( QCString app ) +{ + if( !startupSuspendCount.contains( app )) + return; + if( --startupSuspendCount[ app ] == 0 ) { + startupSuspendCount.remove( app ); + if( startupSuspendCount.isEmpty() && startupSuspendTimeoutTimer.isActive()) { + startupSuspendTimeoutTimer.stop(); + resumeStartupInternal(); + } + } +} + +void KSMServer::startupSuspendTimeout() +{ + kdDebug( 1218 ) << "Startup suspend timeout:" << state << endl; + resumeStartupInternal(); +} + +void KSMServer::resumeStartupInternal() +{ + startupSuspendCount.clear(); + switch( state ) { + case LaunchingWM: + autoStart0(); + break; + case AutoStart0: + autoStart0Done(); + break; + case AutoStart1: + autoStart1Done(); + break; + case Restoring: + autoStart2(); + break; + default: + kdWarning( 1218 ) << "Unknown resume startup state" << endl; + break; + } +} + +void KSMServer::publishProgress( int progress, bool max ) +{ + DCOPRef( "ksplash" ).send( max ? "setMaxProgress" : "setProgress", progress ); +} + + +void KSMServer::upAndRunning( const QString& msg ) +{ + DCOPRef( "ksplash" ).send( "upAndRunning", msg ); + XEvent e; + e.xclient.type = ClientMessage; + e.xclient.message_type = XInternAtom( qt_xdisplay(), "_KDE_SPLASH_PROGRESS", False ); + e.xclient.display = qt_xdisplay(); + e.xclient.window = qt_xrootwin(); + e.xclient.format = 8; + assert( strlen( msg.latin1()) < 20 ); + strcpy( e.xclient.data.b, msg.latin1()); + XSendEvent( qt_xdisplay(), qt_xrootwin(), False, SubstructureNotifyMask, &e ); +} + +// these two are in the DCOP interface but I have no idea what uses them +// Remove for KDE4 +void KSMServer::restoreSessionInternal() +{ + autoStart1Done(); +} + +void KSMServer::restoreSessionDoneInternal() +{ + autoStart2Done(); +} diff --git a/ksmserver/test.cpp b/ksmserver/test.cpp new file mode 100644 index 000000000..57a6c51d6 --- /dev/null +++ b/ksmserver/test.cpp @@ -0,0 +1,27 @@ +#include "shutdowndlg.h" +#include +#include +#include +#include + +int +main(int argc, char *argv[]) +{ + KAboutData about("kapptest", "kapptest", "version"); + KCmdLineArgs::init(argc, argv, &about); + + KApplication a; + a.iconLoader()->addAppDir("ksmserver"); + KSMShutdownFeedback::start(); + + KApplication::ShutdownType sdtype = KApplication::ShutdownTypeNone; + QString bopt; + (void)KSMShutdownDlg::confirmShutdown( true, + sdtype, + bopt ); +/* (void)KSMShutdownDlg::confirmShutdown( false, + sdtype, + bopt ); */ + + KSMShutdownFeedback::stop(); +} -- cgit v1.2.3