Subject: GUI configuration for selecting WM
From: Lubos Lunak
Feature: bnc#332079
Patch-upstream: no

Index: ksmserver/startup.cpp
===================================================================
--- ksmserver/startup.cpp.orig
+++ ksmserver/startup.cpp
@@ -103,38 +103,39 @@ void KSMServer::restoreSession( QString
     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 );
+    upAndRunning( "ksmserver" );
     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();
+    // find all commands to launch the wm in the session
+    QValueList<QStringList> wmStartCommands;
+    if ( !wm.isEmpty() ) {
+	for ( int i = 1; i <= count; i++ ) {
+	    QString n = QString::number(i);
+            // special hack for it, both kde3(=native) and kde4 kwin have the same program,
+            // but the command for kde4 kwin starts with the kde4 wrapper
+            if( config->readEntry( QString("program")+n ) == "kwin" ) {
+                QStringList command = config->readListEntry( QString("restartCommand")+n );
+                if( wmCommands.count() > 1 && wmCommands[ 0 ].endsWith( "kde4" )
+                    && command.count() > 1 && command[ 0 ].endsWith( "kde4" )) {
+                    wmStartCommands << command; // kde4 wanted, kde4 found
+                } else if(!( wmCommands.count() > 1 && wmCommands[ 0 ].endsWith( "kde4" ))
+                    && !( command.count() > 1 && command[ 0 ].endsWith( "kde4" ))) {
+                    wmStartCommands << command; // native wanted, native found
+                }
+            } else if ( wm == config->readEntry( QString("program")+n ) ) {
+		wmStartCommands << config->readListEntry( QString("restartCommand")+n );
+	    }
+	}
     }
+    if( wmStartCommands.isEmpty()) // otherwise use the configured default
+        wmStartCommands << wmCommands;
+    launchWM( wmStartCommands );
 }
 
 /*!
@@ -157,17 +158,53 @@ void KSMServer::startDefaultSession()
                        "autoStart1Done()", true);
     connectDCOPSignal( launcher, launcher, "autoStart2Done()",
                        "autoStart2Done()", true);
-    startApplication( wm );
+    launchWM( QValueList< QStringList >() << wmCommands );
+}
+
+void KSMServer::launchWM( const QValueList< QStringList >& wmStartCommands )
+{
+    assert( state == LaunchingWM );
+
+    // 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.
+    wmProcess = startApplication( wmStartCommands[ 0 ] );
+    connect( wmProcess, SIGNAL( processExited( KProcess* )), SLOT( wmProcessChange()));
+    // there can be possibly more wm's (because of forking for multihead),
+    // but in such case care only about the process of the first one
+    for (unsigned int i = 1; i < wmStartCommands.count(); i++)
+        startApplication( wmStartCommands[i] );
     QTimer::singleShot( 4000, this, SLOT( autoStart0() ) );
 }
 
 
 void KSMServer::clientSetProgram( KSMClient* client )
 {
-    if ( !wm.isEmpty() && client->program() == wm )
+    if ( client->program() == wm )
         autoStart0();
 }
 
+void KSMServer::wmProcessChange()
+{
+    if( state != LaunchingWM )
+    { // don't care about the process when not in the wm-launching state anymore
+        wmProcess = NULL;
+        return;
+    }
+    if( !wmProcess->isRunning())
+    { // wm failed to launch for some reason, go with kwin instead
+        kdWarning( 1218 ) << "Window manager '" << wm << "' failed to launch" << endl;
+        if( wm == "kwin" )
+            return; // uhoh, kwin itself failed
+        kdDebug( 1218 ) << "Launching KWin" << endl;
+        wm = "kwin";
+        wmCommands = ( QStringList() << "kwin" ); 
+        // launch it
+        launchWM( QValueList< QStringList >() << wmCommands );
+        return;
+    }
+}
+
 void KSMServer::autoStart0()
 {
     if( state != LaunchingWM )
Index: ksmserver/server.h
===================================================================
--- ksmserver/server.h.orig
+++ ksmserver/server.h
@@ -30,6 +30,8 @@ Copyright (C) 2000 Matthias Ettrich <ett
 #define SESSION_PREVIOUS_LOGOUT "saved at previous logout"
 #define SESSION_BY_USER  "saved by user"
 
+class KProcess;
+
 typedef QValueList<QCString> QCStringList;
 class KSMListener;
 class KSMConnection;
@@ -98,6 +100,8 @@ public:
 		      KApplication::ShutdownType sdtype,
 		      KApplication::ShutdownMode sdmode );
 
+    void launchWM( const QValueList< QStringList >& wmStartCommands );
+
 public slots:
     void cleanUp();
 
@@ -120,6 +124,7 @@ private slots:
     void autoStart2();
     void tryRestoreNext();
     void startupSuspendTimeout();
+    void wmProcessChange();
 
 private:
     void handlePendingInteractions();
@@ -138,13 +143,14 @@ private:
     void startProtection();
     void endProtection();
 
-    void startApplication( QStringList command,
+    KProcess* 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;
+    void selectWm( const QString& kdewm );
     bool defaultSession() const; // empty session
     void setupXIOErrorHandler();
 
@@ -223,6 +229,8 @@ private:
     int lastAppStarted;
     QString lastIdStarted;
     
+    QStringList wmCommands;
+    KProcess* wmProcess;
     QStringList excludeApps;
 
     WindowMap legacyWindows;
Index: ksmserver/Makefile.am
===================================================================
--- ksmserver/Makefile.am.orig
+++ ksmserver/Makefile.am
@@ -15,7 +15,7 @@
 # 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 = .
+SUBDIRS = . windowmanagers
 
 INCLUDES= -I$(top_srcdir)/kdmlib $(all_includes) $(DBUS_INCS)
 
Index: ksmserver/main.cpp
===================================================================
--- ksmserver/main.cpp.orig
+++ ksmserver/main.cpp
@@ -203,8 +203,6 @@ extern "C" KDE_EXPORT int kdemain( int a
     }
 
     QCString wm = args->getOption("windowmanager");
-    if ( wm.isEmpty() )
-	wm = "kwin";
 
     bool only_local = args->isSet("local");
 #ifndef HAVE__ICETRANSNOLISTEN
Index: ksmserver/server.cpp
===================================================================
--- ksmserver/server.cpp.orig
+++ ksmserver/server.cpp
@@ -77,6 +77,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE
 #include <kprocess.h>
 #include <dcopclient.h>
 #include <dcopref.h>
+#include <kdesktopfile.h>
+#include <kshell.h>
 
 #include "server.h"
 #include "global.h"
@@ -98,11 +100,11 @@ KSMServer* KSMServer::self()
 /*! Utility function to execute a command on the local machine. Used
  * to restart applications.
  */
-void KSMServer::startApplication( QStringList command, const QString& clientMachine,
+KProcess* KSMServer::startApplication( QStringList command, const QString& clientMachine,
     const QString& userId )
 {
     if ( command.isEmpty() )
-        return;
+        return NULL;
     if ( !userId.isEmpty()) {
         struct passwd* pw = getpwuid( getuid());
         if( pw != NULL && userId != QString::fromLocal8Bit( pw->pw_name )) {
@@ -116,12 +118,12 @@ void KSMServer::startApplication( QStrin
         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>" ) );
+    KProcess* process = new KProcess( this );
+    *process << command;
+    // make it auto-delete
+    connect( process, SIGNAL( processExited( KProcess* )), process, SLOT( deleteLater()));
+    process->start();
+    return process;
 }
 
 /*! Utility function to execute a command on the local machine. Used
@@ -580,10 +582,10 @@ extern "C" int _IceTransNoListen(const c
 
 KSMServer::KSMServer( const QString& windowManager, bool _only_local )
   : DCOPObject("ksmserver"), sessionGroup( "" )
+  , wmProcess( NULL )
 {
     the_server = this;
     clean = false;
-    wm = windowManager;
 
     shutdownType = KApplication::ShutdownTypeNone;
 
@@ -595,6 +597,9 @@ KSMServer::KSMServer( const QString& win
     config->setGroup("General" );
     clientInteracting = 0;
     xonCommand = config->readEntry( "xonCommand", "xon" );
+
+    KGlobal::dirs()->addResourceType( "windowmanagers", "share/apps/ksmserver/windowmanagers" );
+    selectWm( windowManager );
     
     connect( &knotifyTimeoutTimer, SIGNAL( timeout()), SLOT( knotifyTimeout()));
     connect( &startupSuspendTimeoutTimer, SIGNAL( timeout()), SLOT( startupSuspendTimeout()));
@@ -851,14 +856,12 @@ void KSMServer::storeSession()
     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;
-            }
-    }
+    // 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();
@@ -909,14 +912,65 @@ bool KSMServer::isWM( const KSMClient* c
 
 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";
+    return program == wm;
 }
 
 bool KSMServer::defaultSession() const
 {
     return sessionGroup.isEmpty();
 }
+
+static bool noDisplay( KDesktopFile& f )
+{
+    KConfigGroup gr( &f, "Desktop Entry" );
+    if (gr.readBoolEntry("NoDisplay", false)) {
+        return true;
+    }
+    if (gr.hasKey("OnlyShowIn")) {
+        if (!gr.readListEntry("OnlyShowIn", ';').contains("KDE"))
+            return true;
+    }
+    if (gr.hasKey("NotShowIn")) {
+        if (gr.readListEntry("NotShowIn", ';').contains("KDE"))
+            return true;
+    }
+    return false;
+}
+
+// selection logic:
+// - $KDEWM is set - use that
+// - a wm is selected using the kcm - use that
+// - if that fails, just use KWin
+void KSMServer::selectWm( const QString& kdewm )
+{
+    wm = "kwin"; // defaults
+    wmCommands = ( QStringList() << "kwin" ); 
+    if( !kdewm.isEmpty())
+    {
+        wmCommands = ( QStringList() << kdewm );
+        wm = kdewm;
+        return;
+    }
+    KConfigGroup config(KGlobal::config(), "General");
+    QString cfgwm = config.readEntry( "windowManager", "kwin" );
+    KDesktopFile file( cfgwm + ".desktop", true, "windowmanagers" );
+    if( noDisplay( file ))
+        return;
+    if( !file.tryExec())
+        return;
+        file.setDesktopGroup();
+    QString testexec = file.readEntry( "X-KDE-WindowManagerTestExec" );
+    if( !testexec.isEmpty())
+    {
+        int ret = system( QFile::encodeName( testexec ));
+        if( !WIFEXITED( ret ) || WEXITSTATUS( ret ) != 0 )
+            return;
+    }
+    QStringList cfgWmCommands = KShell::splitArgs( file.readEntry( "Exec" ));
+    if( cfgWmCommands.isEmpty())
+        return;
+    QString smname = file.readEntry( "X-KDE-WindowManagerId" );
+    // ok
+    wm = smname.isEmpty() ? cfgwm : smname;
+    wmCommands = cfgWmCommands;
+}
Index: ksmserver/windowmanagers/openbox.desktop
===================================================================
--- /dev/null
+++ ksmserver/windowmanagers/openbox.desktop
@@ -0,0 +1,5 @@
+[Desktop Entry]
+Name=Openbox
+Exec=openbox
+TryExec=openbox
+
Index: ksmserver/windowmanagers/Makefile.am
===================================================================
--- /dev/null
+++ ksmserver/windowmanagers/Makefile.am
@@ -0,0 +1,2 @@
+windowmanager_DATA = compiz-custom.desktop compiz.desktop kwin4.desktop metacity.desktop openbox.desktop
+windowmanagerdir = $(kde_datadir)/ksmserver/windowmanagers
Index: ksmserver/windowmanagers/compiz.desktop
===================================================================
--- /dev/null
+++ ksmserver/windowmanagers/compiz.desktop
@@ -0,0 +1,4 @@
+[Desktop Entry]
+Name=Compiz
+Exec=compiz ccp
+TryExec=compiz
Index: ksmserver/windowmanagers/compiz-custom.desktop
===================================================================
--- /dev/null
+++ ksmserver/windowmanagers/compiz-custom.desktop
@@ -0,0 +1,5 @@
+[Desktop Entry]
+Name=Compiz custom (create wrapper script 'compiz-kde-launcher' to launch it)
+Exec=compiz-kde-launcher
+TryExec=compiz
+X-KDE-WindowManagerId=compiz
Index: ksmserver/windowmanagers/kwin4.desktop
===================================================================
--- /dev/null
+++ ksmserver/windowmanagers/kwin4.desktop
@@ -0,0 +1,6 @@
+[Desktop Entry]
+Name=KWin (KDE4)
+Exec=kde4 /usr/bin/kwin
+TryExec=/usr/bin/kwin
+X-KDE-WindowManagerId=kwin
+
Index: ksmserver/windowmanagers/metacity.desktop
===================================================================
--- /dev/null
+++ ksmserver/windowmanagers/metacity.desktop
@@ -0,0 +1,4 @@
+[Desktop Entry]
+Name=Metacity (GNOME)
+Exec=metacity
+TryExec=metacity
Index: kcontrol/smserver/smserverconfigdlg.ui
===================================================================
--- kcontrol/smserver/smserverconfigdlg.ui.orig
+++ kcontrol/smserver/smserverconfigdlg.ui
@@ -1,4 +1,4 @@
-<!DOCTYPE UI><UI version="3.2" stdsetdef="1">
+<!DOCTYPE UI><UI version="3.3" stdsetdef="1">
 <class>SMServerConfigDlg</class>
 <widget class="QWidget">
     <property name="name">
@@ -8,8 +8,8 @@
         <rect>
             <x>0</x>
             <y>0</y>
-            <width>325</width>
-            <height>366</height>
+            <width>334</width>
+            <height>476</height>
         </rect>
     </property>
     <property name="caption">
@@ -148,6 +148,24 @@
                 </widget>
             </vbox>
         </widget>
+        <widget class="QGroupBox">
+            <property name="name">
+                <cstring>windowManagerGroup</cstring>
+            </property>
+            <property name="title">
+                <string>Window Manager</string>
+            </property>
+            <hbox>
+                <property name="name">
+                    <cstring>unnamed</cstring>
+                </property>
+                <widget class="QComboBox">
+                    <property name="name">
+                        <cstring>windowManagerCombo</cstring>
+                    </property>
+                </widget>
+            </hbox>
+        </widget>
         <widget class="QButtonGroup">
             <property name="name">
                 <cstring>advancedGroup</cstring>
@@ -236,6 +254,12 @@
         <receiver>SMServerConfigDlg</receiver>
         <slot>configChanged()</slot>
     </connection>
+    <connection>
+        <sender>windowManagerCombo</sender>
+        <signal>activated(int)</signal>
+        <receiver>SMServerConfigDlg</receiver>
+        <slot>configChanged()</slot>
+    </connection>
 </connections>
 <includes>
     <include location="global" impldecl="in implementation">kdialog.h</include>
Index: kcontrol/smserver/kcmsmserver.cpp
===================================================================
--- kcontrol/smserver/kcmsmserver.cpp.orig
+++ kcontrol/smserver/kcmsmserver.cpp
@@ -22,6 +22,8 @@
 #include <qcheckbox.h>
 #include <qlayout.h>
 #include <qradiobutton.h>
+#include <qcombobox.h>
+#include <qfile.h>
 
 #include <dcopclient.h>
 
@@ -29,6 +31,12 @@
 #include <kconfig.h>
 #include <kgenericfactory.h>
 #include <klineedit.h>
+#include <kstandarddirs.h>
+#include <qregexp.h>
+#include <kdesktopfile.h>
+#include <kdebug.h>
+#include <kprocess.h>
+#include <kmessagebox.h>
 
 #include "kcmsmserver.h"
 #include "smserverconfigimpl.h"
@@ -52,6 +60,7 @@ SMServerConfig::SMServerConfig( QWidget
 
     dialog->show();
     topLayout->add(dialog);
+    KGlobal::dirs()->addResourceType( "windowmanagers", "share/apps/ksmserver/windowmanagers" );
     load();
 
 }
@@ -90,6 +99,7 @@ void SMServerConfig::load(bool useDefaul
     dialog->logoutRadio->setChecked(true);
     break;
   }
+  loadWMs(c->readEntry("windowManager", "kwin"));
   dialog->excludeLineedit->setText( c->readEntry("excludeApps"));
 
   delete c;
@@ -116,6 +126,7 @@ void SMServerConfig::save()
                    dialog->rebootRadio->isChecked() ?
                      int(KApplication::ShutdownTypeReboot) :
                      int(KApplication::ShutdownTypeNone));
+  c->writeEntry("windowManager", currentWM());
   c->writeEntry("excludeApps", dialog->excludeLineedit->text());
   c->sync();
   delete c;
@@ -123,6 +134,12 @@ void SMServerConfig::save()
   // update the k menu if necessary
   QByteArray data;
   kapp->dcopClient()->send( "kicker", "kicker", "configure()", data );
+  if( oldwm != currentWM())
+  { // TODO switch it already in the session instead and tell ksmserver
+    KMessageBox::information( this,
+        i18n( "The new window manager will be used when KDE is started the next time." ),
+        i18n( "Window manager change" ), "windowmanagerchange" );
+  }
 }
 
 void SMServerConfig::defaults()
@@ -130,5 +147,72 @@ void SMServerConfig::defaults()
 	load( true );
 }
 
+static bool noDisplay( KDesktopFile& f )
+{
+    KConfigGroup gr( &f, "Desktop Entry" );
+    if (gr.readBoolEntry("NoDisplay", false)) {
+        return true;
+    }
+    if (gr.hasKey("OnlyShowIn")) {
+        if (!gr.readListEntry("OnlyShowIn", ';').contains("KDE"))
+            return true;
+    }
+    if (gr.hasKey("NotShowIn")) {
+        if (gr.readListEntry("NotShowIn", ';').contains("KDE"))
+            return true;
+    }
+    return false;
+}
+
+void SMServerConfig::loadWMs( const QString& current )
+{
+  QString kwinname = i18n( "KWin (KDE default)" );
+  dialog->windowManagerCombo->insertItem( kwinname );
+  dialog->windowManagerCombo->setCurrentItem( 0 );
+  wms[ kwinname ] = "kwin";
+  oldwm = "kwin";
+  QStringList list = KGlobal::dirs()->findAllResources( "windowmanagers", QString(), false, true );
+  QRegExp reg( ".*/([^/\\.]*)\\.[^/\\.]*" );
+  for( QStringList::ConstIterator it = list.begin();
+       it != list.end();
+       ++it )
+  {
+    QString wmfile = *it;
+    KDesktopFile file( wmfile );
+    if( noDisplay( file ))
+        continue;
+    if( !file.tryExec())
+        continue;
+    file.setDesktopGroup();
+    QString testexec = file.readEntry( "X-KDE-WindowManagerTestExec" );
+    if( !testexec.isEmpty())
+    {
+        int ret = system( QFile::encodeName( testexec ));
+        if( !WIFEXITED( ret ) || WEXITSTATUS( ret ) != 0 )
+            continue;
+    }
+    QString name = file.readName();
+    if( name.isEmpty())
+        continue;
+    if( !reg.exactMatch( wmfile ))
+        continue;
+    QString wm = reg.cap( 1 );
+    if( wms.values().contains( wm ))
+        continue;
+    wms[ name ] = wm;
+    dialog->windowManagerCombo->insertItem( name );
+    if( wms[ name ] == current ) // make it selected
+    {
+        dialog->windowManagerCombo->setCurrentItem( dialog->windowManagerCombo->count() - 1 );
+        oldwm = wm;
+    }
+  }
+}
+
+QString SMServerConfig::currentWM() const
+{
+  return wms[ dialog->windowManagerCombo->currentText() ];
+}
+
 #include "kcmsmserver.moc"
 
Index: kcontrol/smserver/kcmsmserver.h
===================================================================
--- kcontrol/smserver/kcmsmserver.h.orig
+++ kcontrol/smserver/kcmsmserver.h
@@ -40,6 +40,10 @@ public:
 
 private:
   SMServerConfigImpl* dialog;
+  void loadWMs( const QString& current );
+  QString currentWM() const;
+  QMap< QString, QString > wms; // i18n text -> internal name
+  QString oldwm; // the original value
 
 };