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();