grub-devel
[Top][All Lists]
Advanced

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

[PATCH] term: keymapforce command for lack of at_keyboard


From: Daniel Tang
Subject: [PATCH] term: keymapforce command for lack of at_keyboard
Date: Mon, 10 Oct 2022 01:35:49 -0400

>From ba4dfde5037743561bafab617f48163e72989d5c Mon Sep 17 00:00:00 2001
From: Daniel Tang <danielzgtg.opensource@gmail.com>
Date: Fri, 23 Sep 2022 23:15:16 -0400
Subject: [PATCH] term: keymapforce command for lack of at_keyboard

This adds a command named `keymapforce`. Current behaviour is unchanged
until it is run. The use case is people with non-QWERTY keyboards on
computers without support for anything else than `terminal_input
console`. For example, as a Dvorak user on a Microsoft Surface Pro 7,
I would like to enter my encryption key at boot in my preferred layout
using the UEFI touchscreen keyboard in order to log in when encryption
is set up.

The new `keymapforce` command is modeled after the `terminal_x`
commands without arguments. This feature is disabled by default to
avoid regressions, and this command toggles it. A
`grub_term_force_keymap` function pointer global variable is used to
hook the end of the key reception logic. The new code simulates reverse
engineering the intercepted key back into its source on a US keyboard
layout, and then replaying the calculated key code on the keyboard with
the proper layout. This works around any specific `terminal_input`s
that don't support key mapping and insist on passing the character
instead of the key code. This way of moving the key remapping to until
after the specific terminal handlers was the only viable way of adding
support for gettings key mapping working on `terminal_input console`.

I wanted to avoid the global hook by adding instead another
`terminal_input` type but that would complicate fini and memory safety.
I tried to put the code in `console.c` but got linker errors and
software architectural obstacles. The reverse lookup is a loop but
it's still fast, close to o(1), and what I wanted to do being
calculating the inverse of the `layout_us` map manually would be hard
to maintain to keep in sync without `static_assert`.

This bug has existed for 10 years. Many users are affected by this bug:
- bbs.archlinux.org/viewtopic.php?id=240739
- archived.forum.manjaro.org
/t/does-luks-in-manjaro-support-azerty-keyboards/87582/6
- bugs.launchpad.net/ubuntu/+source/grub2/+bug/1914953
- www.mail-archive.com
/search?l=bug-grub@gnu.org&q=subject:%22RE%5C%3A
+Changing+default+keyboard+layout%22&o=newest&f=1
- askubuntu.com/a/1391658/1004020

I have tested the following:
- Machines
  - VirtualBox BIOS Ubuntu 22.04
  - surface-linux Ubuntu 22.10
    - UEFI touchscreen
    - Type cover keyboard
- Actions
  - Timeout then boot
  - Enter to boot normally
  - `c` to command line then running a few commands
- `terminal_input`
  - `console`
  - `at_keyboard`
- `terminal_output`
  - `gfxterm`
  - `console`
- `keymap` from `grub-mklayout`
  - `none` (qwerty)
  - `dvorak`
  - `us -variant colemak`
  - `fr` (azerty)
    - The accents don't work but the ASCII does. Someone can add
Unicode support in another patch.

Signed-off-by: Daniel Tang <danielzgtg.opensource@gmail.com>
---
 grub-core/commands/keylayouts.c | 48 ++++++++++++++++++++++++++++-----
 grub-core/kern/term.c           |  3 +++
 include/grub/term.h             |  1 +
 3 files changed, 45 insertions(+), 7 deletions(-)

diff --git a/grub-core/commands/keylayouts.c b/grub-core/commands/keylayouts.c
index aa3ba34f2..5ca130904 100644
--- a/grub-core/commands/keylayouts.c
+++ b/grub-core/commands/keylayouts.c
@@ -135,26 +135,30 @@ map_key_core (int code, int status, int *alt_gr_consumed)
   if (code >= GRUB_KEYBOARD_LAYOUTS_ARRAY_SIZE)
     return 0;
 
+  struct grub_keyboard_layout *current_layout = grub_current_layout;
+  if (grub_term_force_keymap)
+    current_layout = &layout_us;
+
   if (status & GRUB_TERM_STATUS_RALT)
     {
       if (status & (GRUB_TERM_STATUS_LSHIFT | GRUB_TERM_STATUS_RSHIFT))
        {
-         if (grub_current_layout->keyboard_map_shift_l3[code])
+         if (current_layout->keyboard_map_shift_l3[code])
            {
              *alt_gr_consumed = 1;
-             return grub_current_layout->keyboard_map_shift_l3[code];
+             return current_layout->keyboard_map_shift_l3[code];
            }
        }
-      else if (grub_current_layout->keyboard_map_l3[code])
+      else if (current_layout->keyboard_map_l3[code])
        {
          *alt_gr_consumed = 1;
-         return grub_current_layout->keyboard_map_l3[code];
+         return current_layout->keyboard_map_l3[code];
        }
     }
   if (status & (GRUB_TERM_STATUS_LSHIFT | GRUB_TERM_STATUS_RSHIFT))
-    return grub_current_layout->keyboard_map_shift[code];
+    return current_layout->keyboard_map_shift[code];
   else
-    return grub_current_layout->keyboard_map[code];
+    return current_layout->keyboard_map[code];
 }
 
 unsigned
@@ -293,15 +297,45 @@ grub_cmd_keymap (struct grub_command *cmd __attribute__ 
((unused)),
   return grub_errno;
 }
 
-static grub_command_t cmd;
+static unsigned
+grub_term_force_map_key (unsigned key)
+{
+  if (! key)
+    return GRUB_TERM_NO_KEY;
+  unsigned i;
+  for (i = 0; i < GRUB_KEYBOARD_LAYOUTS_ARRAY_SIZE; i++)
+    // Loop only once so that '+' doesn't go to numpad
+    if (layout_us.keyboard_map[i] == key)
+      return grub_current_layout->keyboard_map[i];
+    else if (layout_us.keyboard_map_shift[i] == key)
+      return grub_current_layout->keyboard_map_shift[i];
+  return key; // Fallback for enter key
+}
+
+static grub_err_t
+grub_cmd_keymap_force (struct grub_command *cmd __attribute__ ((unused)),
+                int argc __attribute__ ((unused)), char *argv[] __attribute__ 
((unused)))
+{
+  if (grub_term_force_keymap)
+    grub_term_force_keymap = NULL;
+  else
+    grub_term_force_keymap = grub_term_force_map_key;
+  return GRUB_ERR_NONE;
+}
+
+static grub_command_t cmd, cmd2;
 
 GRUB_MOD_INIT(keylayouts)
 {
   cmd = grub_register_command ("keymap", grub_cmd_keymap,
                               0, N_("Load a keyboard layout."));
+  cmd2 = grub_register_command ("keymapforce", grub_cmd_keymap_force,
+                              0, N_("Toggles the keymap strategy."));
 }
 
 GRUB_MOD_FINI(keylayouts)
 {
+  grub_term_force_keymap = NULL;
+  grub_unregister_command (cmd2);
   grub_unregister_command (cmd);
 }
diff --git a/grub-core/kern/term.c b/grub-core/kern/term.c
index 14d596498..a7dfffb8c 100644
--- a/grub-core/kern/term.c
+++ b/grub-core/kern/term.c
@@ -34,6 +34,7 @@ grub_uint8_t grub_term_highlight_color = 
GRUB_TERM_DEFAULT_HIGHLIGHT_COLOR;
 
 void (*grub_term_poll_usb) (int wait_for_completion) = NULL;
 void (*grub_net_poll_cards_idle) (void) = NULL;
+unsigned (*grub_term_force_keymap) (unsigned key) = NULL;
 
 /* Put a Unicode character.  */
 static void
@@ -97,6 +98,8 @@ grub_getkey_noblock (void)
   FOR_ACTIVE_TERM_INPUTS(term)
   {
     int key = term->getkey (term);
+    if (grub_term_force_keymap)
+      key = grub_term_force_keymap (key);
     if (key != GRUB_TERM_NO_KEY)
       return key;
   }
diff --git a/include/grub/term.h b/include/grub/term.h
index 7f1a14c84..a2fd0b9ca 100644
--- a/include/grub/term.h
+++ b/include/grub/term.h
@@ -460,6 +460,7 @@ grub_print_spaces (struct grub_term_output *term, int 
number_spaces)
 }
 
 extern void (*EXPORT_VAR (grub_term_poll_usb)) (int wait_for_completion);
+extern unsigned (*EXPORT_VAR (grub_term_force_keymap)) (unsigned key);
 
 #define GRUB_TERM_REPEAT_PRE_INTERVAL 400
 #define GRUB_TERM_REPEAT_INTERVAL 50
-- 
2.37.2







reply via email to

[Prev in Thread] Current Thread [Next in Thread]