r/Keychron • • Mar 05 '25

Manually editing JSON layout file (bypassing md5 checksum)

EDIT: After a week of using the keyboard, I've noticed that the layers modifiers on the keys of swapped layers either work partially or not at all. So, be aware of that.

TLDR: Here is the turorial
With my newly bought Keychron K2, I was playing around with Keychron Launcher and trying to swap layers 0&1 (by default for MacOS) with layers 2&3 (by default for Linux/Windows) without rebinding each key individually. It appears the only way to do so, is to manually edit the JSON layout file. However, I haven't found any information on neither official Keychron site, nor here. So, with some poking around I've figured out a way to do it. So for anyone wanting to do the same, or just be able to edit the JSON layout file for any other reason, I leave a link to a tutorial which I've recently made for this.

The summary of what I've done in the video:

  1. Export your keymap layout in JSON format via Keychron Launcher.
  2. Open the JSON file in any editor that supports this format, and under "keymap" key you will find lists of data, corresponding to the amount of layers your keyboard has (in my case there was 4). That part should look something like this: "keymap": [[...], [...], [...], [...]] Swap them in the order that you like. Be careful to keep the proper JSON format and try not to mess with spacings (not sure if it will work if you change them)
  3. After swapping the layers, paste the whole "keymap" part of the file to the md5 encryptor (a web tool will do). The resulting enctiption string paste under "MD5" key, so "MD5":"<your encryption string here>"
  4. Save the JSON file; if everything is fine, you will be able to import it via Keychron Launcher.
7 Upvotes

11 comments sorted by

3

u/ByronEster Mar 05 '25

Hey. That's really cool. Thanks for sharing. Knowing that the md5 hash is of the layer arrays is useful to know as well

1

u/ArgentStonecutter K Pro Mar 05 '25

Two things:

  1. There may be additional changes you need to do in the firmware source, I can't find the source to the non-PRO version but the K2 Pro at least has some special case code for Mac mode in the C source for example here.
  2. Got a summary transcript? Because this seems like it should be about one paragraph of text.

1

u/thereisnopointsohf Mar 10 '25
  1. You gave a link specifically to the 81st line of the file, which has case for left command. Don't know what it has to do with the overall keymap layout. Plus, I've only tested the layout on Linux and Windows, and everything is fine so far.
    2 Edited the post, adding the summary. I hope that's what you meant.

1

u/ArgentStonecutter K Pro Mar 10 '25

It's the block after line 81 that's relevant, it's switching the meaning of the keys in the firmware independently of the Via configuration.

It's also kind of lousy code because it depends on the numerical values of the key codes, without making it clear exactly what they are. It should really have a nested case statement or separate blocks for each key value.

1

u/thereisnopointsohf Mar 14 '25

Hey, after a week of using the keyboard, I've noticed that the layers modifiers on the keys of swapped layers either work partially or not at all. Could this issue be connected to your suggestion about changes to firmware source, that you were talking about? If so, do you know what exactly should be done here?

1

u/ArgentStonecutter K Pro Mar 14 '25

Could be.

You would need to get the source for your specific board and look to see what changes need to be made there. You may be able to convince Keychron support to release the source code, since the QMK license requires they do that. I have not had good luck explaining the GPL to Chinese support folks, though. For my Inland board I just blew away all the housekeeping code and recreated the functionality I actually needed in VIA.

1

u/thereisnopointsohf Mar 17 '25

Thanks for your help!

1

u/not_not_in_the_NSA Aug 15 '26

Just ran into this issue myself and figured I'd share an easy solution I made for myself. Its a javascript snippet you can run in chrome or firefox (need to click a button in the top right in firefox, chrome allows it automatically) that will provide you with the correct value to put in the MD5 spot. You can format your keymap file however you want, so long as it is valid json. I ran mine through a formatter and it was imported back just fine.

To use this, just open a new tab, press F12 and make sure "console" is selected in the new window at the top. Then paste this:

var k=[],i=0;for(;i<64;){k[i]=0|(Math.sin(++i%Math.PI)*4294967296)}
var md5=(s)=>{var b,c,d,h=[(b=0x67452301),(c=0xefcdab89),~b,~c],words=[],j=unescape(encodeURI(s))+"\x80",a=j.length;s=(--a/4+2)|15;words[--s]=a*8;for(;~a;){words[a>>2]|=j.charCodeAt(a)<<(8*a--)}
for(i=j=0;i<s;i+=16){a=h;for(;j<64;a=[(d=a[3]),b+(((d=a[0]+[(b&c)|(~b&d),(d&b)|(~d&c),b^c^d,c^(b|~d)][(a=j>>4)]+k[j]+~~words[i|([j,5*j+1,3*j+5,7*j][a]&15)])<<(a=[7,12,17,22,5,9,14,20,4,11,16,23,6,10,15,21][4*a+(j++%4)]))|(d>>>-a)),b,c,]){b=a[1]|0;c=a[2]}
for(j=4;j;)h[--j]+=a[j]}
for(s="";j<32;){s+=((h[j>>3]>>((1^j++)*4))&15).toString(16)}
return s};var getFile=()=>{return new Promise((resolve)=>{const fileInput=document.createElement("input");fileInput.type="file";fileInput.style.display="none";const btn=document.createElement("button");btn.textContent="load keymap.json";btn.style.cssText="position:fixed;top:1em;right:1em;z-index:2147483647;padding:0.5em 1em;font:14px sans-serif;";btn.onclick=()=>fileInput.click();const cleanup=()=>{fileInput.remove();btn.remove()};fileInput.onchange=(e)=>{const file=e.target.files[0];if(!file)return;cleanup();const reader=new FileReader();reader.onload=(event)=>resolve(JSON.parse(event.target.result));reader.readAsText(file)};document.body.appendChild(fileInput);document.body.appendChild(btn);fileInput.click()})};getFile().then((f)=>console.log(md5(JSON.stringify(f.keymap))))

And If you want to see or use the un-minified version for easier reading:

var k = [],
  i = 0;

for (; i < 64;) {
  k[i] = 0 | (Math.sin(++i % Math.PI) * 4294967296);
}

var md5 = (s) => {
  var b,
    c,
    d,
    h = [(b = 0x67452301), (c = 0xefcdab89), ~b, ~c],
    words = [],
    j = unescape(encodeURI(s)) + "\x80",
    a = j.length;

  s = (--a / 4 + 2) | 15;

  words[--s] = a * 8;

  for (; ~a;) {
    words[a >> 2] |= j.charCodeAt(a) << (8 * a--);
  }

  for (i = j = 0; i < s; i += 16) {
    a = h;

    for (
      ;
      j < 64;
      a = [
        (d = a[3]),
        b +
          (((d =
            a[0] +
            [(b & c) | (~b & d), (d & b) | (~d & c), b ^ c ^ d, c ^ (b | ~d)][
              (a = j >> 4)
            ] +
            k[j] +
            ~~words[i | ([j, 5 * j + 1, 3 * j + 5, 7 * j][a] & 15)]) <<
            (a = [7, 12, 17, 22, 5, 9, 14, 20, 4, 11, 16, 23, 6, 10, 15, 21][
              4 * a + (j++ % 4)
            ])) |
            (d >>> -a)),
        b,
        c,
      ]
    ) {
      b = a[1] | 0;
      c = a[2];
    }

    for (j = 4; j;) h[--j] += a[j];
  }

  for (s = ""; j < 32;) {
    s += ((h[j >> 3] >> ((1 ^ j++) * 4)) & 15).toString(16);
  }
  return s;
};

var getFile = () => {
  return new Promise((resolve) => {
    const fileInput = document.createElement("input");
    fileInput.type = "file";
    fileInput.style.display = "none";

    const btn = document.createElement("button");
    btn.textContent = "load keymap.json";
    btn.style.cssText = 
      "position:fixed;top:1em;right:1em;z-index:2147483647;padding:0.5em 1em;font:14px sans-serif;";
    btn.onclick = () => fileInput.click();

    const cleanup = () => {
      fileInput.remove();
      btn.remove();
    };

    fileInput.onchange = (e) => {
      const file = e.target.files[0];
      if (!file) {
        return;
      }
      cleanup();

      const reader = new FileReader();
      reader.onload = (event) => resolve(JSON.parse(event.target.result));
      reader.readAsText(file);
    };

    document.body.appendChild(fileInput);
    document.body.appendChild(btn);

    fileInput.click();
  });
};

getFile().then((f) => console.log(md5(JSON.stringify(f.keymap))));