Table of Contents
Framework and the EC
Framework — for those unaware — is the coolest laptop manufacturer ever. Their whole shtick is producing laptops that give the user the ability to easily and effortlessly disassemble, repair, and modify their hardware. I highly suggest checking them out if you’re interested in computer hardware at all. The laptops even have hotswappable I/O!
Anyways getting back on topic, Framework has also been giving power to the user on the software-side of things too! A good while ago they open-sourced the code for the embedded controller of their laptops, which offers all sorts of possibilities for customization of the keyboard, LED lights, and more.
LED Fun!
This is an area of the EC which I have not really looked at or touched much. I do want to play around with this a lot more in the coming future though! So far just for shits-and-giggles, I’ve patched the EC to make the power-button LED green instead of the normal boring white:
~/board/hx20/led.c
@@ -283,22 +283,22 @@ static void led_set_power(void)
/* don’t light up when at lid close */
if (!lid_is_open()) {
set_pwr_led_color(PWM_LED2, -1);
- enable_pwr_breath(PWM_LED2, EC_LED_COLOR_WHITE, breath_led_length, 0);
+ enable_pwr_breath(PWM_LED2, EC_LED_COLOR_GREEN, breath_led_length, 0);
return;
}
if (chipset_in_state(CHIPSET_STATE_ANY_SUSPEND))
- enable_pwr_breath(PWM_LED2, EC_LED_COLOR_WHITE, breath_led_length, 1);
+ enable_pwr_breath(PWM_LED2, EC_LED_COLOR_GREEN, breath_led_length, 1);
else
- enable_pwr_breath(PWM_LED2, EC_LED_COLOR_WHITE, breath_led_length, 0);
+ enable_pwr_breath(PWM_LED2, EC_LED_COLOR_GREEN, breath_led_length, 0);
if (chipset_in_state(CHIPSET_STATE_ON) | power_button_enable) {
if (charge_prevent_power_on(0))
set_pwr_led_color(PWM_LED2, (power_tick %
LED_TICKS_PER_CYCLE < LED_ON_TICKS) ?
- EC_LED_COLOR_WHITE : -1);
+ EC_LED_COLOR_GREEN : -1);
else
- set_pwr_led_color(PWM_LED2, EC_LED_COLOR_WHITE);
+ set_pwr_led_color(PWM_LED2, EC_LED_COLOR_GREEN);
} else
set_pwr_led_color(PWM_LED2, -1);
}
As you can see, it’s all fairly simple. I just had to change our
EC_LED_COLOR_WHITE
for EC_LED_COLOR_GREEN
. The codebase
defines a few colors, but they’re defined as RGB tuples which
is awesome, because it opens the door to custom RGB effects in
the future!
There’s More Than One LED‽
That’s right! The Framework laptop I own (13″; the 16″ releases soon though!) has 3 more LED lights. One on the left of the chassis, one on right of the chassis, and one on the capslock key. The capslock LED acts as an indicator of whether or not you’ve got capslock enabled. This is useless to me though, because my custom keyboard layout doesn’t even support capslock (see the next section) — so I patched it to be a function-lock indicator instead!
Here’s the diff — but do take care if you want to apply similar patches
to your laptop! The files I’m editing are under board/hx20
since I’m on an 11th Gen Intel CPU. If you have a different
CPU, you will probably need to fuck with different code:
~/board/hx20/board.h
@@ -218,7 +218,6 @@
#define CONFIG_CMD_LEDTEST
#define CONFIG_LED_PWM_COUNT 3
#define CONFIG_LED_PWM_TASK_DISABLED
-#define CONFIG_CAPSLED_SUPPORT
#ifdef CONFIG_ACCEL_KX022
#define CONFIG_LID_ANGLE
~/board/hx20/keyboard-customization.c
@@ -249,6 +249,23 @@ int fn_table_set(int8_t pressed, uint32_t fn_bit)
return false;
}
+static void hx20_update_fnkey_led(void) {
+ /* Turn the capslock light into a fn-lock light */
+ gpio_set_level(GPIO_CAP_LED_L, (Fn_key & FN_LOCKED) ? 1 : 0);
+}
+
+/* Set the fn-lock light to the correct setting when the system resumes */
+void hx20_fnkey_resume(void) {
+ hx20_update_fnkey_led();
+}
+DECLARE_HOOK(HOOK_CHIPSET_RESUME, hx20_fnkey_resume, HOOK_PRIO_DEFAULT);
+
+/* Disable the fn-lock light on suspend */
+void hx20_fnkey_suspend(void) {
+ gpio_set_level(GPIO_CAP_LED_L, 0);
+}
+DECLARE_HOOK(HOOK_CHIPSET_SUSPEND, hx20_fnkey_suspend, HOOK_PRIO_DEFAULT);
+
void fnkey_shutdown(void) {
uint8_t current_kb = 0;
@@ -420,6 +437,7 @@ int functional_hotkey(uint16_t *key_code, int8_t pressed)
Fn_key &= ~FN_LOCKED;
else
Fn_key |= FN_LOCKED;
+ hx20_update_fnkey_led();
}
return EC_ERROR_UNIMPLEMENTED;
}
As you can see, toggling the capslock LED is as simple as
invoking gpio_set_level()
. Not only that, but disabling its
functionality with the capslock key is as easy as undefining the
CONFIG_CAPSLOCK_SUPPORT
macro. Figuring out if the function key
is locked is also really easy. The Fn_key
global variable is a
bit-field containing information pertaining to the function key, and we
also conveniently already have the FN_LOCKED
constant defined
that we can bitwise-AND with Fn_key
to check the locked state!
We also setup some hooks with the DECLARE_HOOK()
macro. These
just ensure that we are behaving properly on system resume and -suspend.
The Hybrid Key
Wouldn’t it be cool if a physical key could represent two keys at the same time? I thought so too. Like all Emacs users, I suffer from a distinct lack of easily-accessible modifier keys. I need escape because I use Vim bindings; I need left-control because all the Emacs bindings use it; I need super for my window-managers’ bindings; I need left-alt so I can type characters that don’t come on a standard American keyboard (such as ß, €, and é), and now I have a problem. All my modifiers are taken, but Emacs still needs a meta key to work!

What will I ever do‽ Well thanks to Framework making the EC
open-source, and conveniently giving me a file called
keyboard_customization.c
, I’m going to take two keys and stick
them in one! The basic premise is this: the capslock key is arguably
the easiest modifier key to hit, and it’s currently bound to the escape
key on my system. This is inefficient though, because nobody makes
key-bindings that chord the escape-key with another key;
chords
It took me a little longer this time to figure out how to implement what
I wanted since the code isn’t as clear, but it was still a surprisingly
easy feature to patch into the EC! I basically just updated
the scancode table, swapping out the capslock scancode for my own random
one that I called SCANCODE_CTRL_ESC
. I then created a new
function called try_ctrl_esc()
which is called in the on-keyup
and -down callback function. The try_ctrl_esc()
function
handles all of the logic as you can see in the following diff; it’s
basically just a state machine:
~/board/hx20/keyboard_customization.c
@@ -22,12 +22,15 @@
#define CPRINTS(format, args...) cprints(CC_KEYBOARD, format, ## args)
#define CPRINTF(format, args...) cprintf(CC_KEYBOARD, format, ## args)
+/* The scancode for the caps-lock key, which is now a hybrid key */
+#define SCANCODE_CTRL_ESC 0x0101
+
uint16_t scancode_set2[KEYBOARD_COLS_MAX][KEYBOARD_ROWS] = {
{0x0021, 0x007B, 0x0079, 0x0072, 0x007A, 0x0071, 0x0069, 0xe04A},
{0xe071, 0xe070, 0x007D, 0xe01f, 0x006c, 0xe06c, 0xe07d, 0x0077},
{0x0015, 0x0070, 0x00ff, 0x000D, 0x000E, 0x0016, 0x0067, 0x001c},
{0xe011, 0x0011, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
- {0xe05a, 0x0029, 0x0024, 0x000c, 0x0058, 0x0026, 0x0004, 0xe07a},
+ {0xe05a, 0x0029, 0x0024, 0x000c, 0x0101, 0x0026, 0x0004, 0xe07a},
{0x0022, 0x001a, 0x0006, 0x0005, 0x001b, 0x001e, 0x001d, 0x0076},
{0x002A, 0x0032, 0x0034, 0x002c, 0x002e, 0x0025, 0x002d, 0x002b},
{0x003a, 0x0031, 0x0033, 0x0035, 0x0036, 0x003d, 0x003c, 0x003b},
@@ -497,6 +500,55 @@ int functional_hotkey(uint16_t *key_code, int8_t pressed)
return EC_SUCCESS;
}
+int try_ctrl_esc(uint16_t *key_code, int8_t pressed) {
+ static enum {
+ NONE,
+ HELD,
+ CTRL
+ } ctrl_esc_state;
+
+ if (*key_code == SCANCODE_CTRL_ESC) {
+ /* If we pressed the caps key, enter the HELD state. Otherwise,
+ * we are either releasing from the HELD state or the CTRL
+ * state. In both cases we should reset the state to NONE, but
+ * when releasing from the HELD state we want to send an ESC and
+ * when releasing from the CTRL state we want to end the CTRL.
+ *
+ * Also important to note is that even before we know if we’re
+ * going to be acting as ESC or CTRL, we need to send a press-
+ * event of the CTRL key because you can chord CTRL with mouse-
+ * clicks too, not just other keys.
+ */
+ if (pressed) {
+ ctrl_esc_state = HELD;
+ simulate_keyboard(SCANCODE_LEFT_CTRL, 1);
+ } else if (ctrl_esc_state == HELD) {
+ ctrl_esc_state = NONE;
+ simulate_keyboard(SCANCODE_LEFT_CTRL, 0);
+ simulate_keyboard(SCANCODE_ESC, 1);
+ simulate_keyboard(SCANCODE_ESC, 0);
+ } else if (ctrl_esc_state == CTRL) {
+ ctrl_esc_state = NONE;
+ simulate_keyboard(SCANCODE_LEFT_CTRL, 0);
+ }
+
+ return EC_ERROR_UNIMPLEMENTED;
+ }
+
+ /* If we get here then we are dealing with a key that isn’t the caps
+ * key. In that case we need to handle all 3 states. If the state is
+ * NONE then we can just exit from this function. If it’s HELD and we
+ * are pressing a key, then that’s a key-chord and we need to start a
+ * CTRL. Finally, if we are in the CTRL state, there is nothing to do.
+ */
+ if (ctrl_esc_state == HELD && pressed) {
+ ctrl_esc_state = CTRL;
+ simulate_keyboard(SCANCODE_LEFT_CTRL, 1);
+ }
+
+ return EC_SUCCESS;
+}
+
enum ec_error_list keyboard_scancode_callback(uint16_t *make_code,
int8_t pressed)
{
@@ -521,6 +573,10 @@ enum ec_error_list keyboard_scancode_callback(uint16_t *make_code,
if (!pos_get_state())
return EC_SUCCESS;
+ r = try_ctrl_esc(make_code, pressed);
+ if (r != EC_SUCCESS)
+ return r;
+
r = hotkey_F1_F12(make_code, Fn_key, pressed);
if (r != EC_SUCCESS)
return r;
One thing that’s good to take note of is what I return from
try_ctrl_esc()
. The general pattern for handling a keyup or
-down event is to stick the following code into
keyboard_scancode_callback()
:
keyboard_scancode_callback()
in
~/board/hx20/keyboard_customization.c
/* ‘make_code’ is the scancode. ‘pressed’ is a boolean that is true if this is a
keydown event, and false if it’s a keyup. */
r = my_handler_function(make_code, pressed);
if (r != EC_SUCCESS)
return r;
In my_handler_function()
(or whatever you decide to name it),
you attempt to handle the event. If you don’t want to handle a
particular event and instead want to pass it on to the next handler, you
need to return EC_SUCCESS
. If you managed to successfully
handle the event though, then you need to return an error such as
EC_ERROR_UNIMPLEMENTED
. It’s pretty stupid and makes very
little sense from a naming perspective, but oh well…
What’s Next?
RGB LEDs maybe.