Hello all,
I bought a Logitech keyboard recently and to my bitter surprise, the right Ctrl key was not behaving like I would expect. Instead it had a bizarre logo with stars popping out of a square which seemed to do nothing when I pressed it, and after a bit of research I learned that Logitech intended that key... to be an "AI key" that would launch Copilot.
Insert your favorite curse here
I use single-handed Ctrl+arrow shortcuts on a daily basis and I want my right Ctrl key back. And I want it to work like a Ctrl key everywhere. In the console, in X11, in virtual machines, every dash where.
So what were the options. Well, the genius at Logitech that had the idea must have received a bit of backfire (immanent justice!), because they provided a software-only fix that is able to remap that key to Right Ctrl. But for Windows and macOS only, and only if you install their telemetry-packed ad-stuffed "driver companion" Logi Options+ userland bloatware. FreeBSD? No way.
I then investigated the possibility of remapping that key at the firmware level with Logitech's HID++ protocol (e.g. with Solaar). No way either, a lot of keys can be remapped (F1...F12 etc), but this darn AI key not.
A custom remapped kbd file would not do the job for me as that would work only in the console. And besides, I discovered that the Logitech keyboard emits in fact a combo of two scancodes when that key is pressed. That is to say, it sends Left_Super + Left_Shift(!) when the key is pressed, and Left_Shift+Left_Super when it's released. Admire the brilliant idea of involving a common modifier in the combo.
This leaves us with no other choice than patching at the HID driver level, i.e. in the kernel.
Here we are. I submit this patch to the community, hoping that perhaps it can evolve into some sort of boot-time option that we could use in the GENERIC kernel to toggle this behaviour on/off.
How to use:
As root
1. Save this patch as e.g.
2. Move to the kernel sources directory (install them first with pkgbase)
3. Patch sys/dev/hid/hkbd.c:
4. Generate a custom configuration for this new kernel (it's best to keep the GENERIC kernel intact):
5. Build the beast
6. Deploy the results to your boot partition, under a new directory name
7. Add this at the top of /boot/loader.conf to indicate that you want to load this kernel:
8. Reboot.
If nothing went wrong, the world should now be a better place.
As I suggested above, I would find it oh so handy if that could be merged upstream and controlled by a boot-time flag, because I suspect we'll have to live with these new "AI key" keyboards, and I now see laptop makers jumping into the pit too. Not sure at this point if the same combo is used, but at least this patch does the job for the Logitechs.
I bought a Logitech keyboard recently and to my bitter surprise, the right Ctrl key was not behaving like I would expect. Instead it had a bizarre logo with stars popping out of a square which seemed to do nothing when I pressed it, and after a bit of research I learned that Logitech intended that key... to be an "AI key" that would launch Copilot.
Insert your favorite curse here
I use single-handed Ctrl+arrow shortcuts on a daily basis and I want my right Ctrl key back. And I want it to work like a Ctrl key everywhere. In the console, in X11, in virtual machines, every dash where.
So what were the options. Well, the genius at Logitech that had the idea must have received a bit of backfire (immanent justice!), because they provided a software-only fix that is able to remap that key to Right Ctrl. But for Windows and macOS only, and only if you install their telemetry-packed ad-stuffed "driver companion" Logi Options+ userland bloatware. FreeBSD? No way.
I then investigated the possibility of remapping that key at the firmware level with Logitech's HID++ protocol (e.g. with Solaar). No way either, a lot of keys can be remapped (F1...F12 etc), but this darn AI key not.
A custom remapped kbd file would not do the job for me as that would work only in the console. And besides, I discovered that the Logitech keyboard emits in fact a combo of two scancodes when that key is pressed. That is to say, it sends Left_Super + Left_Shift(!) when the key is pressed, and Left_Shift+Left_Super when it's released. Admire the brilliant idea of involving a common modifier in the combo.
This leaves us with no other choice than patching at the HID driver level, i.e. in the kernel.
Here we are. I submit this patch to the community, hoping that perhaps it can evolve into some sort of boot-time option that we could use in the GENERIC kernel to toggle this behaviour on/off.
C:
--- sys/dev/hid/hkbd.c 2026-08-24 23:51:30.045911000 +0200
+++ sys/dev/hid/hkbd.c 2026-09-20 15:02:46.869967000 +0200
@@ -207,6 +207,28 @@
uint8_t sc_repeat_key;
uint8_t sc_buffer[HKBD_BUFFER_SIZE];
+
+ /*
+ * Some ill-inspired Logitech product manager thought it would boost their sales if they turned a common,
+ * standard, keyboard key - namely the right Ctrl key - into a childish gadget celebrating the latest
+ * fashionable trend in IT. As a result people get keyboards with an unusable right Ctrl key that
+ * does not do what they want, and what every sane keyboard user is accustomed to expect.
+ * The goal of this patch is to put that right Ctrl key back in the place where God intended it to be.
+ */
+#define AIKEY_TRAP 1
+
+#ifdef AIKEY_TRAP /* AI key death trap BEGIN */
+#define SHIFT_L 0xE1
+#define SUPER_L 0xE3
+#define CTRL_R 0xE4
+#define KEY_WITHHELD 0x8000 /* this bit looks unused */
+ struct callout sc_aikeypress_callout;
+ struct callout sc_aikeyrelease_callout;
+ uint8_t sc_aikey_statebits;
+#define AIKEY_PRESS_PENDING (1 << 0)
+#define AIKEY_PRESSED (1 << 1)
+#define AIKEY_RELEASE_PENDING (1 << 2)
+#endif /* AI key death trap END */
};
#define KEY_NONE 0x00
@@ -318,6 +340,9 @@
static int hkbd_ioctl(keyboard_t *, u_long, caddr_t);
static int hkbd_enable(keyboard_t *);
static int hkbd_disable(keyboard_t *);
+#ifdef AIKEY_TRAP /* AI key death trap BEGIN */
+static void hkbd_put_key(struct hkbd_softc *, uint32_t);
+#endif /* AI key death trap END */
static void hkbd_interrupt(struct hkbd_softc *);
static task_fn_t hkbd_event_keyinput;
@@ -385,6 +410,22 @@
callout_reset_sbt(&sc->sc_callout, sc->sc_co_basetime, prec,
hkbd_timeout, sc, C_ABSOLUTE);
}
+
+#ifdef AIKEY_TRAP /* AI key death trap BEGIN */
+static void
+hkbd_aikey_timeout(void *arg)
+{
+ struct hkbd_softc *sc = arg;
+ if (sc->sc_aikey_statebits & AIKEY_PRESS_PENDING) {
+ hkbd_put_key(sc, SUPER_L | KEY_PRESS | KEY_WITHHELD); /* no Shift_L press arrived in due time: emit the withheld Super_L */
+ sc->sc_aikey_statebits &= ~AIKEY_PRESS_PENDING; /* and remember no AI key press event is pending anymore */
+ }
+ if (sc->sc_aikey_statebits & AIKEY_RELEASE_PENDING) {
+ hkbd_put_key(sc, SHIFT_L | KEY_RELEASE | KEY_WITHHELD); /* no Super_L release arrived in due time: emit the withheld Shift_L */
+ sc->sc_aikey_statebits &= ~AIKEY_RELEASE_PENDING; /* and remember no AI key release event is pending anymore */
+ }
+}
+#endif /* AI key death trap END */
static void
hkbd_put_key(struct hkbd_softc *sc, uint32_t key)
@@ -393,6 +434,38 @@
HKBD_LOCK_ASSERT(sc);
+#ifdef AIKEY_TRAP /* AI key death trap BEGIN */
+ /* NOTE: the Logitech "AI key" emits: Super_L PRESS + Shift_L PRESS ... Shift_L RELEASE + Super_L RELEASE */
+ if (key & KEY_WITHHELD)
+ key &= ~KEY_WITHHELD; /* don't run the keys we emit after withholding them through our logic */
+ else if (((key & KEY_RELEASE) == 0) && !(sc->sc_aikey_statebits & AIKEY_PRESSED)) { /* key press while AI key is inactive? */
+ if (KEY_INDEX(key) == SUPER_L) { /* Super_L press while AI key is inactive? */
+ sc->sc_aikey_statebits |= AIKEY_PRESS_PENDING; /* remember a possible AI key press event is pending */
+ callout_reset_sbt(&sc->sc_aikeypress_callout, sbinuptime() + 30 * SBT_1MS, 0, hkbd_aikey_timeout, sc, C_ABSOLUTE);
+ return; /* hold it and wait for the next key, or 30 msec, whichever comes first, before emitting it */
+ }
+ else if ((KEY_INDEX(key) == SHIFT_L) && (sc->sc_aikey_statebits & AIKEY_PRESS_PENDING)) { /* Shift_L press while in possible "AI key" combo? */
+ callout_stop(&sc->sc_aikeypress_callout); /* cancel the deadline */
+ key = CTRL_R | KEY_PRESS; /* turn that "AI key" press into a Ctrl_R key press */
+ sc->sc_aikey_statebits &= ~AIKEY_PRESS_PENDING; /* remember no AI key press event is pending anymore */
+ sc->sc_aikey_statebits |= AIKEY_PRESSED; /* and remember the AI key is now active */
+ }
+ }
+ else if (sc->sc_aikey_statebits & AIKEY_PRESSED) { /* key relase while AI key is active? */
+ if (KEY_INDEX(key) == SHIFT_L) { /* Shift_L release while AI key is active? */
+ sc->sc_aikey_statebits |= AIKEY_RELEASE_PENDING; /* remember a possible AI key release event is pending */
+ callout_reset_sbt(&sc->sc_aikeyrelease_callout, sbinuptime() + 30 * SBT_1MS, 0, hkbd_aikey_timeout, sc, C_ABSOLUTE);
+ return; /* hold it and wait for the next key, or 30 msec, whichever comes first, before emitting it */
+ }
+ else if ((KEY_INDEX(key) == SUPER_L) && (sc->sc_aikey_statebits & AIKEY_RELEASE_PENDING)) { /* Super_L release while in possible "AI key" combo? */
+ callout_stop(&sc->sc_aikeyrelease_callout); /* cancel the deadline */
+ key = CTRL_R | KEY_RELEASE; /* turn that "AI key" release into a Ctrl_R key release */
+ sc->sc_aikey_statebits &= ~AIKEY_RELEASE_PENDING; /* remember no AI key release event is pending anymore */
+ sc->sc_aikey_statebits &= ~AIKEY_PRESSED; /* and remember the AI key is now inactive */
+ }
+ }
+#endif /* AI key death trap END */
+
DPRINTF("0x%02x (%d) %s\n", key, key,
(key & KEY_RELEASE) ? "released" : "pressed");
@@ -969,6 +1042,11 @@
mtx_init(&sc->sc_mtx, "hkbd lock", NULL, MTX_DEF);
TASK_INIT(&sc->sc_task, 0, hkbd_event_keyinput, sc);
callout_init_mtx(&sc->sc_callout, &sc->sc_mtx, 0);
+#ifdef AIKEY_TRAP /* AI key death trap BEGIN */
+ callout_init_mtx(&sc->sc_aikeypress_callout, &sc->sc_mtx, 0);
+ callout_init_mtx(&sc->sc_aikeyrelease_callout, &sc->sc_mtx, 0);
+ sc->sc_aikey_statebits = 0;
+#endif /* AI key death trap END */
hidbus_set_intr(dev, hkbd_intr_callback, sc);
/* interrupt handler will be called with hkbd mutex taken */
@@ -1105,6 +1183,10 @@
HKBD_LOCK(sc);
callout_stop(&sc->sc_callout);
+#ifdef AIKEY_TRAP /* AI key death trap BEGIN */
+ callout_stop(&sc->sc_aikeypress_callout);
+ callout_stop(&sc->sc_aikeyrelease_callout);
+#endif /* AI key death trap END */
HKBD_UNLOCK(sc);
/* kill any stuck keys */
As root
1. Save this patch as e.g.
/root/hid-aikey.patch, then2. Move to the kernel sources directory (install them first with pkgbase)
cd /usr/src3. Patch sys/dev/hid/hkbd.c:
patch < /root/hid-aikey.patch4. Generate a custom configuration for this new kernel (it's best to keep the GENERIC kernel intact):
cp sys/$(uname -m)/conf/GENERIC sys/$(uname -m)/conf/GENERIC-NOAIKEY5. Build the beast
make -j$(sysctl -n hw.ncpu) buildkernel KERNCONF=GENERIC-NOAIKEY6. Deploy the results to your boot partition, under a new directory name
make installkernel KERNCONF=GENERIC-NOAIKEY INSTKERNNAME=GENERIC-NOAIKEY7. Add this at the top of /boot/loader.conf to indicate that you want to load this kernel:
kernel="GENERIC-NOAIKEY"8. Reboot.
shutdown -r nowIf nothing went wrong, the world should now be a better place.
As I suggested above, I would find it oh so handy if that could be merged upstream and controlled by a boot-time flag, because I suspect we'll have to live with these new "AI key" keyboards, and I now see laptop makers jumping into the pit too. Not sure at this point if the same combo is used, but at least this patch does the job for the Logitechs.