Remapping the keyboard in a Chrome OS extension

Remapping the keyboard in a Chrome OS extension

My father recently got a cheap refurbished Chromebook Pixel. It’s a decently powerful machine with a cool OS. The only downside is that his Pixel originally from England and it’s keyboard still has a bit of an accent.

The standard desktop keyboard in the US is the 101-key keyboard first introduced on the IBM model M keyboard. Outside the US there are various other standards which often shrink the left shift and make the enter key taller and thinner.

Standard 101-key US layout keyboard
Standard 102-key UK layout keyboard

While standards are important, and I’m probably biased as an American, I think it’s silly and potentially dangerous to shrink the shift and enter keys. For my typing style both the shift and enter keys are among my most-pressed keys. For such often-pressed keys, it’s desirable to move them to as reachable a position as possible. Naturally my father started considering buying a new laptop shell to replace the original keyboard with a more sane layout. Before turning to hardware I wanted to investigate how customizable Google made Chrome OS.

A few years ago Google published a few custom extensions that add convenient keyboard support to Chrome OS. Even though they’ve included the invaluable capslockremap, there’s no facility to Americanize a UK keyboard. Using those extensions as a guide I created a small extension to convert the two UK-only keys (\ and #) to be (almost) synonyms for their more useful neighbors (shift and enter, respectively). You can download my custom extension here.

After saving the uk2us.zip file to disk, unzip it. The next step is to install it as an unpackage extension to Chrome OS. This action can be accessed in the browser at chrome://extensions. On the extension page, make sure first to check the box Developer mode in the top-right, then use the Load unpacked extension... button to select the directory containing the extension. From that point on, any modifications to the script can be loaded using the Reload button.

Chrome Extension Manager: First enable Developer mode, then load the extension.

Once the extension has been loaded, we can enable the new keyboard from the chrome://settings/languages page. Check the box next to the name of your extension to enable that keyboard. In the manifest.json file included in the extension directory, there is a parameter nested under input_components[0]["language"] that shows which language setting the new extension will be listed under. My manifest is registered with language en-US, and so my new keyboard can be found in the settings under the selection for English (United States).

To use your new keyboard, click its checkbox, and press alt+shift or ctrl+space to select it.

To actually switch to using your new keyboard you’ll need to press either alt+shift or ctrl+space. If you look in the bottom right-hand corner of the screen you’ll see an indicator noting which keyboard you’re using. In my case the original keyboard has is abbreviated with US, and my custom keyboard is shown as EN*. By pressing ctrl+space I was able to toggle between the two whenever I needed to go back to the original keyboard. This feature can be very helpful in the case of a programming bug that disables important keys on the keyboard.

The current keyboard is listed in the status bar in the bottom right of the screen. For me, 'US' means the default keyboard and 'EN*' means using the extension modified keyboard.

So, that’s how to install a custom keyboard extension, but how is the sausage made? The following is the entire code of my shift/enter remapper:

    var context_id = -1;

    chrome.input.ime.onFocus.addListener(function(context) {
      context_id = context.contextID;
    });

    var shifted = false;

    chrome.input.ime.onKeyEvent.addListener(
      function(engineID, keyData) {
        var handled = false;

        if (keyData.type == "keydown") {
          if (keyData.code == "Backslash") {
            keyData.code = "Enter";
            chrome.input.ime.sendKeyEvents({"contextID": context_id, "keyData": [keyData]});
            handled = true;
          } else if (keyData.code == "IntlBackslash") {
            shifted = true;
            handled = true;
          } else  if (shifted) {
            chrome.input.ime.commitText({"contextID": context_id, "text": keyData.key.toUpperCase()});
            handled = true;
          }
        } else if (keyData.type == "keyup") {
          if (keyData.code == "IntlBackslash") {
            shifted = false;
            handled = true;
          }
        }

        return handled;
    });

But what if you want to write your own custom extension?

First, identify which keys you’ll want to remap. A good place to start is with the Keyboard Event Code Value Tables. These tables from the W3C list the standard names by which each key is called on a variety of different keyboard layouts. As an example, you can see in my code above that every time the extension receives a Backslash (which doesn’t actually have a picture of a backslash on the button on a UK keyboard) keydown signal, it replaces that key with a Enter code, and re-sends the KeyEvent.

Note that an enter keypress is a non-printing character and can be interpreted differently in a lot of different contexts, for this type of event, rather than taking a specific action we can use chrome.input.ime.sendKeyEvents to propagate the event back through the key processing pipeline to be handled as if the enter key were actually pressed. Alternatively in the section that handles keyDowns when the shifted state is active, I choose to emit actual letters. chrome.input.ime.commitText will perform a specific action, namely emitting specific text, rather than propagating a given keyboard event.

I couldn’t find Google documentation for which parameters of the keyData object were used for what, but my assumption based on the guidelines in the W3C Events Specification is that keyData.code is the only attribute that matters for event sending, while keydata.key will contain the literal text to be emitted in a commitText call.

My extension above is simple, and certainly has flaws (the shift key only works on A-Z, no numbers, no symbols), but it was quick to write, and helps tremendously. But, hopefully this information can help someone else make a better, more comprehensive keyboard extension in the future.

Again, the best examples of good keyboard extensions are from Google themselves at: https://github.com/google/extra-keyboards-for-chrome-os

The zip containing my extension can be found here: uk2us.zip.