Keyboard firmwares for Atmel AVR and Cortex-M
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

keymap_editor.js 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. /*
  2. * TMK keymap editor
  3. */
  4. // key id under editing
  5. var editing_key;
  6. // layer under editing
  7. var editing_layer = 0;
  8. // load keymap on keyboard key buttons
  9. var load_keymap_on_keyobard = function(layer, keymap) {
  10. for (var row in keymap) {
  11. for (var col in keymap[row]) {
  12. var code = keymap[row][col];
  13. var key = keycodes[code];
  14. // row and column takes range of 0-32(0-9a-v)
  15. $("#key-" + parseInt(row).toString(32) + parseInt(col).toString(32)).text(key.name);
  16. $("#key-" + parseInt(row).toString(32) + parseInt(col).toString(32)).attr({ title: key.desc });
  17. }
  18. }
  19. };
  20. $(function() {
  21. // Title
  22. document.title = "TMK Keymap Editor for " + KEYBOARD_ID;
  23. $("#page-title").text("TMK Keymap Editor for " + KEYBOARD_ID);
  24. /*
  25. * load keymap from URL hash
  26. */
  27. var decoded = decode_keymap(document.location.hash.substring(1));
  28. if (decoded != null) {
  29. keymaps = decoded['keymaps'];
  30. }
  31. /*
  32. * Layer selector
  33. */
  34. $("#layer_radio").buttonset();
  35. // layer change
  36. $(".layer").click(function(ev, ui) {
  37. var layer = parseInt($(this).attr('id').match(/layer-(\d+)/)[1]);
  38. editing_layer = layer;
  39. load_keymap_on_keyobard(layer, keymaps[layer]);
  40. });
  41. /*
  42. * Keyboard(key buttons)
  43. */
  44. // load default keymap on startup
  45. load_keymap_on_keyobard(0, keymaps[0]);
  46. // Select key button to edit
  47. $(".key").click(function(ev, ui) {
  48. editing_key = $(this).attr('id');
  49. // grey-out key to indicate being under editing
  50. $(".key").removeClass("key-editing");
  51. $(this).addClass("key-editing");
  52. }).focus(function(ev, ui) {
  53. // select editing_key with tab key focus
  54. $(this).click();
  55. });
  56. /*
  57. * Keycodes button tab
  58. */
  59. $("#keycode_tabs").tabs({
  60. heightStyle: "auto",
  61. });
  62. // Keycodes: read name and description from code table
  63. $(".action").each(function(index) {
  64. // get code from code button id: code-[0x]CCCC where CCCC is dec or hex number
  65. var code = parseInt($(this).attr('id').match(/code-((0x){0,1}[0-9a-fA-F]+)/)[1]);
  66. $(this).text(keycodes[code].name);
  67. $(this).attr({ title: keycodes[code].desc });
  68. });
  69. $(".action").click(function(ev,ui) {
  70. if (!editing_key) return;
  71. // get matrix position from key id: key-RC where R is row and C is column in "0-v"(radix 32)
  72. var pos = editing_key.match(/key-([0-9a-v])([0-9a-v])/i);
  73. if (!pos) return;
  74. var row = parseInt(pos[1], 32), col = parseInt(pos[2], 32);
  75. // set text and tooltip to key button under editing
  76. $("#" + editing_key).text($(this).text());
  77. $("#" + editing_key).attr({ title: $(this).attr('title'), });
  78. // change keymap array
  79. // get code from keycode button id: code-[0x]CC where CC is dec or hex number
  80. var code = $(this).attr('id').match(/code-((0x){0,1}[0-9a-fA-F]+)/)[1];
  81. keymaps[editing_layer][row][col] = parseInt(code);
  82. // give focus on editing_key for next tab key operation
  83. $("#" + editing_key).focus();
  84. });
  85. /*
  86. * Share URL
  87. */
  88. // Share URL
  89. $("#keymap-share").click(function(ev, ui) {
  90. var hash = encode_keymap({ keymaps: keymaps });
  91. $("#share-url").text(document.location.origin + document.location.pathname + "#" + hash);
  92. });
  93. // Shorten URL
  94. $("#shorten-url").click(function(ev, ui) {
  95. var hash = encode_keymap({ keymaps: keymaps });
  96. var editor_url = document.location.origin + document.location.pathname;
  97. window.open("https://bitly.com/shorten/?url=" + encodeURIComponent(editor_url + "#" + hash));
  98. //window.open("http://tinyurl.com/create.php?url=" + encodeURIComponent(editor_url + "#" + hash));
  99. });
  100. // Hex Save
  101. $("#keymap-download").click(function(ev, ui) {
  102. var keymap_data = fn_actions.concat(keymaps);
  103. var content = firmware_hex() +
  104. hex_output(KEYMAP_START_ADDRESS, keymap_data) +
  105. hex_eof();
  106. // download hex file
  107. var blob = new Blob([content], {type: "application/octet-stream"});
  108. var hex_link = $("#hex-download");
  109. hex_link.attr('href', window.URL.createObjectURL(blob));
  110. hex_link.attr('download', KEYBOARD_ID + "_firmware.hex");
  111. // jQuery click() doesn't work straight for 'a' element
  112. // http://stackoverflow.com/questions/1694595/
  113. hex_link[0].click();
  114. });
  115. /*
  116. * Output options
  117. */
  118. //$("#keymap-output").resizable(); // resizable textarea
  119. // Hex output
  120. $("#keymap-hex-generate").click(function(ev, ui) {
  121. var keymap_data = fn_actions.concat(keymaps);
  122. $("#keymap-output").text(hex_output(KEYMAP_START_ADDRESS, keymap_data));
  123. });
  124. // C source output
  125. $("#keymap-source-generate").click(function(ev, ui) {
  126. $("#keymap-output").text(source_output(keymaps));
  127. });
  128. // JSON output
  129. //$("#keymap-json-generate").css('display', 'none'); // hide
  130. $("#keymap-json-generate").click(function(ev, ui) {
  131. var keymap_output;
  132. //keymap_output = JSON.stringify(keymaps, null, 4);
  133. keymap_output = JSON.stringify({ keymaps: keymaps });
  134. $("#keymap-output").text(keymap_output);
  135. });
  136. // encode keymap
  137. $("#keymap-encode").click(function(ev, ui) {
  138. var keymap_output = encode_keymap({ keymaps: keymaps });
  139. $("#keymap-output").text(keymap_output);
  140. });
  141. // decode keymap
  142. $("#keymap-decode").click(function(ev, ui) {
  143. var hash = $("#keymap-output").text();
  144. var keymap_output = decode_keymap(hash);
  145. $("#keymap-output").text(JSON.stringify(keymap_output));
  146. });
  147. // lost keymap under edting when leave the page
  148. /* TODO: Needed when released
  149. $(window).bind('beforeunload', function(){
  150. return 'CAUTION: You will lost your change.';
  151. });
  152. */
  153. });
  154. /*
  155. * Share URL
  156. */
  157. function encode_keymap(obj)
  158. {
  159. if (typeof LZString != "undefined" && typeof Base64 != "undefined") {
  160. return Base64.encode(LZString.compress(JSON.stringify(obj)));
  161. }
  162. return window.btoa(JSON.stringify(obj));
  163. }
  164. function decode_keymap(str)
  165. {
  166. try {
  167. /* lz-string-1.3.3.js: LZString.decompress() runs away if given short string. */
  168. if (str == null || typeof str != "string" || str.length < 30) return null;
  169. if (typeof LZString != "undefined" && typeof Base64 != "undefined") {
  170. return JSON.parse(LZString.decompress(Base64.decode(str)));
  171. }
  172. return JSON.parse(window.atob(str));
  173. } catch (err) {
  174. return null;
  175. }
  176. }
  177. /*
  178. * Hex file
  179. */
  180. function hexstr2(b)
  181. {
  182. return ('0'+ b.toString(16)).substr(-2).toUpperCase();
  183. }
  184. function hex_line(address, record_type, data)
  185. {
  186. var sum = 0;
  187. sum += data.length;
  188. sum += (address >> 8);
  189. sum += (address & 0xff);
  190. sum += record_type;
  191. var line = '';
  192. line += ':';
  193. line += hexstr2(data.length);
  194. line += hexstr2(address >> 8);
  195. line += hexstr2(address & 0xff);
  196. line += hexstr2(record_type);
  197. for (var i = 0; i < data.length; i++) {
  198. sum = (sum + data[i]);
  199. line += hexstr2(data[i]);
  200. }
  201. line += hexstr2((~sum + 1)&0xff); // Checksum
  202. line +="\r\n";
  203. return line;
  204. }
  205. function hex_eof()
  206. {
  207. return ":00000001FF\r\n";
  208. }
  209. function hex_output(address, data) {
  210. var output = '';
  211. var line = [];
  212. // TODO: refine: flatten data into one dimension array
  213. [].concat.apply([], [].concat.apply([], data)).forEach(function(e) {
  214. line.push(e);
  215. if (line.length == 16) {
  216. output += hex_line(address, 0x00, line);
  217. address += 16;
  218. line.length = 0; // clear array
  219. }
  220. });
  221. if (line.length > 0) {
  222. output += hex_line(address, 0x00, line);
  223. }
  224. return output;
  225. }
  226. /*
  227. * Source file
  228. */
  229. function source_output(keymaps) {
  230. var output = '';
  231. // fn actions
  232. output += "/*\n";
  233. output += " * Keymap for " + KEYBOARD_ID + "\n";;
  234. output += " * generated by tmk keymap editor\n";
  235. output += " */\n";
  236. output += "#include <stdint.h>\n";
  237. output += "#include <stdbool.h>\n";
  238. output += "#include <avr/pgmspace.h>\n";
  239. output += "#include \"keycode.h\"\n";
  240. output += "#include \"action.h\"\n";
  241. output += "#include \"action_macro.h\"\n";
  242. output += "#include \"keymap.h\"\n\n";
  243. output += "#ifdef KEYMAP_SECTION_ENABLE\n";
  244. output += "const uint16_t fn_actions[] __attribute__ ((section (\".keymap.fn_actions\"))) = {\n";
  245. output += "#else\n";
  246. output += "static const uint16_t fn_actions[] PROGMEM = {\n";
  247. output += "#endif\n";
  248. output += fn_actions_source;
  249. output += "};\n\n";
  250. // keymaps
  251. output += "#ifdef KEYMAP_SECTION_ENABLE\n";
  252. output += "const uint8_t keymaps[][";
  253. output += keymaps[0].length; // row
  254. output += "][";
  255. output += keymaps[0][0].length; // col
  256. output += "] __attribute__ ((section (\".keymap.keymaps\"))) = {\n";
  257. output += "#else\n";
  258. output += "static const uint8_t keymaps[][";
  259. output += keymaps[0].length; // row
  260. output += "][";
  261. output += keymaps[0][0].length; // col
  262. output += "] PROGMEM = {\n";
  263. output += "#endif\n";
  264. for (var i in keymaps) {
  265. output += " {\n";
  266. for (var j in keymaps[i]) {
  267. output += " { ";
  268. for (var k in keymaps[i][j]) {
  269. output += '0x' + ('0' + keymaps[i][j][k].toString(16)).substr(-2);
  270. output += ',';
  271. }
  272. output += " },\n";
  273. }
  274. output += " },\n";
  275. }
  276. output += "};\n";
  277. output += "\n";
  278. output += "/* translates key to keycode */\n";
  279. output += "uint8_t keymap_key_to_keycode(uint8_t layer, key_t key)\n";
  280. output += "{\n";
  281. output += " return pgm_read_byte(&keymaps[(layer)][(key.row)][(key.col)]);\n";
  282. output += "}\n";
  283. output += "\n";
  284. output += "/* translates Fn index to action */\n";
  285. output += "action_t keymap_fn_to_action(uint8_t keycode)\n";
  286. output += "{\n";
  287. output += " action_t action;\n";
  288. output += " action.code = pgm_read_word(&fn_actions[FN_INDEX(keycode)]);\n";
  289. output += " return action;\n";
  290. output += "}\n";
  291. return output;
  292. };