[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#789689: [PATCH]: Add a language selector to kdm



Package: kde-workspace
Version: 4:4.11.13-2.1
Severity: wishlist
Tags: patch

Hello!

The attached patch adds a language selector to kdm which allows one to
set the environment variables for language and localization at login
time.

This is implemented by adding an additional popup menu below the session
selector in kdm which will provide a list of languages available for
login. The choice made here will cause kdm to set the environment
variable KDM_LANG to a valid locale like "de_DE.utf-8" or "fr_FR.utf-8",
very similar to what gdm implemented before GNOME developers decided
to rip that functionality out. gdm just set GDM_LANG instead of
KDM_LANG.

One then just needs to add a few lines to kdm's Xsession file located
in /etc/kde4/kdm/Xsession which then reads in the current value of KDM_LANG
and uses it to set LANG, LANGUAGE and LC_ALL accordingly. Currently,
we have added the following lines to our kdm Xsession file to achieve
this:

if [ -n "$KDM_LANG" ] ; then
    LANGUAGE="$KDM_LANG"
    LANG="$KDM_LANG"
    LC_ALL="$KDM_LANG"
    export LANGUAGE
    export LANG
    export LC_ALL
fi

Please note that this patch has not been thoroughly tested to the extent
that I would recommend applying it to the current kde-workspace source
package in Debian or even upstream. However, since we have already deployed
such a modified version of kdm on all our Jessie machines without any
issues, this patch might still be helpful to anyone who is looking for
a display manager with working selection menus for both session and
language as well as the capability to store these values per user
across the network.

None of the other display managers available in Debian currently provides
this functionality: gdm3 doesn't have a language selector anymore, lightdm
has one but restoring the settings from accounts-service or .dmrc is
completely broken in lightdm and sddm can only save these settings for
one global user. Thus, I went ahead and just added a language selector
to kdm myself, based on a old patch provided in KDE's bug tracker [1].

I used the patch, ported it to the new kdm codebase as well added
the feature to save and restore the language chosen from kdm. Thus,
part of the credit for this patch goes to the user "dump" in KDE's
bug tracker.

Oh, and currently the list of available languages is hard-coded
into the source code in kdm/kfrontend/kgreeter.cpp. Thus, if
anyone seriously considers committing this patch Debian's kde-workspace
source package or upstream, they should improve the code afterwards
to parse the list of available locales from the system.

Cheers,
Adrian

> [1] https://bugs.kde.org/show_bug.cgi?id=55379#c19
Description: Adds a language selector to KDM
 See: https://bugsfiles.kde.org/attachment.cgi?id=23647

Index: kde-workspace-4.11.13/kdm/backend/client.c
===================================================================
--- kde-workspace-4.11.13.orig/kdm/backend/client.c
+++ kde-workspace-4.11.13/kdm/backend/client.c
@@ -1454,6 +1454,7 @@ startClient(volatile int *pid)
     env = setEnv(env, "PATH", curuid ? td->userPath : td->systemPath);
     env = setEnv(env, "SHELL", p->pw_shell);
     env = setEnv(env, "HOME", p->pw_dir);
+    env = setEnv( env, "KDM_LANG", td->lang );
 #if !defined(USE_PAM) && !defined(_AIX) && defined(KERBEROS)
     if (krbtkfile[0] != '\0')
         env = setEnv(env, "KRBTKFILE", krbtkfile);
Index: kde-workspace-4.11.13/kdm/backend/dm.h
===================================================================
--- kde-workspace-4.11.13.orig/kdm/backend/dm.h
+++ kde-workspace-4.11.13/kdm/backend/dm.h
@@ -303,6 +303,7 @@ struct display {
     Xauth **authorizations;     /* authorization data */
     int authNum;                /* number of authorizations */
     char *authFile;             /* file to store authorization in */
+    char *lang;                 /* Language used (eg: en_US.utf-8) */
     char *greeterAuthFile;      /* file to store authorization for greeter in */
 };
 
Index: kde-workspace-4.11.13/kdm/backend/greet.h
===================================================================
--- kde-workspace-4.11.13.orig/kdm/backend/greet.h
+++ kde-workspace-4.11.13/kdm/backend/greet.h
@@ -170,6 +170,7 @@ from the copyright holder.
 #define G_Verify        109 /* str type; ..., int V_ret */
 #define G_VerifyRootOK  110 /* str type; ..., int V_ret */
 #define G_List          111 /* int flags; ?*(str,str,[int,]str,str,int), int 0 */
+#define G_PutLang       112 /* str key, str value */
 # define lstRemote        1
 # define lstPassive       2
 # define lstTTY           4
Index: kde-workspace-4.11.13/kdm/backend/session.c
===================================================================
--- kde-workspace-4.11.13.orig/kdm/backend/session.c
+++ kde-workspace-4.11.13/kdm/backend/session.c
@@ -361,6 +361,16 @@ ctrlGreeterWait(int wreply, time_t *star
             free(pass);
             free(name);
             break;
+	case G_PutLang:
+            debug("G_PutLang\n");
+            name = gRecvStr();
+            debug(" key %s\"s\"", name);
+            pass = gRecvStr();
+            debug(" value %\"s\n", pass);
+            strDup(&td->lang, pass);
+            free(pass);
+            free(name);
+            break;
         case G_VerifyRootOK:
             debug("G_VerifyRootOK\n");
             rootok = True;
Index: kde-workspace-4.11.13/kdm/kfrontend/kgreeter.cpp
===================================================================
--- kde-workspace-4.11.13.orig/kdm/kfrontend/kgreeter.cpp
+++ kde-workspace-4.11.13/kdm/kfrontend/kgreeter.cpp
@@ -178,6 +178,7 @@ KGreeter::KGreeter(bool framed)
     , nNormals(0)
     , nSpecials(0)
     , curPrev(0)
+    , langSel(0)
     , prevValid(true)
     , needLoad(false)
 {
@@ -203,6 +204,13 @@ KGreeter::KGreeter(bool framed)
     sessGroup = new QActionGroup(this);
     insertSessions();
 
+    langMenu = new QMenu( this );
+    connect(langMenu, SIGNAL(triggered(QAction*)),
+	     SLOT(slotLanguageSelected()));
+
+    langGroup = new QActionGroup(this);
+    insertLanguages();
+
     if (curPlugin < 0) {
         curPlugin = 0;
         pluginList = KGVerify::init(_pluginsLogin);
@@ -461,6 +469,35 @@ KGreeter::putSession(const QString &type
 }
 
 void
+KGreeter::putLanguage( const QString &type, const QString &name )
+{
+    languageTypes.append(LangType(name, type, false, languageTypes.size() ) );
+}
+
+void
+KGreeter::insertLanguages()
+{
+    /* Should read from /usr/lib/locale/<type>/LC_IDENTIFICATION */
+    /* GDM uses a static file which is almost as bad as this hardcoding */
+
+    putLanguage( "fr_FR.utf-8", i18n("Francais") );
+    putLanguage( "en_US.utf-8", i18n("English") );
+    putLanguage( "de_DE.utf-8", i18n("Deutsch") );
+    putLanguage( "es_ES.utf-8", i18n("Español") );
+    putLanguage( "it_IT.utf-8", i18n("Italiano") );
+    putLanguage( "ru_RU.utf-8", i18n("русский") );
+    putLanguage( "zh_CN.utf-8", i18n("中文(中国)") );
+    putLanguage( "zh_TW.utf-8", i18n("中文(台湾)") );
+    qSort(languageTypes);
+    for (int i = 0; i < languageTypes.size(); i++) {
+        languageTypes[i].action = langGroup->addAction(languageTypes[i].name);
+        languageTypes[i].action->setData(i);
+        languageTypes[i].action->setCheckable(true);
+    }
+    langMenu->addActions(langGroup->actions());
+}
+
+void
 KGreeter::insertSessions()
 {
     for (char **dit = _sessionsDirs; *dit; ++dit)
@@ -513,10 +550,14 @@ KGreeter::slotUserEntered()
         userView->clearSelection();
     }
   oke:
-    if (isVisible())
+    if (isVisible()) {
         slotLoadPrevWM();
-    else
+	slotLoadPrevLang();
+    }
+    else {
         QTimer::singleShot(0, this, SLOT(slotLoadPrevWM()));
+	QTimer::singleShot(0, this, SLOT(slotLoadPrevLang()));
+    }
 }
 
 void
@@ -530,6 +571,12 @@ KGreeter::slotUserClicked(QListWidgetIte
 }
 
 void
+KGreeter::slotLanguageSelected()
+{
+    verify->gplugChanged();
+}
+
+void
 KGreeter::slotSessionSelected()
 {
     verify->gplugChanged();
@@ -562,6 +609,20 @@ KGreeter::setPrevWM(QAction *wm)
 }
 
 void
+KGreeter::setPrevLang(QAction *lang)
+{
+    if (langSel != lang) {
+        if (langSel)
+            langSel->setText(languageTypes[langSel->data().toInt()].name);
+        if (lang)
+            lang->setText(i18nc("@item:inmenu session type",
+				"%1 (previous)",
+				languageTypes[lang->data().toInt()].name));
+        langSel = lang;
+   }
+}
+
+void
 KGreeter::slotLoadPrevWM()
 {
     int len, i, b;
@@ -626,6 +687,50 @@ KGreeter::slotLoadPrevWM()
     setPrevWM(0);
 }
 
+void
+KGreeter::slotLoadPrevLang()
+{
+    int len, i;
+    QByteArray name;
+    char *lang;
+
+    // // XXX this should actually check for !CoreBusy - would it be safe?
+    // if (verify->coreState != KGVerify::CoreIdle) {
+    //     needLoad = true;
+    //     return;
+    // }
+    // //    needLoad = false;
+
+    prevLangValid = true;
+    name = curUser.toLocal8Bit();
+    gSendInt(G_ReadDmrc);
+    gSendStr(name.data());
+    gRecvInt(); // ignore status code ...
+    if ((len = name.length())) {
+	gSendInt(G_GetDmrc);
+        gSendStr("Language");
+        lang = gRecvStr();
+        if (lang) {
+	    for (int i = 0; i < languageTypes.count(); i++)
+	        if (languageTypes[i].type == lang) {
+		    gSendInt(G_PutLang);
+		    gSendStr("Language");
+		    gSendStr(languageTypes[i].type.toUtf8());
+		    free(lang);
+		    setPrevLang(languageTypes[i].action);
+		    return;
+	        }
+	    if (!langGroup->checkedAction())
+                KFMsgBox::box(this, sorrybox,
+                              i18n("Your saved language '%1' is not valid any more.\n"
+                                   "Please select a new one, otherwise the default language will be used.", lang));
+	    free(lang);
+            prevLangValid = false;
+        }
+    }
+    setPrevLang(0);
+}
+
 void // protected
 KGreeter::pluginSetup()
 {
@@ -663,6 +768,8 @@ KGreeter::verifyClear()
 {
     curUser.clear();
     slotUserEntered();
+    if (QAction *curSel = langGroup->checkedAction())
+        curSel->setChecked(false);
     if (QAction *curSel = sessGroup->checkedAction())
         curSel->setChecked(false);
 }
@@ -684,6 +791,23 @@ KGreeter::verifyOk()
         gSendStr("Session");
         gSendStr("default");
     }
+
+    if (QAction *curSel = langGroup->checkedAction()) {
+        gSendInt(G_PutDmrc);
+        gSendStr("Language");
+        gSendStr(languageTypes[curSel->data().toInt()].type.toUtf8());
+	gSendInt(G_PutLang);
+	gSendStr("Language");
+	gSendStr(languageTypes[curSel->data().toInt()].type.toUtf8());
+    } else if (!prevLangValid) {
+        gSendInt(G_PutDmrc);
+        gSendStr("Language");
+        gSendStr("en_US.utf-8");
+	gSendInt(G_PutLang);
+	gSendStr("Language");
+        gSendStr("en_US.utf-8");
+    }
+
     done(ex_login);
 }
 
@@ -692,8 +816,10 @@ KGreeter::verifyFailed()
 {
     if (userView)
         userView->setEnabled(false);
-    if (needLoad)
+    if (needLoad) {
         slotLoadPrevWM();
+	slotLoadPrevLang();
+    }
 }
 
 void
@@ -832,6 +958,11 @@ KStdGreeter::KStdGreeter()
         needSep = true;
     }
 
+    if (langMenu->actions().count() > 1) {
+        inserten(i18nc("@title:menu", "La&nguage"), Qt::ALT + Qt::Key_L, langMenu );
+        needSep = true;
+    }
+
     if (plugMenu) {
         inserten(i18nc("@title:menu", "&Authentication Method"), Qt::ALT + Qt::Key_A, plugMenu);
         needSep = true;
@@ -922,8 +1053,6 @@ KThemedGreeter::KThemedGreeter(KdmThemer
     KdmItem *itm;
     if ((itm = themer->findNode("pam-message"))) // done via msgboxes
         itm->setVisible(false);
-    if ((itm = themer->findNode("language_button"))) // not implemented yet
-        itm->setVisible(false);
 
     if (console_node) {
 #ifdef WITH_KDM_XCONSOLE
@@ -958,6 +1087,11 @@ KThemedGreeter::KThemedGreeter(KdmThemer
     tverify->selectPlugin(curPlugin);
     verify = tverify;
 
+    if (plugMenu) {
+        inserten(i18nc("@title:menu", "&Authentication Method"), Qt::ALT + Qt::Key_A, plugMenu);
+        needSep = true;
+    }
+
     if ((session_button = themer->findNode("session_button"))) {
         if (sessMenu->actions().count() <= 1) {
             session_button->setVisible(false);
@@ -968,11 +1102,10 @@ KThemedGreeter::KThemedGreeter(KdmThemer
             inserten(i18nc("@title:menu", "Session &Type"), Qt::ALT + Qt::Key_T, sessMenu);
             needSep = true;
         }
-    }
 
-    if (plugMenu) {
-        inserten(i18nc("@title:menu", "&Authentication Method"), Qt::ALT + Qt::Key_A, plugMenu);
-        needSep = true;
+	if (langMenu->actions().count() > 1) {
+	    inserten(i18nc("@title:menu", "La&nguage"), Qt::ALT + Qt::Key_L, langMenu );
+	}
     }
 
 #ifdef XDMCP
@@ -1074,11 +1207,19 @@ KThemedGreeter::slotThemeActivated(const
         accept();
     else if (id == "session_button")
         slotSessMenu();
+    else if (id == "language_button")
+        slotLangMenu();
     else if (id == "system_button")
         slotActionMenu();
 }
 
 void
+KThemedGreeter::slotLangMenu()
+{
+    langMenu->popup( mapToGlobal( language_button->rect().center() ) );
+}
+
+void
 KThemedGreeter::slotSessMenu()
 {
     sessMenu->popup(mapToGlobal(session_button->rect().center()));
Index: kde-workspace-4.11.13/kdm/kfrontend/kgreeter.h
===================================================================
--- kde-workspace-4.11.13.orig/kdm/kfrontend/kgreeter.h
+++ kde-workspace-4.11.13/kdm/kfrontend/kgreeter.h
@@ -37,6 +37,22 @@ class KConfigGroup;
 class QListWidgetItem;
 class QActionGroup;
 
+struct LangType {
+  QString name, type;
+  QAction *action;
+  bool hid;
+  int prio;
+
+  LangType() {}
+  LangType(const QString &n, const QString &t, bool h, int p ) :
+      name(n), type(t), action(0), hid(h), prio(p) {}
+  bool operator<( const LangType &st ) const {
+      return hid != st.hid ? hid < st.hid :
+	     prio != st.prio ? prio < st.prio :
+             name < st.name;
+  }
+};
+
 struct SessType {
     QString name, type;
     QAction *action;
@@ -66,26 +82,35 @@ class KGreeter : public KGDialog, public
     void reject();
     void slotUserClicked(QListWidgetItem *);
     void slotSessionSelected();
+    void slotLanguageSelected();
     void slotUserEntered();
 
   protected:
     void insertUser(const QImage &, const QString &, struct passwd *);
     void insertUsers();
     void putSession(const QString &, const QString &, bool, const char *);
+    void putLanguage( const QString &, const QString & );
     void insertSessions();
+    void insertLanguages();
     virtual void pluginSetup();
     void setPrevWM(QAction *);
+    void setPrevLang(QAction *);
 
     QString curUser, dName;
     KConfigGroup *stsGroup;
     UserListView *userView;
     QStringList *userList;
     QMenu *sessMenu;
+    QMenu *langMenu;
     QActionGroup *sessGroup;
+    QActionGroup *langGroup;
     QVector<SessType> sessionTypes;
+    QVector<LangType> languageTypes;
     int nNormals, nSpecials;
     QAction *curPrev;
+    QAction *langSel;
     bool prevValid;
+    bool prevLangValid;
     bool needLoad;
 
     static int curPlugin;
@@ -93,6 +118,7 @@ class KGreeter : public KGDialog, public
 
   private Q_SLOTS:
     void slotLoadPrevWM();
+    void slotLoadPrevLang();
 
   public: // from KGVerifyHandler
     virtual void verifyPluginChanged(int id);
@@ -134,6 +160,7 @@ class KThemedGreeter : public KGreeter {
   public Q_SLOTS:
     void slotThemeActivated(const QString &id);
     void slotSessMenu();
+    void slotLangMenu();
     void slotActionMenu();
     void slotDebugToggled();
 
@@ -148,7 +175,7 @@ class KThemedGreeter : public KGreeter {
     KdmThemer *themer;
     KdmItem *caps_warning, *xauth_warning, *pam_error, *timed_label,
             *userlist_node, *userlist_rect,
-            *session_button, *system_button;
+            *session_button, *language_button, *system_button;
 
 //  public: // from KGVerifyHandler
 //    virtual void verifyFailed();

Reply to: