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: