[systemd-devel] [Patch] NumLock setting from vconsole.conf

Matthias Berndt matthias_berndt at gmx.de
Wed Feb 6 06:12:05 PST 2013


Hi David,

that you for your feedback.

> > The ioctls return 0 and don't affect X. After switching back to text mode,
> > the changes do take effect.
> 
> How do you know that?
I've tried it and this is the behaviour I've observed.

> Looking at the kernel code it seems that the
> LEDs are immediately set with the keyboard_tasklet/kbd_bh. So if the
> xserver doesn't grab the input devices, then this _can_ break the
> xserver input handling if it caches these states. Or am I missing
> something here?
> 
> The problem with these ioctl's is that they affect global keyboard
> state. They don't modify the VT state but instead send LED_* events
> directly to the hardware to modify hardware state. That is, every
> other application currently using the keyboards will be affected by
> this.
> 
> The kernel-code also says:
>   /* the ioctls below read/set the flags usually shown in the leds */
>   /* don't use them - they will go away without warning */
> Did you try to investigate why these are supposed to go away?
No. I had seen a similar hint toward the end of the console_ioctl man page and 
decided to ignore it. These ioctls have been around forever, tools like 
setleds(1) rely on them. We know how Linus feels about breaking userspace, so 
they're not going to go away. Also, to my knowledge there's no other way to do 
this. One could launch setleds(1) instead and rely on it being changed in the 
unlikely event that these ioctls go away. That would however require patching 
setleds(1) in order to implement the optimisation suggested by Lennart.

> The VT layer also caches led-states and updates them only if they
> differ. However, if you manually change led-states via linux input API
> then this cache will be invalid and the whole concept is broken. So I
> think this API isn't really what we want here. Instead, you probably
> want to modify all input devices manually instead. But then this
> configuration option is at the wrong place.
I've observed that when you switch from an X vt with NumLock off to a text vt 
with NumLock on, the LED will not light up, but the numpad keys generate 
numbers nevertheless -- the caching you've mentioned may be the reason for 
that.
Anyway, this happens regardless of whether you enable NumLock with the 
keyboard key or with the ioctl, so this issue, aside from being cosmetic, is 
orthogonal to the patch at hand.

> All in all, it sounds nice to have this option at vconsole.conf, but
> it affects _global_ state, not _local_ VT state so other users might
> be really confused if they wonder why their Xserver starts with
> NUMLOCK enabled and they cannot find the option to disable it (why
> would one look into vconsole.conf to modify xserver or input
> behavior?).
I just tried starting an X server on a console where NumLock is enabled, and 
NumLock was disabled in that X session. After terminating it, it was on again. 
Afaics it all works as one would expect it to.

The attached version contains whitespace fixes and is formatted with git 
format-patch as suggested by Colin.

Cheers
Matthias
-------------- next part --------------
>From bd82c41cb521e2e09015c32bd4d80231c6624e4d Mon Sep 17 00:00:00 2001
From: Matthias Berndt <matthias_berndt at gmx.de>
Date: Wed, 6 Feb 2013 14:01:43 +0100
Subject: [PATCH] Add support for keyboard led flags, i. e. NumLock, ScrollLock
 and CapsLock.

---
 man/vconsole.conf.xml         |  21 ++++++-
 src/vconsole/vconsole-setup.c | 126 +++++++++++++++++++++++++++++++++++-------
 2 files changed, 123 insertions(+), 24 deletions(-)

diff --git a/man/vconsole.conf.xml b/man/vconsole.conf.xml
index 45156b7..e3f0068 100644
--- a/man/vconsole.conf.xml
+++ b/man/vconsole.conf.xml
@@ -74,7 +74,10 @@
                 <varname>vconsole.keymap.toggle=</varname>,
                 <varname>vconsole.font=</varname>,
                 <varname>vconsole.font.map=</varname>,
-                <varname>vconsole.font.unimap=</varname> may be used
+                <varname>vconsole.font.unimap=</varname>,
+                <varname>vconsole.scroll_lock</varname>,
+                <varname>vconsole.num_lock</varname>,
+                <varname>vconsole.caps_lock</varname> may be used
                 to override the console settings at boot.</para>
 
                 <para>Depending on the operating system other
@@ -115,6 +118,17 @@
                                 font map.</para></listitem>
                         </varlistentry>
 
+                        <varlistentry>
+                                <term><varname>SCROLL_LOCK=</varname></term>
+                                <term><varname>NUM_LOCK=</varname></term>
+                                <term><varname>CAPS_LOCK=</varname></term>
+                                <listitem><para>These boolean variables control
+                                the state of the respective keyboard flags.
+                                When an invalid or no value is specified,
+                                the state is left unchanged.
+                                </para></listitem>
+                        </varlistentry>
+
                 </variablelist>
         </refsect1>
 
@@ -122,12 +136,13 @@
                 <title>Example</title>
 
                 <example>
-                        <title>German keyboard and console</title>
+                        <title>German keyboard and console with NumLock enabled:</title>
 
                         <para><filename>/etc/vconsole.conf:</filename></para>
 
                         <programlisting>KEYMAP=de-latin1
-FONT=latarcyrheb-sun16</programlisting>
+FONT=latarcyrheb-sun16
+NUM_LOCK=on</programlisting>
                 </example>
 
         </refsect1>
diff --git a/src/vconsole/vconsole-setup.c b/src/vconsole/vconsole-setup.c
index 6501705..45018eb 100644
--- a/src/vconsole/vconsole-setup.c
+++ b/src/vconsole/vconsole-setup.c
@@ -158,6 +158,35 @@ static int font_load(const char *vc, const char *font, const char *map, const ch
         return 0;
 }
 
+struct vt_iter {
+        int index;
+        int fd;
+};
+
+static void vt_iter_init(struct vt_iter *it) {
+        it->index = 0;
+        it->fd = -1;
+}
+
+static bool vt_iter_next(struct vt_iter *it) {
+        if (it->fd >= 0)
+                close_nointr_nofail(it->fd);
+        while (++(it->index) < 16) {
+                char vcname[16];
+
+                /* skip non-allocated ttys */
+                snprintf(vcname, sizeof(vcname), "/dev/vcs%i", it->index);
+                if (access(vcname, F_OK) < 0)
+                        continue;
+
+                snprintf(vcname, sizeof(vcname), "/dev/tty%i", it->index);
+                it->fd = open_terminal(vcname, O_RDWR|O_CLOEXEC);
+                if (it->fd >= 0)
+                        return true;
+        }
+        return false;
+}
+
 /*
  * A newly allocated VT uses the font from the active VT. Here
  * we update all possibly already allocated VTs with the configured
@@ -166,41 +195,78 @@ static int font_load(const char *vc, const char *font, const char *map, const ch
  */
 static void font_copy_to_all_vcs(int fd) {
         struct vt_stat vcs;
-        int i;
         int r;
+        struct vt_iter it;
 
-        /* get active, and 16 bit mask of used VT numbers */
+        /* get active VT */
         zero(vcs);
         r = ioctl(fd, VT_GETSTATE, &vcs);
         if (r < 0)
                 return;
 
-        for (i = 1; i <= 15; i++) {
-                char vcname[16];
-                int vcfd;
+        for (vt_iter_init(&it); vt_iter_next(&it); ) {
                 struct console_font_op cfo;
 
-                if (i == vcs.v_active)
-                        continue;
-
-                /* skip non-allocated ttys */
-                snprintf(vcname, sizeof(vcname), "/dev/vcs%i", i);
-                if (access(vcname, F_OK) < 0)
-                        continue;
-
-                snprintf(vcname, sizeof(vcname), "/dev/tty%i", i);
-                vcfd = open_terminal(vcname, O_RDWR|O_CLOEXEC);
-                if (vcfd < 0)
+                if (it.index == vcs.v_active)
                         continue;
 
                 /* copy font from active VT, where the font was uploaded to */
                 zero(cfo);
                 cfo.op = KD_FONT_OP_COPY;
                 cfo.height = vcs.v_active-1; /* tty1 == index 0 */
-                ioctl(vcfd, KDFONTOP, &cfo);
+                ioctl(it.fd, KDFONTOP, &cfo);
+        }
+}
+
+static int set_led_flags(int fd, int num_flags, const char *flags, char *const *flag_values) {
+        char old_mask, new_mask = 0, flags_to_set = 0;
+        int r = 0;
+
+        for (int i = 0; i < num_flags; ++i) {
+                char flag = flags[i] | flags[i] << 4;
+                if (flag_values[i] == NULL)
+                        continue;
+                switch (parse_boolean(flag_values[i])) {
+                case 0:
+                        new_mask &= ~flag;
+                        flags_to_set |= flag;
+                        break;
+                case 1:
+                        new_mask |= flag;
+                        flags_to_set |= flag;
+                        break;
+                default:
+                        log_warning("invalid value for keyboard led flag: \"%s\"", flag_values[i]);
+                }
+        }
 
-                close_nointr_nofail(vcfd);
+        if (!flags_to_set) {
+                return 0;
         }
+
+        if (ioctl(fd, KDGKBLED, &old_mask) < 0) {
+                r = -errno;
+                log_warning("failed to get keyboard led flags: %s", strerror(errno));
+                return r;
+        }
+
+        new_mask |= old_mask & ~flags_to_set;
+        if (new_mask != old_mask && ioctl(fd, KDSKBLED, new_mask) < 0) {
+                r = -errno;
+                log_warning("failed to set keyboard led flag status: %s", strerror(errno));
+                return r;
+        }
+
+        return 0;
+}
+
+static int set_led_flags_on_all_vcs(int num_flags, const char *flags, char *const *flag_values) {
+        int r = 0;
+        struct vt_iter it;
+        for (vt_iter_init(&it); vt_iter_next(&it); ) {
+                r = set_led_flags(it.fd, num_flags, flags, flag_values) || r;
+        }
+        return -r;
 }
 
 int main(int argc, char **argv) {
@@ -210,10 +276,12 @@ int main(int argc, char **argv) {
         char *vc_font = NULL;
         char *vc_font_map = NULL;
         char *vc_font_unimap = NULL;
+        const char vc_led_flags[] = { LED_SCR, LED_NUM, LED_CAP };
+        char *vc_led_flag_values[ELEMENTSOF(vc_led_flags)] = { };
         int fd = -1;
         bool utf8;
         pid_t font_pid = 0, keymap_pid = 0;
-        bool font_copy = false;
+        bool apply_to_all_vcs = false;
         int r = EXIT_FAILURE;
 
         log_set_target(LOG_TARGET_AUTO);
@@ -226,7 +294,7 @@ int main(int argc, char **argv) {
                 vc = argv[1];
         else {
                 vc = "/dev/tty0";
-                font_copy = true;
+                apply_to_all_vcs = true;
         }
 
         fd = open_terminal(vc, O_RDWR|O_CLOEXEC);
@@ -251,6 +319,9 @@ int main(int argc, char **argv) {
                                    "vconsole.font", &vc_font,
                                    "vconsole.font.map", &vc_font_map,
                                    "vconsole.font.unimap", &vc_font_unimap,
+                                   "vconsole.scroll_lock", vc_led_flag_values,
+                                   "vconsole.num_lock", vc_led_flag_values + 1,
+                                   "vconsole.caps_lock", vc_led_flag_values + 2,
                                    NULL);
 
                 if (r < 0 && r != -ENOENT)
@@ -266,6 +337,9 @@ int main(int argc, char **argv) {
                                    "FONT", &vc_font,
                                    "FONT_MAP", &vc_font_map,
                                    "FONT_UNIMAP", &vc_font_unimap,
+                                   "SCROLL_LOCK", vc_led_flag_values,
+                                   "NUM_LOCK", vc_led_flag_values + 1,
+                                   "CAPS_LOCK", vc_led_flag_values + 2,
                                    NULL);
 
                 if (r < 0 && r != -ENOENT)
@@ -282,13 +356,20 @@ int main(int argc, char **argv) {
             font_load(vc, vc_font, vc_font_map, vc_font_unimap, &font_pid) >= 0)
                 r = EXIT_SUCCESS;
 
+        if (apply_to_all_vcs) {
+                if (set_led_flags_on_all_vcs(ELEMENTSOF(vc_led_flags), vc_led_flags, vc_led_flag_values) != 0)
+                        r = EXIT_FAILURE;
+        }
+        else if (set_led_flags(fd, ELEMENTSOF(vc_led_flags), vc_led_flags, vc_led_flag_values))
+                r = EXIT_FAILURE;
+
 finish:
         if (keymap_pid > 0)
                 wait_for_terminate_and_warn(KBD_LOADKEYS, keymap_pid);
 
         if (font_pid > 0) {
                 wait_for_terminate_and_warn(KBD_SETFONT, font_pid);
-                if (font_copy)
+                if (apply_to_all_vcs)
                         font_copy_to_all_vcs(fd);
         }
 
@@ -296,6 +377,9 @@ finish:
         free(vc_font);
         free(vc_font_map);
         free(vc_font_unimap);
+        free(vc_led_flag_values[0]);
+        free(vc_led_flag_values[1]);
+        free(vc_led_flag_values[2]);
 
         if (fd >= 0)
                 close_nointr_nofail(fd);
-- 
1.8.1



More information about the systemd-devel mailing list