Framework is Awesome

  • Home
  • Back
  • UNIX was not designed to stop its users from doing stupid things, as that would also stop them from doing clever things.

    Doug Gywn

    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
    diff --git a/board/hx20/led.c b/board/hx20/led.c
    index a4dc4564e..dacf73fda 100644
    --- a/board/hx20/led.c
    +++ b/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
    diff --git a/board/hx20/board.h b/board/hx20/board.h
    index 7b4ea288a..cfc6a61a2 100644
    --- a/board/hx20/board.h
    +++ b/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
    diff --git a/board/hx20/keyboard_customization.c b/board/hx20/keyboard_customization.c
    index 2b91f2e0c..9a5050a0f 100644
    --- a/board/hx20/keyboard_customization.c
    +++ b/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!

    Workflow by Randall Munroe
    XKCD Comic 1172

    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 1 are always done with a modifier like control, and Emacs is no different. So my plan was to make it so that the capslock key when used on its own mimics an escape-key, while instead mimicking the left-control-key when used in a chord with another key.

    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
    diff --git a/board/hx20/keyboard_customization.c b/board/hx20/keyboard_customization.c
    index 9a5050a0f..2756f17ce 100644
    --- a/board/hx20/keyboard_customization.c
    +++ b/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.