summaryrefslogtreecommitdiffstats
path: root/ksmserver
diff options
context:
space:
mode:
Diffstat (limited to 'ksmserver')
-rw-r--r--ksmserver/KSMServerInterface.h27
-rw-r--r--ksmserver/LICENSE16
-rw-r--r--ksmserver/Makefile.am50
-rw-r--r--ksmserver/README177
-rw-r--r--ksmserver/client.cpp190
-rw-r--r--ksmserver/client.h60
-rw-r--r--ksmserver/configure.in.in4
-rw-r--r--ksmserver/global.h13
-rw-r--r--ksmserver/ksmserver.upd6
-rw-r--r--ksmserver/legacy.cpp402
-rw-r--r--ksmserver/main.cpp247
-rwxr-xr-xksmserver/move_session_config.sh32
-rw-r--r--ksmserver/server.cpp922
-rw-r--r--ksmserver/server.h219
-rw-r--r--ksmserver/server2.h16
-rw-r--r--ksmserver/shutdown.cpp563
-rw-r--r--ksmserver/shutdowndlg.cpp278
-rw-r--r--ksmserver/shutdowndlg.h88
-rw-r--r--ksmserver/shutdownkonq.pngbin0 -> 38334 bytes
-rw-r--r--ksmserver/startup.cpp442
-rw-r--r--ksmserver/test.cpp27
21 files changed, 3779 insertions, 0 deletions
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 <dcopobject.h>
+#include <qstringlist.h>
+
+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 <[email protected]>
+Lubos Lunak <[email protected]>
+
+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 <wm> 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 <[email protected]>
+Copyright (C) 2005 Lubos Lunak <[email protected]>
+
+relatively small extensions by Oswald Buddenhagen <[email protected]>
+
+some code taken from the dcopserver (part of the KDE libraries), which is
+Copyright (c) 1999 Matthias Ettrich <[email protected]>
+Copyright (c) 1999 Preston Brown <[email protected]>
+
+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 <config.h>
+#endif
+
+#include "client.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#ifdef HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+#include <time.h>
+
+#include <kstaticdeleter.h>
+
+#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<SmProp> 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<QString> 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 <[email protected]>
+******************************************************************/
+
+#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 <qobject.h>
+#include <qstring.h>
+#include <qstringlist.h>
+#include <qsocketnotifier.h>
+#include <qptrlist.h>
+#include <qvaluelist.h>
+#include <qcstring.h>
+#include <qdict.h>
+#include <qptrqueue.h>
+#include <qptrdict.h>
+#include <kapplication.h>
+#include <qtimer.h>
+#include <dcopobject.h>
+
+#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<SmProp> 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 <[email protected]>
+******************************************************************/
+
+#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 <[email protected]>
+Copyright (C) 2005 Lubos Lunak <[email protected]>
+
+relatively small extensions by Oswald Buddenhagen <[email protected]>
+
+some code taken from the dcopserver (part of the KDE libraries), which is
+Copyright (c) 1999 Matthias Ettrich <[email protected]>
+Copyright (c) 1999 Preston Brown <[email protected]>
+
+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 <config.h>
+#endif
+
+#ifdef HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+
+#include "server.h"
+
+#ifdef HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+
+#include <unistd.h>
+
+#include <qtimer.h>
+
+#include <kconfig.h>
+#include <kdebug.h>
+#include <kwinmodule.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+
+
+/*
+ * 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<WId>::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<nprotocols; i++)
+ if (protocols[i] == wm_save_yourself) {
+ wtype = SM_WMSAVEYOURSELF;
+ break;
+ }
+ XFree((void*) protocols);
+ }
+ SMData data;
+ data.type = wtype;
+ XClassHint classHint;
+ if( XGetClassHint( qt_xdisplay(), leader, &classHint ) ) {
+ data.wmclass1 = classHint.res_name;
+ data.wmclass2 = classHint.res_class;
+ XFree( classHint.res_name );
+ XFree( classHint.res_class );
+ }
+ legacyWindows.insert(leader, data);
+ }
+ }
+ // Open fresh display for sending WM_SAVE_YOURSELF
+ XSync(qt_xdisplay(), False);
+ Display *newdisplay = XOpenDisplay(DisplayString(qt_xdisplay()));
+ if (!newdisplay) {
+ windowMapPtr = NULL;
+ XSetErrorHandler(oldHandler);
+ return;
+ }
+ WId root = DefaultRootWindow(newdisplay);
+ XGrabKeyboard(newdisplay, root, False,
+ GrabModeAsync, GrabModeAsync, CurrentTime);
+ XGrabPointer(newdisplay, root, False, Button1Mask|Button2Mask|Button3Mask,
+ GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
+ // Send WM_SAVE_YOURSELF messages
+ XEvent ev;
+ int awaiting_replies = 0;
+ for (WindowMap::Iterator it = legacyWindows.begin(); it != legacyWindows.end(); ++it) {
+ if ( (*it).type == SM_WMSAVEYOURSELF ) {
+ WId w = it.key();
+ awaiting_replies += 1;
+ memset(&ev, 0, sizeof(ev));
+ ev.xclient.type = ClientMessage;
+ ev.xclient.window = w;
+ ev.xclient.message_type = wm_protocols;
+ ev.xclient.format = 32;
+ ev.xclient.data.l[0] = wm_save_yourself;
+ ev.xclient.data.l[1] = qt_x_time;
+ XSelectInput(newdisplay, w, PropertyChangeMask|StructureNotifyMask);
+ XSendEvent(newdisplay, w, False, 0, &ev);
+ }
+ }
+ // Wait for change in WM_COMMAND with timeout
+ XFlush(newdisplay);
+ QTime start = QTime::currentTime();
+ while (awaiting_replies > 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 "<path>/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 <[email protected]>
+******************************************************************/
+
+#include <config.h>
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+
+#include <dcopclient.h>
+#include <qmessagebox.h>
+#include <qdir.h>
+
+#include <kapplication.h>
+#include <kcmdlineargs.h>
+#include <kaboutdata.h>
+#include <kdebug.h>
+#include <klocale.h>
+#include <kglobal.h>
+#include <kconfig.h>
+#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 <wm>", 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, "[email protected]");
+ aboutData.addAuthor("LuboÅ¡ Luňák", I18N_NOOP( "Maintainer" ), "[email protected]" );
+
+ 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 <[email protected]>
+Copyright (C) 2005 Lubos Lunak <[email protected]>
+
+relatively small extensions by Oswald Buddenhagen <[email protected]>
+
+some code taken from the dcopserver (part of the KDE libraries), which is
+Copyright (c) 1999 Matthias Ettrich <[email protected]>
+Copyright (c) 1999 Preston Brown <[email protected]>
+
+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 <config.h>
+#endif
+
+#include <pwd.h>
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#ifdef HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <time.h>
+#include <errno.h>
+#include <string.h>
+#include <assert.h>
+
+#ifdef HAVE_LIMITS_H
+#include <limits.h>
+#endif
+
+#include <qfile.h>
+#include <qtextstream.h>
+#include <qdatastream.h>
+#include <qptrstack.h>
+#include <qpushbutton.h>
+#include <qmessagebox.h>
+#include <qguardedptr.h>
+#include <qtimer.h>
+#include <qregexp.h>
+
+#include <klocale.h>
+#include <kglobal.h>
+#include <kconfig.h>
+#include <kstandarddirs.h>
+#include <unistd.h>
+#include <kapplication.h>
+#include <kstaticdeleter.h>
+#include <ktempfile.h>
+#include <kprocess.h>
+#include <dcopclient.h>
+#include <dcopref.h>
+
+#include "server.h"
+#include "global.h"
+#include "client.h"
+
+#include "server.moc"
+
+#include <kdebug.h>
+
+#include <dmctl.h>
+
+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<QCString> argList;
+ for ( int i=1; i < n; i++)
+ argList.append( QCString(command[i].latin1()));
+ DCOPRef( launcher ).send( "exec_blind", app, DCOPArg( argList, "QValueList<QCString>" ) );
+}
+
+/*! 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<KSMClient> 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 <[email protected]>
+******************************************************************/
+
+#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 <qobject.h>
+#include <qstring.h>
+#include <qstringlist.h>
+#include <qsocketnotifier.h>
+#include <qptrlist.h>
+#include <qvaluelist.h>
+#include <qcstring.h>
+#include <qdict.h>
+#include <qptrqueue.h>
+#include <qptrdict.h>
+#include <kapplication.h>
+#include <qtimer.h>
+#include <dcopobject.h>
+
+#include "server2.h"
+
+#include "KSMServerInterface.h"
+
+#define SESSION_PREVIOUS_LOGOUT "saved at previous logout"
+#define SESSION_BY_USER "saved by user"
+
+typedef QValueList<QCString> 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<WId,SMData> 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<KSMListener> listener;
+ QPtrList<KSMClient> 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 <X11/Xlib.h>
+#include <X11/Xmd.h>
+#include <X11/ICE/ICElib.h>
+extern "C" {
+#include <X11/ICE/ICEutil.h>
+#include <X11/ICE/ICEmsg.h>
+#include <X11/ICE/ICEproto.h>
+#include <X11/SM/SM.h>
+#include <X11/SM/SMlib.h>
+}
+
+#include <fixx11h.h>
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 <[email protected]>
+Copyright (C) 2005 Lubos Lunak <[email protected]>
+
+relatively small extensions by Oswald Buddenhagen <[email protected]>
+
+some code taken from the dcopserver (part of the KDE libraries), which is
+Copyright (c) 1999 Matthias Ettrich <[email protected]>
+Copyright (c) 1999 Preston Brown <[email protected]>
+
+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 <config.h>
+#endif
+
+#include <pwd.h>
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#ifdef HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <time.h>
+#include <errno.h>
+#include <string.h>
+#include <assert.h>
+
+#ifdef HAVE_LIMITS_H
+#include <limits.h>
+#endif
+
+#include <qfile.h>
+#include <qtextstream.h>
+#include <qdatastream.h>
+#include <qptrstack.h>
+#include <qpushbutton.h>
+#include <qmessagebox.h>
+#include <qguardedptr.h>
+#include <qtimer.h>
+#include <qregexp.h>
+
+#include <klocale.h>
+#include <kglobal.h>
+#include <kconfig.h>
+#include <kstandarddirs.h>
+#include <unistd.h>
+#include <kapplication.h>
+#include <kstaticdeleter.h>
+#include <ktempfile.h>
+#include <kprocess.h>
+#include <dcopclient.h>
+#include <dcopref.h>
+#include <dmctl.h>
+#include <kdebug.h>
+#include <knotifyclient.h>
+
+#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 <[email protected]>
+******************************************************************/
+
+#include <config.h>
+
+#include "shutdowndlg.h"
+#include <qapplication.h>
+#include <qlayout.h>
+#include <qgroupbox.h>
+#include <qvbuttongroup.h>
+#include <qlabel.h>
+#include <qvbox.h>
+#include <qtimer.h>
+#include <qstyle.h>
+#include <qcombobox.h>
+#include <qcursor.h>
+#include <qmessagebox.h>
+#include <qbuttongroup.h>
+#include <qiconset.h>
+#include <qpopupmenu.h>
+#include <qtooltip.h>
+#include <qimage.h>
+
+#include <klocale.h>
+#include <kapplication.h>
+#include <kdebug.h>
+#include <kpushbutton.h>
+#include <kstdguiitem.h>
+#include <kiconloader.h>
+#include <kglobalsettings.h>
+#include <kwin.h>
+#include <kuser.h>
+#include <kpixmap.h>
+#include <kimageeffect.h>
+#include <kdialog.h>
+#include <kseparator.h>
+
+#include <sys/types.h>
+#include <sys/utsname.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <dmctl.h>
+
+#include <X11/Xlib.h>
+
+#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 <[email protected]>
+******************************************************************/
+
+#ifndef SHUTDOWNDLG_H
+#define SHUTDOWNDLG_H
+
+#include <qpixmap.h>
+#include <qdialog.h>
+#include <kpushbutton.h>
+class QPushButton;
+class QVButtonGroup;
+class QPopupMenu;
+class QTimer;
+
+#include <kapplication.h>
+
+// 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
--- /dev/null
+++ b/ksmserver/shutdownkonq.png
Binary files 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 <[email protected]>
+Copyright (C) 2005 Lubos Lunak <[email protected]>
+
+relatively small extensions by Oswald Buddenhagen <[email protected]>
+
+some code taken from the dcopserver (part of the KDE libraries), which is
+Copyright (c) 1999 Matthias Ettrich <[email protected]>
+Copyright (c) 1999 Preston Brown <[email protected]>
+
+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 <config.h>
+#endif
+
+#include <pwd.h>
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#ifdef HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <time.h>
+#include <errno.h>
+#include <string.h>
+#include <assert.h>
+
+#ifdef HAVE_LIMITS_H
+#include <limits.h>
+#endif
+
+#include <qfile.h>
+#include <qtextstream.h>
+#include <qdatastream.h>
+#include <qptrstack.h>
+#include <qpushbutton.h>
+#include <qmessagebox.h>
+#include <qguardedptr.h>
+#include <qtimer.h>
+
+#include <klocale.h>
+#include <kglobal.h>
+#include <kconfig.h>
+#include <kstandarddirs.h>
+#include <unistd.h>
+#include <kapplication.h>
+#include <kstaticdeleter.h>
+#include <ktempfile.h>
+#include <kprocess.h>
+#include <dcopclient.h>
+#include <dcopref.h>
+#include <kwinmodule.h>
+#include <knotifyclient.h>
+
+#include "server.h"
+#include "global.h"
+#include "client.h"
+
+#include <kdebug.h>
+
+/*! 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<QStringList> 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 <kcmdlineargs.h>
+#include <kaboutdata.h>
+#include <kapplication.h>
+#include <kiconloader.h>
+
+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();
+}