mirror of
https://github.com/qmk/qmk_userspace.git
synced 2026-09-07 04:32:08 -04:00
Add layout doc tooling, linger brackets, and GPL-3.0 licensing
Generate the layout documentation from the C sources rather than by hand. tools/kle_export.py runs the C preprocessor over ramo_layers.h, so it reads the same keymap the firmware builds; kle_render.py draws each layer in KLE's own keycap markup, adding the legends KLE's SVG export omits; kle_docs.py writes this README and the keyboard-layout repo from one parse. Add ramo_linger.c: hold-to-autopair brackets and the Caps Word space rule. License users/raphaelmor as GPL-3.0-or-later. The design follows moutis' Hands Down Promethium, whose QMK implementation is GPL-3.0; QMK is GPL-2.0-or-later, so the combination is GPL-3.0. tools/ stays MIT, being original and shared with the MIT layout repo.
This commit is contained in:
parent
e484d6ad0e
commit
44804ef5d6
34 changed files with 5930 additions and 122 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -14,3 +14,7 @@
|
|||
compile_commands.json
|
||||
.clangd/
|
||||
.cache/
|
||||
|
||||
# Python caches
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
|
|
|||
208
users/raphaelmor/HDPM-layers.html
Normal file
208
users/raphaelmor/HDPM-layers.html
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Hands Down Promethium — full layer map</title>
|
||||
<style>
|
||||
:root{
|
||||
--bg:#0f1117; --card:#171a22; --key:#222634; --key2:#1b1f2b;
|
||||
--ink:#e7e9ee; --dim:#8b90a0; --line:#2c3142;
|
||||
--alpha:#e7e9ee; --mod:#f6c177; --layer:#9ccfd8; --sem:#c4a7e7;
|
||||
--sym:#7fd6a6; --media:#eb6f92; --rgb:#f28fad; --num:#a3be8c;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
body{margin:0;background:var(--bg);color:var(--ink);
|
||||
font:14px/1.4 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;padding:28px}
|
||||
h1{font-size:22px;margin:0 0 4px}
|
||||
.sub{color:var(--dim);margin:0 0 20px;max-width:900px}
|
||||
.legend{display:flex;flex-wrap:wrap;gap:14px;margin:0 0 26px;font-size:12px}
|
||||
.legend span{display:inline-flex;align-items:center;gap:6px;color:var(--dim)}
|
||||
.legend i{width:12px;height:12px;border-radius:3px;display:inline-block}
|
||||
.layer{background:var(--card);border:1px solid var(--line);border-radius:14px;
|
||||
padding:18px 20px 22px;margin:0 0 22px}
|
||||
.layer h2{margin:0 0 2px;font-size:17px;display:flex;align-items:baseline;gap:10px}
|
||||
.layer h2 b{font-size:12px;font-weight:600;color:#0f1117;background:var(--layer);
|
||||
padding:2px 8px;border-radius:20px}
|
||||
.layer .how{color:var(--dim);font-size:12px;margin:0 0 16px}
|
||||
.board{display:flex;gap:44px;flex-wrap:wrap;justify-content:center}
|
||||
.half{display:grid;grid-template-columns:repeat(6,minmax(0,58px));gap:6px}
|
||||
.row{display:contents}
|
||||
.key{background:var(--key);border:1px solid var(--line);border-radius:8px;
|
||||
height:52px;padding:4px 5px;position:relative;display:flex;flex-direction:column;
|
||||
justify-content:center;align-items:center;text-align:center;overflow:hidden}
|
||||
.key .m{font-size:13px;font-weight:600;line-height:1.15;word-break:break-word}
|
||||
.key .h{position:absolute;top:2px;right:4px;font-size:9px;color:var(--dim);font-weight:700}
|
||||
.key.trns{background:transparent;border-style:dashed;color:var(--dim)}
|
||||
.key.trns .m{color:var(--dim);font-weight:400;font-size:15px}
|
||||
.key.none{background:transparent;border-color:#20222c}
|
||||
.key.none .m{color:#3a3f52}
|
||||
.key.mod .m{color:var(--mod)} .key.layer .m{color:var(--layer)}
|
||||
.key.sem .m{color:var(--sem);font-size:11px} .key.sym .m{color:var(--sym)}
|
||||
.key.media .m{color:var(--media);font-size:11px} .key.rgb .m{color:var(--rgb);font-size:10px}
|
||||
.key.num .m{color:var(--num)}
|
||||
.thumbs{grid-column:1/7;display:grid;grid-template-columns:repeat(6,minmax(0,58px));gap:6px;margin-top:4px}
|
||||
.thumbs .key{height:46px}
|
||||
.foot{color:var(--dim);font-size:12px;margin-top:8px;max-width:900px}
|
||||
code{background:#20222c;padding:1px 5px;border-radius:4px}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hands Down · Promethium — full layer map</h1>
|
||||
<p class="sub">Canonical moutis HD system (<code>github.com/moutis/HandsDown</code>), rendered for a 3×6+3 Corne.
|
||||
Seven layers total; the six functional ones are shown (QWERTY is just a plain-QWERTY compatibility fallback).
|
||||
Small top-right badge = <b>hold</b> behavior (mod or layer). Layers are reached by <b>holding</b> a thumb/home key on the alpha layer.</p>
|
||||
|
||||
<div class="legend">
|
||||
<span><i style="background:var(--alpha)"></i>alpha / letter</span>
|
||||
<span><i style="background:var(--mod)"></i>mod (hold)</span>
|
||||
<span><i style="background:var(--layer)"></i>layer-tap / switch</span>
|
||||
<span><i style="background:var(--sem)"></i>semantic key (SK_*)</span>
|
||||
<span><i style="background:var(--sym)"></i>symbol</span>
|
||||
<span><i style="background:var(--num)"></i>number / nav</span>
|
||||
<span><i style="background:var(--media)"></i>media</span>
|
||||
<span><i style="background:var(--rgb)"></i>RGB / config</span>
|
||||
<span><i style="border:1px dashed var(--dim)"></i>▽ transparent</span>
|
||||
</div>
|
||||
|
||||
<div id="app"></div>
|
||||
|
||||
<p class="foot">How to reach each layer from the alpha layer (all momentary, via layer-tap holds):
|
||||
<b>SYM</b> = hold Space · <b>NAV</b> = hold R (L-thumb) or Enter (R-thumb) · <b>FUN</b> = hold Bksp (L-thumb) or F · <b>NUM</b> = hold K · <b>CFG</b> = hold Mute/Play (outer thumbs), or the CFG combo (both inner-thumb pairs).
|
||||
Home-row mods on the alpha & FUN/NUM layers: pinky=⌃ ring=⌥ middle=⌘ index=⇧ (both hands).</p>
|
||||
|
||||
<script>
|
||||
// ---- raw keycodes per layer, in visual order ----
|
||||
// each half: [ row of 6 (outer→inner) ]; thumbs: 3 inner keys
|
||||
const L = {
|
||||
HD:{purpose:"Base alpha — Hands Down Promethium", how:"letters + home-row mods + thumb layer-taps",
|
||||
top:[["KC_LNG1","KC_V","KC_W","KC_G","KC_M","KC_J"],["KC_HASH","KC_DOT","KC_SLSH","KC_DQUO","KC_QUOT","KC_LNG2"]],
|
||||
home:[["KC_ESC","RCTL_T(KC_S)","RALT_T(KC_N)","RGUI_T(KC_T)","RSFT_T(KC_H)","LT(L_NUM,KC_K)"],["KC_COMM","RSFT_T(KC_A)","RGUI_T(KC_E)","RALT_T(KC_I)","RCTL_T(KC_C)","KC_Z"]],
|
||||
bot:[["KC_TAB","LT(L_FUN,KC_F)","KC_P","KC_D","KC_L","KC_X"],["KC_MINS","KC_U","KC_O","KC_Y","KC_B","KC_Q"]],
|
||||
thumbs:[["LT(L_FUN,KC_BSPC)","LT(L_NAV,KC_R)","LT(L_CFG,KC_MUTE)"],["LT(L_CFG,KC_MPLY)","LT(L_SYM,KC_SPC)","LT(L_NAV,KC_ENT)"]]},
|
||||
|
||||
SYM:{purpose:"Symbols & punctuation",how:"hold Space",
|
||||
top:[["KC_TRNS","KC_Z","KC_Q","SK_PARA","SK_SCRS","SK_DCRS"],["SK_CENT","KC_DLR","SK_EURO","SK_BPND","SK_JPY","KC_TRNS"]],
|
||||
home:[["KC_TRNS","SK_SECT","KC_LBRC","KC_LPRN","KC_LCBR","SK_BBLT"],["KC_PSLS","RSFT_T(KC_PEQL)","RGUI_T(KC_PAST)","RALT_T(KC_PPLS)","RCTL_T(KC_PMNS)","KC_TRNS"]],
|
||||
bot:[["KC_TRNS","SK_DEGR","KC_RBRC","KC_RPRN","KC_RCBR","SK_SBLT"],["KC_CIRC","KC_PIPE","KC_AMPR","KC_LT","KC_GT","KC_TRNS"]],
|
||||
thumbs:[["SK_DELWDL","SK_DELWDR","KC_VOLD"],["KC_VOLU","KC_UNDS","SK_IEXC"]]},
|
||||
|
||||
FUN:{purpose:"Function keys (left) · number row (right)",how:"hold Bksp or F",
|
||||
top:[["RGB_TOG","KC_F13","KC_F7","KC_F8","KC_F9","KC_F10"],["KC_SLSH","KC_7","KC_8","KC_9","KC_MINS","RGB_MOD"]],
|
||||
home:[["RGB_VAI","LCTL_T(KC_F14)","LALT_T(KC_F1)","LGUI_T(KC_F2)","LSFT_T(KC_F3)","KC_F11"],["KC_ASTR","RSFT_T(KC_1)","RGUI_T(KC_2)","RALT_T(KC_3)","RCTL_T(KC_PPLS)","RGB_HUI"]],
|
||||
bot:[["RGB_VAD","KC_F15","KC_F4","KC_F5","KC_F3","KC_F12"],["KC_COMM","KC_4","KC_5","KC_6","KC_EQL","RGB_HUD"]],
|
||||
thumbs:[["KC_SCLN","TG(L_NUM)","KC_INS"],["KC_TRNS","KC_0","KC_DOT"]]},
|
||||
|
||||
NUM:{purpose:"Navigation (left) · numpad (right)",how:"hold K → right-hand numpad only (left index is busy holding K). For two-handed L-nav + R-numpad, TOGGLE it on: R+Space combo, or TG(NUM) from FUN.",
|
||||
top:[["KC_TRNS","SK_DOCBEG","KC_HOME","KC_UP","KC_PGUP","SK_PARAPRV"],["KC_PSLS","KC_P7","KC_P8","KC_P9","KC_PMNS","KC_TRNS"]],
|
||||
home:[["KC_TRNS","SK_WORDPRV","KC_LEFT","KC_DOWN","KC_RGHT","SK_WORDNXT"],["KC_PAST","RSFT_T(KC_P4)","RGUI_T(KC_P5)","RALT_T(KC_P6)","RCTL_T(KC_PPLS)","KC_TRNS"]],
|
||||
bot:[["KC_TRNS","SK_DOCEND","KC_END","KC_SPC","KC_PGDN","SK_PARANXT"],["KC_PCMM","KC_P1","KC_P2","KC_P3","KC_PEQL","KC_TRNS"]],
|
||||
thumbs:[["LGUI_T(KC_BSPC)","LSFT_T(KC_ENT)","SK_CLOZ"],["KC_C","KC_P0","KC_PDOT"]]},
|
||||
|
||||
NAV:{purpose:"Edit & mods (left) · arrows/nav (right)",how:"hold R (left thumb) or Enter (right thumb) — the held thumb frees the OTHER hand: navigate arrows with the right, hold ⌃⌥⌘⇧ + edit keys with the left.",
|
||||
top:[["KC_TRNS","KC_TAB","SK_CLOZ","SK_SCAP","SK_SCLP","SK_PSTM"],["SK_DOCBEG","KC_HOME","KC_UP","KC_PGUP","SK_PARAPRV","KC_TRNS"]],
|
||||
home:[["KC_VOLU","KC_LCTL","KC_LALT","KC_LGUI","KC_LSFT","SK_FIND"],["SK_WORDPRV","KC_LEFT","KC_DOWN","KC_RGHT","SK_WORDNXT","KC_TRNS"]],
|
||||
bot:[["KC_VOLD","SK_UNDO","SK_CUT","SK_COPY","SK_PSTE","SK_FAGN"],["SK_DOCEND","KC_END","KC_DOWN","KC_PGDN","SK_PARANXT","KC_TRNS"]],
|
||||
thumbs:[["KC_BSPC","KC_ENTER","SK_ZOOMRST"],["SK_ZOOMRST","SK_HISTPRV","SK_HISTNXT"]]},
|
||||
|
||||
CFG:{purpose:"Keyboard config (left) · media & consumer (right)",how:"hold Mute/Play, or CFG combo",
|
||||
top:[["KC_NO","SK_Lux","SK_Win","SK_Mac","QK_RBT","SK_KILL"],["SK_ZOOMRST","KC_NUM","KC_EJCT","KC_LALT","LCA(KC_DEL)","RGB_MOD"]],
|
||||
home:[["RGB_VAI","HD_AdaptKeyToggle","HD_L_QWERTY","HD_L_ALPHA","KC_VOLU","KC_BRIU"],["SK_ZOOMOUT","KC_MPRV","KC_MUTE","KC_MNXT","EE_CLR","RGB_SAI"]],
|
||||
bot:[["RGB_HUI","QK_BOOT","AG_SWAP","AG_NORM","KC_VOLD","KC_BRID"],["SK_ZOOMIN","KC_MRWD","KC_MPLY","KC_MFFD","QK_BOOT","RGB_HUD"]],
|
||||
thumbs:[["SK_ZOOMOUT","SK_ZOOMIN","SK_ZOOMRST"],["SK_ZOOMRST","SK_ZOOMIN","SK_ZOOMOUT"]]},
|
||||
};
|
||||
|
||||
// ---- keycode → readable label ----
|
||||
const SK={PARA:"¶",SCRS:"†",DCRS:"‡",CENT:"¢",EURO:"€",BPND:"£",JPY:"¥",SECT:"§",IEXC:"¡",BBLT:"•",SBLT:"·",DEGR:"°",IQUE:"¿",BLLT:"•",ELPS:"…",MDSH:"—",
|
||||
DELWDL:"Del Wd◀",DELWDR:"Del Wd▶",WORDPRV:"Word◀",WORDNXT:"Word▶",DOCBEG:"Doc Top",DOCEND:"Doc End",
|
||||
LINEBEG:"Ln Beg",LINEEND:"Ln End",PARAPRV:"¶ ◀",PARANXT:"¶ ▶",
|
||||
UNDO:"Undo",REDO:"Redo",CUT:"Cut",COPY:"Copy",PSTE:"Paste",PSTM:"Paste=",SALL:"Sel All",SWRD:"Sel Wd",
|
||||
FIND:"Find",FAGN:"Find→",NEW:"New",OPEN:"Open",CLOZ:"Close",QUIT:"Quit",
|
||||
SCAP:"Shot▸file",SCLP:"Shot▸clip",SRCH:"Search",KILL:"Force-Quit",HENT:"Hard⏎",
|
||||
ZOOMIN:"Zoom+",ZOOMOUT:"Zoom−",ZOOMRST:"Zoom·0",HISTPRV:"◀ Back",HISTNXT:"Fwd ▶",
|
||||
Mac:"OS·Mac",Win:"OS·Win",Lux:"OS·Linux",MHEN:"英数",HENK:"かな",DKT8:"Dictate",AIVC:"AI/Siri"};
|
||||
const KC={SPC:"Space",BSPC:"⌫",ENT:"⏎",ENTER:"⏎",TAB:"⇥",ESC:"Esc",DEL:"⌦",INS:"Ins",
|
||||
HOME:"Home",END:"End",PGUP:"PgUp",PGDN:"PgDn",UP:"↑",DOWN:"↓",LEFT:"←",RGHT:"→",RIGHT:"→",
|
||||
MUTE:"🔇",VOLU:"Vol+",VOLD:"Vol−",MPLY:"⏯",MPRV:"⏮",MNXT:"⏭",MSTP:"⏹",MRWD:"⏪",MFFD:"⏩",
|
||||
EJCT:"⏏",BRIU:"☀+",BRID:"☀−",NUM:"NumLk",CAPS:"CapsLk",APP:"▤ Menu",
|
||||
DLR:"$",HASH:"#",SLSH:"/",DQUO:'"',QUOT:"'",COMM:",",DOT:".",MINS:"−",UNDS:"_",EQL:"=",
|
||||
CIRC:"^",PIPE:"|",AMPR:"&",LT:"<",GT:">",LBRC:"[",RBRC:"]",LPRN:"(",RPRN:")",LCBR:"{",RCBR:"}",
|
||||
ASTR:"*",PLUS:"+",SCLN:";",TILD:"~",GRAVE:"`",EXLM:"!",QUES:"?",
|
||||
PSLS:"/",PAST:"*",PMNS:"−",PPLS:"+",PEQL:"=",PDOT:".",PCMM:",",PENT:"⏎",
|
||||
P0:"0",P1:"1",P2:"2",P3:"3",P4:"4",P5:"5",P6:"6",P7:"7",P8:"8",P9:"9",
|
||||
LCTL:"⌃",LALT:"⌥",LGUI:"⌘",LSFT:"⇧",RCTL:"⌃",RALT:"⌥",RGUI:"⌘",RSFT:"⇧",
|
||||
LNG1:"英/かな",LNG2:"かな",T:"T",C:"C"};
|
||||
const MODSYM={SFT:"⇧",CTL:"⌃",ALT:"⌥",GUI:"⌘",HYPR:"✦",MEH:"◆"};
|
||||
const LAYERNAME={L_SYM:"SYM",L_FUN:"FUN",L_NUM:"NUM",L_NAV:"NAV",L_CFG:"CFG",L_HD:"HD"};
|
||||
const EXACT={ "LALT(KC_8)":{m:"•",h:"⌥8"}, "LSA(KC_8)":{m:"°",h:"⇧⌥8"}, "LSA(KC_9)":{m:"·",h:"⇧⌥9"},
|
||||
"LALT(KC_9)":{m:"ª",h:"⌥9"}, "LCA(KC_DEL)":{m:"⌃⌥⌦",h:"kill"}, "G(KC_TAB)":{m:"⌘⇥"}, "G(KC_R)":{m:"⌘R"},
|
||||
"HD_AdaptKeyToggle":{m:"Adaptive",h:"on/off",cls:"layer"}, "HD_L_QWERTY":{m:"▸QWERTY",cls:"layer"},
|
||||
"HD_L_ALPHA":{m:"▸HD",cls:"layer"}, "QK_BOOT":{m:"Bootloader",cls:"rgb"}, "QK_RBT":{m:"Reboot",cls:"rgb"},
|
||||
"EE_CLR":{m:"Clr EEPROM",cls:"rgb"}, "AG_SWAP":{m:"⌥/⌘ swap",cls:"rgb"}, "AG_NORM":{m:"⌥/⌘ norm",cls:"rgb"},
|
||||
"KC_TRNS":{m:"▽",cls:"trns"}, "KC_NO":{m:"∅",cls:"none"} };
|
||||
|
||||
function fmt(kc){
|
||||
if(EXACT[kc]) {const e=EXACT[kc];return {m:e.m,h:e.h||"",cls:e.cls||classify(kc,e.m)};}
|
||||
let m;
|
||||
// mod-tap RSFT_T(KC_A)
|
||||
let mt=kc.match(/^[LR](SFT|CTL|ALT|GUI|HYPR|MEH)_T\((.+)\)$/);
|
||||
if(mt){let inner=fmt(mt[2]);return {m:inner.m,h:MODSYM[mt[1]],cls:"mod"};}
|
||||
// layer-tap LT(L_x,KC_y)
|
||||
let lt=kc.match(/^LT\((\w+),\s*(.+)\)$/);
|
||||
if(lt){let inner=fmt(lt[2]);return {m:inner.m,h:"▸"+(LAYERNAME[lt[1]]||lt[1].replace("L_","")),cls:"layer"};}
|
||||
// MO/TG/TO/TT/DF(L_x)
|
||||
let mo=kc.match(/^(MO|TG|TO|TT|DF)\((\w+)\)$/);
|
||||
if(mo){return {m:"▸"+(LAYERNAME[mo[2]]||mo[2].replace("L_","")),h:mo[1].toLowerCase(),cls:"layer"};}
|
||||
// SK_*
|
||||
let sk=kc.match(/^SK_(\w+)$/);
|
||||
if(sk){return {m:SK[sk[1]]||sk[1],h:"",cls:classify(kc,SK[sk[1]]||"")};}
|
||||
// RGB_*
|
||||
let rg=kc.match(/^RGB_(\w+)$/);
|
||||
if(rg){const map={TOG:"RGB ⏻",MOD:"RGB ▸",VAI:"RGB ☀+",VAD:"RGB ☀−",HUI:"RGB hue+",HUD:"RGB hue−",SAI:"RGB sat+",SAD:"RGB sat−"};
|
||||
return {m:map[rg[1]]||("RGB "+rg[1]),h:"",cls:"rgb"};}
|
||||
// KC_*
|
||||
let k=kc.match(/^KC_(\w+)$/);
|
||||
if(k){m=KC[k[1]] !== undefined ? KC[k[1]] : (k[1].match(/^F\d+$/)?k[1]: k[1].length===1?k[1]:k[1]);
|
||||
return {m:m,h:"",cls:classify(kc,m)};}
|
||||
return {m:kc,h:"",cls:"base"};
|
||||
}
|
||||
function classify(kc,m){
|
||||
if(/SK_/.test(kc))return "sem";
|
||||
if(/RGB_|QK_|AG_|EE_/.test(kc))return "rgb";
|
||||
if(/VOL|MPLY|MPRV|MNXT|MSTP|MRWD|MFFD|MUTE|BRIU|BRID|EJCT/.test(kc))return "media";
|
||||
if(/KC_(DLR|HASH|SLSH|DQUO|QUOT|COMM|DOT|MINS|UNDS|EQL|CIRC|PIPE|AMPR|LT|GT|LBRC|RBRC|LPRN|RPRN|LCBR|RCBR|ASTR|PLUS|SCLN|TILD|GRAVE|EXLM|QUES|PSLS|PAST|PMNS|PPLS|PEQL)/.test(kc))return "sym";
|
||||
if(/KC_(P?\d|P[0-9]|HOME|END|PGUP|PGDN|UP|DOWN|LEFT|RGHT|RIGHT|PDOT|PCMM)/.test(kc))return "num";
|
||||
if(/KC_[LR](CTL|ALT|GUI|SFT)$/.test(kc))return "mod";
|
||||
return "base";
|
||||
}
|
||||
|
||||
function keyEl(kc){
|
||||
const f=fmt(kc);
|
||||
const d=document.createElement("div");
|
||||
d.className="key "+(f.cls||"base");
|
||||
d.title=kc;
|
||||
d.innerHTML=(f.h?`<span class="h">${f.h}</span>`:"")+`<span class="m">${f.m}</span>`;
|
||||
return d;
|
||||
}
|
||||
function half(rows,thumbs,side){
|
||||
const h=document.createElement("div");h.className="half";
|
||||
rows.forEach(r=>r.forEach(kc=>h.appendChild(keyEl(kc))));
|
||||
const t=document.createElement("div");t.className="thumbs";
|
||||
// pad: left half thumbs sit in cols 4-6; right half in cols 1-3
|
||||
const cells = side==="L" ? ["","","",...thumbs] : [...thumbs,"","",""];
|
||||
cells.forEach(kc=>{ if(kc==="") {const s=document.createElement("div");s.style.visibility="hidden";t.appendChild(s);} else t.appendChild(keyEl(kc)); });
|
||||
h.appendChild(t);
|
||||
return h;
|
||||
}
|
||||
const app=document.getElementById("app");
|
||||
Object.entries(L).forEach(([name,d])=>{
|
||||
const sec=document.createElement("div");sec.className="layer";
|
||||
sec.innerHTML=`<h2><b>${name}</b> ${d.purpose}</h2><p class="how">reach: ${d.how}</p>`;
|
||||
const board=document.createElement("div");board.className="board";
|
||||
board.appendChild(half([d.top[0],d.home[0],d.bot[0]],d.thumbs[0],"L"));
|
||||
board.appendChild(half([d.top[1],d.home[1],d.bot[1]],d.thumbs[1],"R"));
|
||||
sec.appendChild(board);app.appendChild(sec);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
242
users/raphaelmor/HDPM-parity.md
Normal file
242
users/raphaelmor/HDPM-parity.md
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
# Hands Down Promethium — feature parity checklist
|
||||
|
||||
Exhaustive list of everything the **canonical** moutis Hands Down repo supports for the
|
||||
**Promethium (pm)** variant, for porting into this firmware. Sourced from
|
||||
`../HandsDown/handsdown/pm-adaptive.c`, `../HandsDown/handsdown/pm-config.h`,
|
||||
`../HandsDown/moutis_combo_def.c`, `../HandsDown/moutis.h`. Updated against **main @ `a46d847`** (SemKeys-refactor / QWERTY-optional era).
|
||||
|
||||
Legend: `[x]` already in this firmware · `[ ]` not yet · **(hold)** = distinct action when held past `COMBO_HOLD`/`LINGER_TIME`.
|
||||
|
||||
> **Status (implemented).** Adaptives: **complete PM set incl. the 3-key rules** via `preprior` tracking,
|
||||
> gated to the Hands Down base (`ramo_adaptive.c`). **ADAPT_SHIFT** (comma-leader one-shot Shift), HDP-only.
|
||||
> Combos: `qu`/`z`/`th` + Shift-Tab + App-menu + diacritics/ligatures + `; : ! ?`, all at canonical positions,
|
||||
> HDP-gated (`ramo_combos.c`). Linger brackets `( {` + smart caps-word + Shift+Bksp→Del (`ramo_linger.c`,
|
||||
> `ramo_process_record.c`). Option-A thumbs; HD outer columns blanked. Mac-only → Semantic-Keys engine
|
||||
> intentionally **not** ported.
|
||||
|
||||
> **Porting note.** Our HDP base now uses moutis's **canonical arrangement** (V W G M J top-left,
|
||||
> `# . / " '` top-right, F P D L X bottom-left, `- U O Y B` bottom-right), so combos map to his exact
|
||||
> positions. Kept customizations: GACS home-row mods, Hyper/Meh on fixed top positions (G/M, ./,/),
|
||||
> blanked outer pinky columns, Option-A thumbs (Colemak-aligned). Home rows match canonical (`S N T H K` / `, A E I C`).
|
||||
|
||||
Canonical PM reference layout (positions → letters):
|
||||
```
|
||||
V W G M J #$ .: /* "[ ']
|
||||
S N T H K () ,; A E I C Z(RM5)
|
||||
F P D L X [ ] -+ U O Y B Qu(RB5)
|
||||
bsp R spc ret
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. Adaptive keys (`pm-adaptive.c`)
|
||||
|
||||
Fire only if the previous key was within `ADAPTIVE_TERM` (rolling window). All eliminate an
|
||||
SFB/scissor by rewriting the roll. "→ del X" = backspaces the already-typed key first.
|
||||
|
||||
### Left hand
|
||||
- [x] **P D → PWD** — on `D` after `P`: insert `W` (then D). *(simplified from the old 3-key P·B·D in the refactor)*
|
||||
- [x] **P F → PS** — on `F` after `P`: type `S` instead of F
|
||||
- [x] **K G → KL** — on `G` after `K`: type `L` instead of G
|
||||
- [x] **W M G → WML** — on `G` after `W,M`: type `L` instead of G *(3-key, via preprior)*
|
||||
- [x] **M G → LG** — on `G` after `M`: del M, type `L`, then G
|
||||
- [x] **J G → JPG** — on `G` after `J`: insert `P`, then G
|
||||
- [x] **W G → WD** — on `G` after `W`: type `D` instead of G
|
||||
- [x] **K H → KN** — on `H` after `K`: type `N` instead of H
|
||||
- [x] **G J → G+"th"** — on `J` after `G`: type `th` (for "length")
|
||||
- [x] **V J / W J → …L** — on `J` after `W`: type `L` instead of J. *(`V J` **removed** — V+J is the `˜` tilde combo)*
|
||||
- [x] **M K → LK** — on `K` after `M`: del M, type `L`, then K
|
||||
- [x] **H K → NK** — on `K` after `H`: del H, type `N`, then K
|
||||
- [ ] **P/B/S then L → unshifted L** — skipped by choice (low value on this HDP arrangement)
|
||||
- [x] **G M → GL** — on `M` after `G`: type `L` instead of M
|
||||
- [x] **V M → VL** — on `M` after `V`: type `L` instead of M
|
||||
- [ ] **M W M → …L** — **removed**: unreachable, W+M is the `qu` combo (see below)
|
||||
- [ ] **W M → "lm"** — **removed**: unreachable, W+M is the `qu` combo. Combos resolve in
|
||||
`pre_process_record_quantum`, *before* the adaptive engine runs — a roll fast enough for the
|
||||
adaptive is also inside `COMBO_TERM`, so `qu` always won. Kept the combo; `Q` needs a home.
|
||||
- [x] **F P → SP** — on `P` after `F`: del F, type `S`, then P
|
||||
- [x] **G V → GT** — on `V` after `G`: type `T` instead of V
|
||||
- [x] **M V → LV** — on `V` after `M`: del M, type `L`, then V
|
||||
- [x] **G W → GD** — on `W` after `G`: type `D` instead of W
|
||||
- [x] **M W → MP** — on `W` after `M`: type `P` instead of W
|
||||
|
||||
### Right hand
|
||||
- [x] **Y B → IB** — on `B` after `Y`: del Y, type `I`, then B
|
||||
- [x] **A E → AU** — on `E` after `A`: type `U` instead of E
|
||||
|
||||
### Adaptive infrastructure
|
||||
- [x] **Base-gating** — adaptives run only when Hands Down is the active default layer (`ramo_adaptive.c`); Colemak types clean.
|
||||
- [x] **preprior tracking** — engine keeps `prior` AND `preprior` (enables the 3-key rules above)
|
||||
- [x] **ADAPT_SHIFT** (`,` leader) — HDP-only: `,` then a letter within `RAMO_ADAPT_SHIFT_TERM` → capitalizes it, deletes the comma (`ramo_process_record.c`)
|
||||
- [ ] **HD_MAGIC key** (`#`) + **HD_MAGIC_B** (`Bksp`) — "magic"/repeat-style text expansion (`.com`, `.org`, `.edu`, etc. via `adapt_magic.c`). Not pursued. Note: canonical `,,` toggles **Caps Lock**.
|
||||
|
||||
---
|
||||
|
||||
## 2. Combos (`moutis_combo_def.c` → `key_combos[]`)
|
||||
|
||||
### 2a. Off-map alphas — *needed to type these letters ergonomically*
|
||||
- [x] **qu** (`W+M`, canonical) **(hold: delete u → q)**
|
||||
- [x] **z** (`N+H`)
|
||||
|
||||
### 2b. H-digraph combos — `th` is **enabled** (`USE_HD_H_DIGRAPH_COMBO`, `HDP-defs.h`); the rest are optional
|
||||
- [x] **th** (`T+H`) — live on HDP; Colemak gets its own (`CMK_Th_keys`, `S+T` positions)
|
||||
- [ ] **ch** (`I+C`, or `N+T`)
|
||||
- [ ] **sh** (`S+N`)
|
||||
- [ ] **wh** (`W+G`)
|
||||
- [ ] **ph** (`Y+B`, or `P+L`)
|
||||
- [ ] **gh** (`G+M`)
|
||||
- [ ] **sch** (`S+N+T`)
|
||||
|
||||
### 2c. Whitespace / editing keys
|
||||
- [x] **Tab** — on thumb (not a combo)
|
||||
- [x] **Shift-Tab** (`V+W+G`) — combo
|
||||
- [x] **App menu** (`M+J`) — combo
|
||||
- [ ] **Space / Enter** — on thumbs already (no combo needed)
|
||||
- [ ] **hard-Enter / page break** (`X+L+D`, `SK_HENT`) — needs SemKeys (skipped, Mac-only)
|
||||
- [ ] **Config layer MO(L_CFG)** — no CFG layer in our scheme
|
||||
- [ ] **Num-layer toggle** (`R + Space`) → `TG(L_NUM)` — optional, not added
|
||||
|
||||
### 2d. Semantic editing combos (all via SemKeys — see §3)
|
||||
- [ ] **New** (`X+K`)
|
||||
- [ ] **Open** (`(-)+(,)`)
|
||||
- [ ] **Close** (`(#)+(,)`)
|
||||
- [ ] **Quit** (`J+K`)
|
||||
- [ ] **Find** (`S+K`) **(hold: find-selection)**
|
||||
- [ ] **Select All** (`F+L`)
|
||||
- [ ] **Select Word** (`F+X`)
|
||||
- [ ] **Undo** (`F+P`)
|
||||
- [ ] **Redo** (`F+P+D`)
|
||||
- [ ] **Copy** (`P+D`) **(hold: Cut)**
|
||||
- [ ] **Paste** (`D+L`) **(hold: Paste-and-Match)**
|
||||
|
||||
### 2e. Symbol / punctuation combos (canonical top-right cluster) — HDP base only
|
||||
- [x] **;** (`,+A`)
|
||||
- [x] **:** (`.+"`)
|
||||
- [x] **!** (`.+/`)
|
||||
- [x] **?** (`/+"`)
|
||||
- [ ] `$ § @ _ ~ = –` — not added (belong on **SYM v2**, where there's room)
|
||||
|
||||
### 2f. Ligatures — HDP base only, Mac ⌥ dead-keys
|
||||
- [x] **Œ** (`E+O` → ⌥Q)
|
||||
- [x] **Æ** (`A+U` → ⌥')
|
||||
|
||||
### 2g. Dead-key diacritics (Mac ⌥) — HDP base only, canonical positions
|
||||
- [x] **´ acute** (`A+E` → ⌥E)
|
||||
- [x] **` grave** (`E+I` → ⌥`)
|
||||
- [x] **ˆ circumflex** (`A+I` → ⌥I)
|
||||
- [x] **¨ dieresis** (`U+O` → ⌥U)
|
||||
- [x] **˜ tilde / ñ** (`V+J` → ⌥N)
|
||||
- [x] **¸ cedilla** (`S+T` → ⌥C)
|
||||
- [ ] **¯ macron** (`E+C`) / **˚ ring** (`A+C`) — not added (rare)
|
||||
|
||||
### 2h. Screen / system (via SemKeys)
|
||||
- [ ] **Esc** (`S+H`)
|
||||
- [ ] **Force-quit** (`(")+(#)`, `SK_KILL`)
|
||||
- [ ] **Screenshot → file** (`W+G+M+J`)
|
||||
- [ ] **Screenshot → clipboard** (`G+M+J`)
|
||||
- [ ] **Platform search / Siri** (`T+H+K`)
|
||||
- [ ] **Caps Word** (`Y+B`) — *combo only;* the feature itself is done, but **implemented differently** (see §3).
|
||||
⚠️ `Y+B` is already the **YB → IB adaptive** (`ramo_adaptive.c`). Porting this combo means moving one of them.
|
||||
- [ ] **Caps Lock** (`O+Y+B`)
|
||||
- [ ] **kana / eisuu toggles** (`I+E+A` / `N+T+H`)
|
||||
|
||||
### 2i. Custom text-macro combos
|
||||
- [ ] **LeftCombo** (`W+G+M`) → user string **(hold: alt string)**
|
||||
- [ ] **RightCombo** (`(.)+(/)+(")`) → user string **(hold: alt string)**
|
||||
|
||||
### 2j. Numpad combos (on L_NUM; position-based, ~22)
|
||||
- [ ] Enter, Bksp, Del, Tab, Shift-Tab on numpad
|
||||
- [ ] `– ~ … ( ) : % °` and currency `€ ¥ $ ¢ £ ÷` , `± ≠`
|
||||
- [ ] Clear (`C`), All-Clear (`Esc`), Num-Lock toggle
|
||||
|
||||
### 2k. Pronoun combos — *optional (`EN_PRONOUN_COMBOS`), moutis calls them "unnecessary"*
|
||||
- [ ] `I` (`I+C`), `I'd` (`I+D`), `I'll` (`I+L`), `I'm` (`I+M`), `I've` (`I+V`) — most **(hold: +'ve)**
|
||||
- [ ] (`_ALL`) you'd / you'll / you're / you've / your, they / their / they're / they'll / they'd
|
||||
- [ ] (`EN_W_PRONOUNS`) we'd / we'll / we're / we've / where
|
||||
|
||||
### 2l. Japanese youon combos — *optional (`JP_MODE_ENABLE`), ~50 kana*
|
||||
- [ ] きゃ〜りょ contracted-sound combos (skip unless you type Japanese)
|
||||
|
||||
---
|
||||
|
||||
## 3. Feature systems (`moutis.h` + engine files)
|
||||
|
||||
- [x] **Home-row mods** (all base-layer alphas)
|
||||
- [x] **Thumb layer-taps** — Colemak: Esc→MEDIA, Spc→NAV, Tab→MOUSE, Ent→SYM, Bspc→NUM, Del→FUN. HDP (Option A): thumbs aligned to Colemak, only R→SYM (inner-right) & Ent→FUN (outer-right) differ; Del→**Shift+Bksp**.
|
||||
- [x] **Caps Word** — **different implementation, same intent.** We use QMK's built-in `caps_word` + the
|
||||
`caps_word_press_user` hook (`ramo_linger.c`); moutis rolls his own engine (`moutis_casemods.c`,
|
||||
~144 lines, from Andrew Rae's pre-QMK-builtin implementation). Behavioral differences worth knowing:
|
||||
| | Ours (QMK builtin) | Moutis (`moutis_casemods.c`) |
|
||||
|---|---|---|
|
||||
| Shifting | weak mods per letter | toggles real **Caps Lock** (`tap_code(KC_CAPS)`) — for iOS etc. |
|
||||
| Activation | `CW_TOGG` on NAV | `,,` adaptive + `Y+B` combo |
|
||||
| space → `_` | in `process_record` | in the engine, tracks `last_press_was_space` |
|
||||
| double-space | *(nothing special)* | exits and **rewrites** `_` back to a real space |
|
||||
| Auto-off | `CAPS_WORD_IDLE_TIMEOUT` (5 s default) | `STATE_RESET_TIME * 3` ≈ 1.8 s, in `matrix_scan` |
|
||||
His Caps-Lock approach survives host-side remapping that weak-mod shifting doesn't; ours needs no
|
||||
`matrix_scan` work and no LED-state round-trip. Only port his if the weak-mod version misbehaves somewhere.
|
||||
- [ ] **Semantic keys (`SK_*`)** — cross-platform abstraction. **Intentionally skipped — you're Mac-only**, so `⌘C/⌘V/⌘Z` on NAV stay as-is. — `moutis_semantickeys.c`
|
||||
- [x] **Linger keys** — hold `(` `{` → auto-paired with caret inside (`ramo_linger.c`, `RAMO_LINGER_TERM`). `"` is unmapped and `[` isn't on **any** layer yet (SYM has `{ } ( )` only) — both need a SYM v2 home.
|
||||
- [ ] **Key overrides** — manual Shift/Alt variants of punctuation. (Only Shift+Bksp→Del is done, for the Option-A thumb change.)
|
||||
- [ ] **OS platform switching** — N/A (Mac-only).
|
||||
- [ ] **Adaptive on/off toggle** — not needed: adaptives are auto-gated to the HD base, off on Colemak.
|
||||
- [ ] **Japanese input mode** — `JP_MODE_ENABLE` (not pursued).
|
||||
- [~] **Layer structure** — unchanged: our 8-layer scheme (COLEMAK/HDP/MEDIA/NAV/MOUSE/SYM/NUM/FUN). Not adopting canonical HD's separate layers.
|
||||
|
||||
---
|
||||
|
||||
## 4. Timings (`config.h`) — ours vs. canonical
|
||||
|
||||
Deliberately **not** matched to moutis. His values are in `../HandsDown/config.h` + `moutis.h`.
|
||||
WPM equivalent = `12000 / ms` (5 chars/word), i.e. the speed *above* which a window still catches you.
|
||||
|
||||
| Constant | Ours | Moutis | Note |
|
||||
|---|---|---|---|
|
||||
| `TAPPING_TERM` | 200 | 170 | |
|
||||
| **Adaptive window** | **175 ms** (~69 WPM) | 229 ms (`COMBO_HOLD*1.35`, ~52 WPM) | see below |
|
||||
| ADAPT_SHIFT window | 200 ms | 918 ms (`ADAPTIVE_TERM*4`) | |
|
||||
| `COMBO_TERM` | 50 (QMK default) | 42 (`TT/4`) | |
|
||||
| Combo hold / linger | 200 / 200 | 170 / 204 | |
|
||||
| `TAP_CODE_DELAY` | **20** | 20 | adopted — at 0, back-to-back taps in del-then-retype adaptives can drop or reorder |
|
||||
| `QUICK_TAP_TERM` | **0** | 170 (default) | intentional: disables double-tap-hold auto-repeat |
|
||||
| `PERMISSIVE_HOLD` | off | on | intentional (for now) |
|
||||
|
||||
**Adaptive window — tuned empirically (`tt verify`), 50 → 100 → 175 ms.**
|
||||
The key insight: **adaptives fix SFBs and scissors, which are slow motions by definition** — so a
|
||||
window tuned to *roll* speed systematically fails the highest-value rules. At 100 ms, fast
|
||||
adjacent-finger rolls passed (`PD`, `WG`) while `KH` (same-finger, index→index) and `KG`
|
||||
(inner-column stretch) never fired at all. 175 ms clears those motions while staying well under
|
||||
moutis's always-armed 229 ms.
|
||||
|
||||
**Window is measured with `record->event.time`, not `timer_read()`.** For a mod-tap/layer-tap,
|
||||
`process_record_user` only sees the record once the tap resolves (on release, or after
|
||||
`TAPPING_TERM`), so read-time would charge the hold duration against the window. 12 keys are
|
||||
mod-taps (`G M . /` + both home rows), i.e. most adaptive triggers.
|
||||
|
||||
**3-key rules need two consecutive intervals inside the window**, so their failure rate is roughly
|
||||
the square of the 2-key rate — expect them to be the last to become reliable.
|
||||
|
||||
**Known collisions to watch at 175 ms** — same-row rolls that appear in real words:
|
||||
`GM` in *pragma* / *progmem* · `PD` in *update* · `YB` in *keyboard* · `MW` in *firmware* · `KG` in *background*.
|
||||
None observed yet in practice. `MK` (*qmk*) is safe, being same-finger.
|
||||
|
||||
**Combo-vs-adaptive collisions:** combos resolve in `pre_process_record_quantum`, before the adaptive
|
||||
engine. Any rule whose two keys are also a combo is unreachable — `W M`/`M W M` (`qu`) and `V J` (`˜`)
|
||||
were removed for this. Check new adaptives against `ramo_combos.c` before adding.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Done vs. remaining
|
||||
**Done:** complete PM adaptives incl. 3-key rules (preprior) · ADAPT_SHIFT · `qu`/`z`/`th` · Shift-Tab · App-menu · diacritics + ligatures (`é è ê ü ñ ç œ æ`) · `; : ! ?` symbol combos · linger `( {` · smart caps-word · Shift+Bksp→Del · Option-A thumbs · canonical arrangement · blanked HD outer columns. **All HDP-gated where base-specific. Builds ~64 KB.**
|
||||
|
||||
**Still open (by choice / next):**
|
||||
|
||||
> **Current focus: learning the layout, not extending it.** The firmware is feature-complete for
|
||||
> daily driving. Everything below is deferred until real use shows it's needed — including the
|
||||
> adaptive-window collisions in §4.
|
||||
|
||||
1. **SYM "Programming v2"** — bracket/operator layer for Swift/Odin/ObjC (mock in `hd-colemak.html`). Would also give `[` (currently on no layer) and `"` a home for linger.
|
||||
2. **Colemak-DH** — optional base swap (6 keys), mocked as a toggle.
|
||||
3. Nice-to-have combos: `ch/sh/wh/ph/gh` digraphs (`th` already on); `$ § @ ~ = –` (better on SYM v2).
|
||||
4. **Not pursued (Mac-only / design):** Semantic keys → so §2d editing & §2h screen combos (they need SemKeys), HD_MAGIC, macron/ring diacritics, pronouns (§2k), Japanese (§2l), numpad combos (§2j).
|
||||
674
users/raphaelmor/LICENSE
Normal file
674
users/raphaelmor/LICENSE
Normal file
|
|
@ -0,0 +1,674 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
328
users/raphaelmor/QMK-feature-audit.md
Normal file
328
users/raphaelmor/QMK-feature-audit.md
Normal file
|
|
@ -0,0 +1,328 @@
|
|||
# QMK feature audit — candidates for this firmware
|
||||
|
||||
Survey of **everything QMK offers**, judged against *this* keymap (Colemak + Hands Down
|
||||
Promethium on a Corne v4.1). Companion to [`HDPM-parity.md`](HDPM-parity.md): that doc tracks
|
||||
parity with **moutis' Hands Down**, this one tracks parity with **QMK core**.
|
||||
|
||||
Sources: QMK `../qmk` @ `3ef9860ee1` (`0.29.8-768`, 2026-07-22) · userspace `users/raphaelmor/`.
|
||||
|
||||
Legend: `[x]` in this firmware · `[ ]` not here · **T1/T2** = recommended tier · **✗** = examined
|
||||
and rejected, with the reason.
|
||||
|
||||
> **The framing.** `HDPM-parity.md:235-237` freezes feature work ("learning the layout, not
|
||||
> extending it"). So the bar below is not *"is this interesting"* but **"does it reduce friction
|
||||
> while learning, or fix something already broken?"**
|
||||
>
|
||||
> **The one-line finding:** this firmware is unusually complete on the **typing-transformation**
|
||||
> axis (adaptives, combos, linger, comma-shift, smart Caps Word) and near-empty on the
|
||||
> **tap-hold-correctness** and **feedback** axes. 16 home-row mod-taps run with *zero* misfire
|
||||
> protection. That asymmetry is where all the value is.
|
||||
|
||||
---
|
||||
|
||||
## 1. What's already on
|
||||
|
||||
**`rules.mk`:** Caps Word · Combos · Tap Dance · Console + `KEYCODE_STRING` · introspection.
|
||||
|
||||
**Inherited from `crkbd/rev4_1`:** RGB Matrix · split · Mouse Keys · OLED · encoders ·
|
||||
bootmagic · NKRO · extrakey.
|
||||
|
||||
**Hand-rolled, with no QMK feature behind it:**
|
||||
|
||||
| Behaviour | Where | QMK equivalent |
|
||||
|---|---|---|
|
||||
| Adaptive keys (24 rules) | `ramo_adaptive.c` | none — genuinely custom |
|
||||
| Linger brackets `( {` | `ramo_linger.c:10-38` | none |
|
||||
| SCREAMING_SNAKE Caps Word | `ramo_linger.c:80-97` | none (extends builtin Caps Word) |
|
||||
| Comma-leader one-shot Shift | `ramo_process_record.c:48-60` | loosely, one-shot mods |
|
||||
| Shift+Bspc → Del | `ramo_process_record.c:35-42` | **Key Overrides** → §3.3 |
|
||||
| Combo hold-to-linger | `ramo_combos.c:154-167` | none |
|
||||
|
||||
---
|
||||
|
||||
## 2. The full QMK catalog
|
||||
|
||||
### 2a. Tap/hold and timing — `docs/tap_hold.md`
|
||||
- [x] `TAPPING_TERM` (200) · `QUICK_TAP_TERM` (0)
|
||||
- [ ] **`CHORDAL_HOLD`** — same-hand nested key forces the tap. → **T1, §3.1**
|
||||
- [ ] **`FLOW_TAP_TERM`** — suppress hold shortly after another key. → **T2, §4.1**
|
||||
- [ ] `PERMISSIVE_HOLD` / `HOLD_ON_OTHER_KEY_PRESS` — → **T1, pairs with §3.1**
|
||||
- [ ] **`DYNAMIC_TAPPING_TERM_ENABLE`** (`DT_UP`/`DT_DOWN`/`DT_PRNT`) → **T2, §4.3**
|
||||
- [ ] `RETRO_TAPPING` / Retro Shift — ✗ fights home-row mods
|
||||
- [ ] `SPECULATIVE_HOLD` (`docs/tap_hold.md:819`) — ✗ very new, perturbs the exact path the adaptives ride
|
||||
- [ ] `TAPPING_TERM_PER_KEY` · selectable debounce — not needed yet
|
||||
- **Achordion is not in QMK core.** It's Getreuer's userspace library; `CHORDAL_HOLD` is the
|
||||
in-tree replacement. Don't go looking for it.
|
||||
|
||||
### 2b. Text / typing aids
|
||||
- [x] Caps Word (`docs/features/caps_word.md`) — see §4.5 for two free upgrades
|
||||
- [x] Combos · Tap Dance · Send String
|
||||
- [ ] **Repeat / Alt-Repeat Key** (`QK_REP`/`QK_AREP`) → **T1, §3.2**
|
||||
- [ ] **Key Overrides** → **T1, §3.3**
|
||||
- [ ] **Layer Lock** (`QK_LLCK`) → **T2, §4.2**
|
||||
- [ ] Autocorrect — ✗ §5
|
||||
- [ ] Leader Key — ✗ §5
|
||||
- [ ] Unicode / Unicode Map / UCIS / `send_unicode_string()` — ✗ §5 (Mac ⌥ dead keys by design)
|
||||
- [ ] Auto Shift · Space Cadet · one-shot/sticky mods · Dynamic Macros · Key Lock (`QK_LOCK`) ·
|
||||
Swap Hands · Secure · Grave Escape · Magic keycodes · Stenography — ✗ §5
|
||||
|
||||
### 2c. Input devices
|
||||
- [x] Mouse Keys (tuned, `config.h:17-26`)
|
||||
- [~] **Encoders** — compiled in, *inert*, and pin-blocked → **§4.7**
|
||||
- [ ] Pointing Device · PS/2 Mouse · Digitizer · Joystick · Programmable Buttons · DIP Switch — no hardware / no need
|
||||
|
||||
### 2d. Output / feedback
|
||||
- [x] RGB Matrix — static ledmap only, all 44 stock animations `#undef`'d (`config.h:56-101`)
|
||||
- [~] **OLED** — enabled at keyboard level, **no `oled_task_user`** → **T1, §3.4**
|
||||
- [ ] **WPM** (+ `SPLIT_WPM_ENABLE`) → **T2, §4.4**
|
||||
- [ ] LED Matrix · Backlight · LED Indicators · ST7565/HD44780 · Quantum Painter · Audio/Music/Clicky ·
|
||||
MIDI · Sequencer · Haptic — not applicable or not wanted
|
||||
|
||||
### 2e. Infrastructure
|
||||
- [x] Split + `SPLIT_LAYER_STATE_ENABLE` · introspection · Community Modules
|
||||
(`elpekenin/ledmap` + `elpekenin/generics`, 2 of ~25)
|
||||
- [ ] Deferred execution — ✗ §5 (polling was a deliberate choice)
|
||||
- [ ] Remaining `SPLIT_*_ENABLE` flags: `LED_STATE` `MODS` `WPM` `OLED` `POINTING` `HAPTIC` `ACTIVITY`
|
||||
- [ ] EEPROM/NVM/wear-leveling · VIA · Raw HID · Tri Layer · OS Detection · Wireless/Battery — not needed
|
||||
|
||||
---
|
||||
|
||||
## 3. Tier 1 — worth doing
|
||||
|
||||
### 3.1 Chordal Hold — the standout gap
|
||||
`#define CHORDAL_HOLD` · `docs/tap_hold.md:568`
|
||||
|
||||
**Why.** 16 home-row mod-taps across two base layers at `TAPPING_TERM 200`, with
|
||||
`PERMISSIVE_HOLD` off, `HOLD_ON_OTHER_KEY_PRESS` off, and **no misfire protection whatsoever**
|
||||
(`HDPM-parity.md:202` records this as "intentional (for now)"). Chordal Hold settles a mod-tap as
|
||||
a *tap* whenever the next key is on the **same hand**, and leaves cross-hand chords to
|
||||
`HOLD_ON_OTHER_KEY_PRESS`/`PERMISSIVE_HOLD`. Hands Down Promethium is built around same-hand
|
||||
rolls, and the adaptive rules deliberately target same-hand SFBs and scissors (`KH`, `KG`, `MG`) —
|
||||
exactly the motions that misfire a mod today.
|
||||
|
||||
**⚠️ It is not a one-liner on this board.** Three things verified in the tree:
|
||||
|
||||
**(a) The auto-generated handedness table misses 10 of your 46 keys.**
|
||||
Handedness is generated as a *weak* `chordal_hold_layout`
|
||||
(`../qmk/lib/python/qmk/cli/generate/keyboard_c.py:207-239`), but the generator **reads only the
|
||||
first layout in `info.json`** — for `crkbd/rev4_1` that's `LAYOUT_split_3x5_3` (36 keys), while
|
||||
this keymap uses `LAYOUT_split_3x6_3_ex2` (46). The 10 uncovered matrix positions stay `0`, which
|
||||
is neither `'L'`, `'R'`, nor `'*'`:
|
||||
|
||||
| Uncovered | Positions | HDP | Colemak |
|
||||
|---|---|---|---|
|
||||
| Outer pinky columns | `(0,0) (1,0) (2,0) (4,0) (5,0) (6,0)` → `L/R T5 M5 B5` | `KC_NO` | `KC_NO` |
|
||||
| The four `ex2` keys | `(0,6) (1,6) (4,6) (5,6)` → `LTA LMA RTA RMA` | `KC_NO` | **`LCTL_T(KC_A)`** |
|
||||
|
||||
Per `get_chordal_hold_default` (`../qmk/quantum/action_tapping.c:940-951`) a `0` compares unequal
|
||||
to everything, so those keys always permit the hold — i.e. Chordal Hold is silently *off* for
|
||||
them. Almost entirely harmless here, since 6 of the 10 are `KC_NO` on both bases; the only live
|
||||
case is the four `ex2` keys on Colemak, which are all `LCTL_T(KC_A)` (`CMK-defs.h:77,79,93,95`).
|
||||
If §3.2 puts `QK_REP`/`QK_AREP` on those keys they become non-tap-hold and the issue disappears
|
||||
entirely.
|
||||
|
||||
**(b) The thumbs get real `'L'`/`'R'` handedness, and that breaks every layer.**
|
||||
The geometry guess only assigns `'*'` to a spacebar or a dead-centre key; all six thumbs land
|
||||
firmly L or R. Every layer here puts its *payload* on the hand opposite its activating thumb —
|
||||
but its **same-side modifier block on the same hand as that thumb**. With a bare
|
||||
`#define CHORDAL_HOLD`, chording the thumb with its own hand's mods within `TAPPING_TERM` settles
|
||||
the layer-tap as a tap:
|
||||
|
||||
| Thumb | Same-hand keys on the target layer | Broken result |
|
||||
|---|---|---|
|
||||
| `LT(L_MEDIA, KC_ESC)` `HDP-defs.h:77` | `KC_LCTL/LALT/LGUI/LSFT` `MED-defs.h:37-40`, `KC_ALGR :55` | types `Esc` |
|
||||
| `LT(L_NAV, KC_SPC)` `:78` | left mods `NAV-defs.h:37-40` **+ both base-switch tap dances** `:23-24` | types `Space` |
|
||||
| `LT(L_MOUSE, KC_TAB)` `:79` | left mods `MOS-defs.h:36-39` | types `Tab` |
|
||||
| `LT(L_SYM, KC_R)` `:80` | `KC_RSFT/RGUI/RALT/RCTL` `SYM-defs.h:46-49` | types `r` |
|
||||
| `LT(L_NUM, KC_BSPC)` `:81` | right mirror mods `NUM-defs.h:46-49` | types `Backspace` |
|
||||
| `LT(L_FUN, KC_ENT)` `:82` | right mirror mods `FUN-defs.h:46-49` | types `Enter` |
|
||||
|
||||
Scope, stated honestly: "Chordal Hold has no effect after the tapping term", so this only bites
|
||||
when the second key lands within 200 ms of the thumb press — which is precisely normal fast layer
|
||||
use. **Prerequisite: define `chordal_hold_layout` over `LAYOUT_split_3x6_3_ex2` with `'*'` on all
|
||||
six thumbs** — the docs recommend exactly this for thumb keys, and it fixes (a) at the same time
|
||||
by covering all 46 positions.
|
||||
|
||||
**(c) The 17 combos are safe.** `get_chordal_hold_default` returns `true` for non-`KEY_EVENT`
|
||||
records (`action_tapping.c:941-943`), so combos are exempt from the opposite-hands rule.
|
||||
|
||||
**Pair it with `HOLD_ON_OTHER_KEY_PRESS`** — the docs state Chordal Hold is *intended* to be used
|
||||
with that or Permissive Hold; alone, the cross-hand case is unchanged.
|
||||
|
||||
**If adopted, verify:** `tt verify` on the same-hand adaptives most at risk — `KH` (same-finger
|
||||
index), `KG` (inner-column stretch), and the 3-key `WMG` — plus confirm cross-hand `⌘C`/`⌘V` and
|
||||
`⇧`+letter still hold. Expect adaptives to get *more* reliable, not less: same-hand taps now
|
||||
settle at the next keydown instead of at release/timeout, which shortens the very delay documented
|
||||
at `ramo_adaptive.c:12-18`.
|
||||
|
||||
### 3.2 Repeat Key — and it answers the four dead keys
|
||||
`REPEAT_KEY_ENABLE = yes` · `QK_REP` / `QK_AREP` · `docs/features/repeat_key.md`
|
||||
|
||||
Same problem domain as the adaptive engine — eliminating same-finger bigrams — but with **zero
|
||||
collision risk**, because it never rewrites already-typed text and so can't fight combos the way
|
||||
`W M`/`V J` did (`HDPM-parity.md:223-225`). moutis' own HD layouts lean on it.
|
||||
|
||||
**Placement is already decided by the keymap:** all four `ex2` positions carry
|
||||
`// TODO: should be a real key once a use is found` — `KC_NO` on HDP (`HDP-defs.h:33,35,49,51`)
|
||||
and a pointless duplicate `LCTL_T(KC_A)` on Colemak (`CMK-defs.h:77,79,93,95`). `QK_REP` and
|
||||
`QK_AREP` are the answer to that TODO, and doing so also erases finding §3.1(a).
|
||||
Alt-Repeat's context-dependent inverse (`←`→`→`, PgDn→PgUp) is a real NAV-layer win.
|
||||
|
||||
**Caveat to check, not assume:** `process_last_key` and `process_repeat_key` run at
|
||||
`../qmk/quantum/quantum.c:343`, **upstream of `process_record_kb`/`process_record_user` (line
|
||||
355)**. So the adaptive engine's `prior_keycode`/`preprior_keycode` history will see the
|
||||
*repeated* keycode rather than `QK_REP`. That's probably what you want — a repeat should count as
|
||||
the letter for adaptive purposes — but it needs a `tt verify` pass to confirm the interaction with
|
||||
the del-then-retype rules.
|
||||
|
||||
### 3.3 Key Overrides — strictly less code than what's there
|
||||
`KEY_OVERRIDE_ENABLE = yes` · `docs/features/key_overrides.md` · already tracked `[ ]` at
|
||||
`HDPM-parity.md:180`
|
||||
|
||||
`ramo_process_record.c:35-42` hand-rolls Shift+Bspc→Del with manual `clear_mods()`/`set_mods()`.
|
||||
`ko_make_with_layers_negmods_and_options` (`../qmk/quantum/process_keycode/process_key_override.h:135`)
|
||||
expresses it declaratively, and `negative_mod_mask` (`:69`) is the exact equivalent of the current
|
||||
`!(get_mods() & ~MOD_MASK_SHIFT)` guard — so ⌘⇧Bspc keeps *not* becoming Del. Also gives a proper
|
||||
home for future shifted-punctuation variants.
|
||||
|
||||
**Behavioural difference to record:** `process_key_override` runs at `quantum/quantum.c:387`,
|
||||
*after* `process_record_user`. Today's early `return false` means the adaptive dispatch never sees
|
||||
the keypress; with an override it would run first and set `prior_keycode = KC_BSPC`. Harmless as
|
||||
far as the current 24 rules go (none trigger on `KC_BSPC`), but it is a real change in state.
|
||||
|
||||
### 3.4 `oled_task_user` — the display is currently lying
|
||||
No `oled_task_user` exists anywhere in the userspace, so stock `crkbd.c` renders: it prints
|
||||
**"Lower" for Hands Down and "Undef" for every layer ≥ 4**, against an 8-layer keymap.
|
||||
|
||||
The DRY fix reuses `RAMO_FOR_EACH_LAYER` (`ramo_layers.h:16-24`) — already the single source for
|
||||
both `keymaps[]` (`keymap.c:49-57`) and `ledmap[]` (`ramo_ledmap.c:6-13`) — to generate a
|
||||
layer-name table from the same list, so a layer can't have a keymap without a name. Worth showing
|
||||
alongside: active base (CMK vs HDP, from `default_layer_state`), Caps Word state, and WPM (§4.4).
|
||||
|
||||
`elpekenin/ui` is already in the `modules/` submodule if you'd rather not hand-roll the drawing.
|
||||
|
||||
---
|
||||
|
||||
## 4. Tier 2 — good fits, lower stakes
|
||||
|
||||
### 4.1 Flow Tap — `#define FLOW_TAP_TERM 150` · `docs/tap_hold.md:485`
|
||||
Complement to Chordal Hold: suppresses the hold when a tap-hold key is pressed shortly after
|
||||
another key. Same mental model as `RAMO_ADAPTIVE_TERM 175` (`config.h:35-49`) — a rolling
|
||||
typing-speed window.
|
||||
|
||||
**⚠️ It would break the thumbs as shipped.** The default `is_flow_tap_key()`
|
||||
(`docs/tap_hold.md:504`) matches `KC_A`–`KC_Z`, `KC_SPC`, `KC_COMM`, `KC_DOT`, `KC_SCLN`, `KC_SLSH`
|
||||
— applied to `get_tap_keycode()`, so it captures `LT(L_NAV, KC_SPC)` (`HDP-defs.h:78`) and HDP's
|
||||
`LT(L_SYM, KC_R)` (`:80`). A letter followed within `FLOW_TAP_TERM` by a reach for thumb-NAV or
|
||||
thumb-SYM would emit a literal space or `r`. **Overriding `is_flow_tap_key()` to exclude thumb
|
||||
layer-taps is mandatory, not optional** (the docs even call out removing `case KC_SPC`).
|
||||
|
||||
**Upside for the adaptives:** Flow Tap forces early tap resolution, which directly mitigates the
|
||||
late-`event.time` problem documented at `ramo_adaptive.c:12-18`.
|
||||
|
||||
### 4.2 Layer Lock — `LAYER_LOCK_ENABLE = yes` · `QK_LLCK` · `docs/features/layer_lock.md`
|
||||
Long numeric entry on NUM and extended mouse work on MOS without holding a thumb the whole time.
|
||||
Both layers have plenty of `*_NO` slots to host it.
|
||||
|
||||
### 4.3 Dynamic Tapping Term — `DYNAMIC_TAPPING_TERM_ENABLE = yes` · `docs/tap_hold.md:79`
|
||||
`DT_UP`/`DT_DOWN`/`DT_PRNT` tune the tapping term live and type out the current value. This is the
|
||||
same empirical loop already used for `RAMO_ADAPTIVE_TERM` (50 → 100 → 175, `config.h:35-49`), but
|
||||
without a reflash per iteration. FUN has free slots.
|
||||
|
||||
**Caveat:** it switches the *runtime* value to `g_tapping_term` while the `TAPPING_TERM` **macro**
|
||||
stays 200 — so `RAMO_COMBO_HOLD`, `RAMO_LINGER_TERM` and `RAMO_ADAPT_SHIFT_TERM`
|
||||
(`config.h:32,33,52`) would stay pinned at 200 while the real tapping term moved. Use
|
||||
`GET_TAPPING_TERM(keycode, record)` if they should track it.
|
||||
|
||||
### 4.4 WPM — `WPM_ENABLE = yes` + `#define SPLIT_WPM_ENABLE`
|
||||
Direct progress feedback during the learning phase, surfaced on the OLED from §3.4. Cheap, and it
|
||||
is the one addition that measures whether the layout is actually being learned.
|
||||
|
||||
### 4.5 Caps Word activation extras — two defines, no code
|
||||
Caps Word is used heavily already (`CW_TOGG` on `NAV_RM0`, plus the `Y+B` combo
|
||||
`ramo_combos.c:76`).
|
||||
- `BOTH_SHIFTS_TURNS_ON_CAPS_WORD` — both index home-row shifts (`LSFT_T(KC_H)` +
|
||||
`RSFT_T(KC_A)`), which is a **cross-hand** chord and so composes cleanly with §3.1.
|
||||
- `CAPS_WORD_INVERT_ON_SHIFT` — shift inverts instead of breaking the word.
|
||||
- Also worth knowing: `CAPS_WORD_IDLE_TIMEOUT` defaults to 5 s; moutis uses ~1.8 s
|
||||
(`HDPM-parity.md:175`).
|
||||
|
||||
### 4.6 `QK_BOOT` / `EE_CLR` — currently on no layer at all
|
||||
Flashing relies entirely on the RP2040 double-tap-reset. Cheap insurance on FUN, which has free
|
||||
slots. (Both appear in the `HDPM-layers.html` mock-up but exist on no real layer.)
|
||||
|
||||
### 4.7 Encoders — **check the hardware before spending any effort**
|
||||
`info.json` declares 2 encoders per side, but there is no `encoder_map`, no `ENCODER_MAP_ENABLE`
|
||||
and no `encoder_update_user` — so they're compiled in and inert.
|
||||
|
||||
**Verified pin conflict:** encoder pins are GP5/GP6/GP7 (left) and GP24/GP6/GP7 (right)
|
||||
(`../qmk/keyboards/crkbd/rev4_1/info.json:36-37,65-66`) against `I2C1_SDA_PIN GP6` /
|
||||
`I2C1_SCL_PIN GP7` (`../qmk/keyboards/crkbd/rev4_1/config.h:10-11`). **Encoders and the OLED
|
||||
share the same pins on rev4.1 and are mutually exclusive.** Decide which you want before writing
|
||||
an encoder map or an `oled_task_user`.
|
||||
|
||||
---
|
||||
|
||||
## 5. Examined and rejected
|
||||
|
||||
- **✗ Autocorrect** — collides with the adaptive engine conceptually: both are armed on every
|
||||
keystroke and both rewrite via backspace. Worse, during a learning phase it **masks the layout
|
||||
errors you need to feel**. Revisit once HDP is automatic.
|
||||
- **✗ Leader Key** — redundant against 17 combos; a second chord vocabulary to memorise for no new
|
||||
capability.
|
||||
- **✗ Auto Shift / Retro Shift** — fights home-row mods and the comma-leader shift
|
||||
(`ramo_process_record.c:48-60`).
|
||||
- **✗ Deferred executors** — polling via `matrix_scan_user` was chosen *deliberately* to dodge
|
||||
roll-over ordering bugs; the reasoning is at `ramo_linger.c:5-8`. Leave it.
|
||||
- **✗ Unicode / Unicode Map / UCIS / `send_unicode_string()`** — the whole diacritic set is Mac ⌥
|
||||
dead keys by design (`ramo_combos.c`, `HDPM-parity.md:181`). Only relevant if a non-Mac host
|
||||
ever appears, and then **OS Detection** (`docs/features/os_detection.md`) is the companion.
|
||||
- **✗ Grave Escape** — Esc is a thumb layer-tap (`HDP-defs.h:77`), so `QK_GESC` has nowhere to go.
|
||||
- **✗ Speculative Hold** (`docs/tap_hold.md:819`) — genuine latency win, but very new and it
|
||||
perturbs the exact tap-hold path the adaptives depend on. Not while also learning the layout.
|
||||
- **✗ One-shot mods** — the comma-leader shift is better suited to this layout than `OSM(MOD_LSFT)`.
|
||||
- **✗ Space Cadet · Swap Hands · Dynamic Macros · Secure · Key Lock · Tri Layer · Programmable
|
||||
Buttons · Digitizer · Joystick · MIDI · Sequencer · Audio · Haptic · Stenography · Quantum
|
||||
Painter · VIA · Raw HID** — no problem in this firmware that they solve.
|
||||
|
||||
---
|
||||
|
||||
## 6. Defects noticed during the survey (documented, not fixed)
|
||||
|
||||
1. **`saved_modifiers` is dead code.** Declared `raphaelmor.c:10` and read at `ramo_combos.c:108`
|
||||
as the "don't fire combos while ⌥/⌘/⌃ is held" guard — but **never assigned anywhere**. As a
|
||||
zero-initialised global it is permanently 0, so the guard never fires. Intent was presumably
|
||||
`get_mods()`.
|
||||
2. **The HD Gold path does not compile.** Un-defining `RAMO_USE_HD_PROMETHIUM` (`config.h:34`)
|
||||
breaks the build: `HDG-defs.h:186,189` define `HD_Q_keys`/`HD_Z_keys` in terms of `HDP_*`
|
||||
positions, and `ramo_combos.c:13-34` references `HDP_*` unconditionally, but `ramo_layers.h:2-6`
|
||||
includes one defs file or the other, never both.
|
||||
3. **`rules.mk:5`** is `// TEMP: for debugging adaptive`. `//` is not a Make comment, and the line
|
||||
contains a `:`, so Make parses it as a stray rule target with prerequisites. Harmless in
|
||||
practice (QMK sets its default goal first) — but it is not a comment.
|
||||
4. **`HDPM-parity.md:138`** marks Caps Word `Y+B` as not done; it *is* implemented
|
||||
(`ramo_combos.c:34,76`).
|
||||
|
||||
---
|
||||
|
||||
## 7. Toolchain blocker
|
||||
|
||||
The `qmk` CLI is currently broken on this machine — `/opt/homebrew/bin/qmk` has a dangling
|
||||
interpreter (`/opt/homebrew/Cellar/qmk/1.1.8/libexec/bin/python`). The userspace `Makefile`
|
||||
resolves `QMK_FIRMWARE_ROOT` via `qmk config -ro user.qmk_home`, so **nothing here can be built
|
||||
until that's repaired** (`brew reinstall qmk`). Unrelated to this audit, but it gates acting on
|
||||
any of it.
|
||||
|
||||
---
|
||||
|
||||
## 8. Summary
|
||||
|
||||
| Priority | Feature | Effort | Note |
|
||||
|---|---|---|---|
|
||||
| **T1** | Chordal Hold + `HOLD_ON_OTHER_KEY_PRESS` | 2 defines **+ a 46-key handedness table with `'*'` thumbs** | biggest win; do not ship without the table |
|
||||
| **T1** | Repeat / Alt-Repeat on the four `ex2` keys | 1 flag + 4 keycodes | answers an existing TODO; also erases §3.1(a) |
|
||||
| **T1** | Key Overrides | 1 flag + 1 table; **deletes** `ramo_process_record.c:35-42` | net less code |
|
||||
| **T1** | `oled_task_user` | ~30 lines via `RAMO_FOR_EACH_LAYER` | the OLED is currently wrong |
|
||||
| **T2** | Flow Tap | 1 define + `is_flow_tap_key()` override | override is mandatory |
|
||||
| **T2** | Layer Lock · Dynamic Tapping Term · WPM · Caps Word defines · `QK_BOOT`/`EE_CLR` | flags + keycodes | cheap |
|
||||
| **T2** | Encoder map | medium | **blocked on the OLED-vs-encoder pin decision** |
|
||||
|
|
@ -1,4 +1,15 @@
|
|||
# HANDS DOWN GOLD and COLEMAK MIRYOKU CUSTOM SETUP FOR CORNE
|
||||
# Corne v4.1 · QMK
|
||||
|
||||
The Corne implementation of my [Colemak + Hands Down Promethium
|
||||
layout](https://github.com/raphaelmor/keyboard-layout).
|
||||
|
||||
**The layout itself is documented there**, not here — the alphas, the six layers,
|
||||
the combos, the adaptive keys and the always-on behaviors are the same whichever
|
||||
board runs them. This file covers only what is true of this board and this
|
||||
firmware.
|
||||
|
||||
Everything below the marker is generated from the C sources by
|
||||
`tools/kle_docs.py`. Edit around it, not inside it.
|
||||
|
||||
## TODO
|
||||
|
||||
|
|
@ -27,3 +38,105 @@ SPRING : Colemak
|
|||
ORANGE : HD-p or g
|
||||
CHARTREUSE : Core Keys of the layer (arrows on NAV, numbers on NUM etc...)
|
||||
WHITE : Other Keys for the layer (home,end, copy cut on NAV, etc..)
|
||||
|
||||
<!-- layout:begin -->
|
||||
|
||||
<!-- Generated by qmk_userspace/users/raphaelmor/tools/kle_docs.py. Do not hand-edit this section; edit around it. -->
|
||||
|
||||
## The board
|
||||
|
||||
Corne v4.1, `LAYOUT_split_3x6_3_ex2` (`keymap.c`): 46 switches — 3x6 per
|
||||
half, three thumbs, plus the four ex2 inner keys `LTA LMA RTA RMA`.
|
||||
|
||||
Only 36 of them carry anything, so the pictures in the layout
|
||||
repo draw a 3x5 + 3. See *Not drawn* below for the rest.
|
||||
|
||||
## Not drawn
|
||||
|
||||
The board has 46 switches. These 10 are left off:
|
||||
|
||||
- Outer pinky column `LT5 LM5 LB5 RT5 RM5 RB5` — `KC_NO` on every layer.
|
||||
- Inner ex2 keys `LTA LMA RTA RMA`.
|
||||
|
||||
Careful: these hidden positions do have something bound.
|
||||
|
||||
- `CMK_LTA` is `LCTL_T(KC_A)`
|
||||
- `CMK_RTA` is `LCTL_T(KC_A)`
|
||||
- `CMK_LMA` is `LCTL_T(KC_A)`
|
||||
- `CMK_RMA` is `LCTL_T(KC_A)`
|
||||
|
||||
## Build flags and timings
|
||||
|
||||
All from `config.h`, read at generation time.
|
||||
|
||||
```
|
||||
define value effect
|
||||
-------------------------------------------------------------------------------
|
||||
RAMO_USE_HD_PROMETHIUM set Hands Down Promethium
|
||||
TAPPING_TERM 200 hold to get the mod, the layer, the bracket pair
|
||||
RAMO_ADAPTIVE_TERM 175 keydown to keydown, for an adaptive to fire
|
||||
```
|
||||
|
||||
## Where each feature lives
|
||||
|
||||
All paths are relative to `users/raphaelmor/`.
|
||||
|
||||
```
|
||||
feature file
|
||||
-------------------------------------------------------------------------------------------------
|
||||
layer list and order ramo_layers.h
|
||||
key and colour per position layers/{CMK,HDP,HDG,MED,NAV,MOS,SYM,NUM,FUN}-defs.h
|
||||
the keymap itself ../../keyboards/crkbd/rev4_1/standard/keymaps/raphaelmor/keymap.c
|
||||
combos ramo_combos.c
|
||||
adaptive keys ramo_adaptive.c
|
||||
linger brackets, Caps Word ramo_linger.c
|
||||
Shift+Bksp, comma-leader shift ramo_process_record.c
|
||||
base switch (tap dance) ramo_tap_dance.c
|
||||
per-key RGB ramo_ledmap.c
|
||||
```
|
||||
|
||||
## Regenerating the docs
|
||||
|
||||
```
|
||||
python3 tools/kle_docs.py # this file and the layout repo
|
||||
python3 tools/kle_export.py # corne-v4-1-ramo.json, for KLE
|
||||
```
|
||||
|
||||
`kle_export.py` runs the C preprocessor over `ramo_layers.h`, so it reads
|
||||
the same keymap the firmware builds. It aborts instead of writing a wrong
|
||||
file: an unknown keycode, a combo whose keys do not resolve, or a legend
|
||||
too long for a cap all stop the export.
|
||||
|
||||
To open it in [keyboard-layout-editor.com](http://www.keyboard-layout-editor.com),
|
||||
use the **Upload** button and pick `corne-v4-1-ramo.json`. Do not paste that
|
||||
file into the **Raw data** tab — that tab wraps whatever you type in `[ ]` of
|
||||
its own, so a whole `.json` file ends up double-wrapped and draws one empty
|
||||
key. Paste this instead:
|
||||
|
||||
```
|
||||
python3 tools/kle_export.py --raw -o /tmp/kle-raw.txt && pbcopy < /tmp/kle-raw.txt
|
||||
```
|
||||
|
||||
<!-- layout:end -->
|
||||
|
||||
## License
|
||||
|
||||
**GPL-3.0-or-later.** See [LICENSE](LICENSE).
|
||||
|
||||
This directory is not under the GPL-2.0 licence at the root of `qmk_userspace`,
|
||||
which covers the userspace template. The design here follows [Hands Down
|
||||
Promethium by moutis](https://github.com/moutis/HandsDown), whose QMK
|
||||
implementation is GPL-3.0. QMK itself is GPL-2.0-**or-later**, and that "or
|
||||
later" is what lets the two combine: the result is GPL-3.0.
|
||||
|
||||
A keyboard layout — which letter sits on which position — is a system rather than
|
||||
an expressive work, so no licence is owed for the arrangement itself. The
|
||||
attribution is given because it is deserved, not because it is required.
|
||||
|
||||
Layer structure follows [Miryoku by
|
||||
manna-harbour](https://github.com/manna-harbour/miryoku). That project publishes
|
||||
no licence, which is another reason nothing of its is copied here.
|
||||
|
||||
`tools/` is MIT, not GPL. Those scripts are original and share nothing with
|
||||
upstream, and they generate the [layout
|
||||
repo](https://github.com/raphaelmor/keyboard-layout), which is MIT.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,13 @@
|
|||
/*
|
||||
* config.h
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Copyright (c) 2026 Raphaël Mor
|
||||
*
|
||||
* Part of a QMK userspace whose design follows Hands Down Promethium by moutis
|
||||
* (https://github.com/moutis/HandsDown, GPL-3.0). See ./LICENSE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// default but used in macros
|
||||
|
|
@ -7,6 +17,12 @@
|
|||
// Enable rapid switch from tap to hold, disables double tap hold auto-repeat.
|
||||
#define QUICK_TAP_TERM 0
|
||||
|
||||
// Gap between register/unregister in tap_code(). At the default 0, back-to-back
|
||||
// taps in the adaptives (del-then-retype, e.g. MG -> LG) can land in the same USB
|
||||
// frame and go missing or out of sequence. 20 is moutis's value.
|
||||
#undef TAP_CODE_DELAY
|
||||
#define TAP_CODE_DELAY 20
|
||||
|
||||
// Mouse key speed and acceleration.
|
||||
#undef MOUSEKEY_DELAY
|
||||
#define MOUSEKEY_DELAY 0
|
||||
|
|
@ -24,8 +40,26 @@
|
|||
|
||||
// RAMO CUSTOM CONFIG
|
||||
#define RAMO_COMBO_HOLD (TAPPING_TERM) // time to hold to trigger delayed combo
|
||||
#define RAMO_LINGER_TERM (TAPPING_TERM) // hold time to auto-pair brackets
|
||||
#define RAMO_USE_HD_PROMETHIUM // (Gold is the default)
|
||||
#define RAMO_ADAPTIVE_TERM (TAPPING_TERM/4) // rolling threshold
|
||||
// Adaptive rolling window: how long after a keydown the next key can still trigger
|
||||
// an adaptive. Measured keydown-to-keydown (see prior_keydown_timer / event.time).
|
||||
// 50ms (TAPPING_TERM/4) = ~240 WPM — roll-only; missed most rules in practice
|
||||
// 100ms = ~120 WPM — fine for fast rolls (PD, WG), but SFB and
|
||||
// cross-row rules (KH same-finger, KG inner-column
|
||||
// stretch) are inherently slower and never fired
|
||||
// 175ms = ~69 WPM — current. Adaptives exist to fix SFBs and
|
||||
// scissors, which are *slow by definition*, so the
|
||||
// window has to clear those motions.
|
||||
// 229ms (moutis, COMBO_HOLD*1.35) = ~52 WPM
|
||||
// Collisions to watch at 175ms (same-row rolls in real words): GM in
|
||||
// "pragma"/"progmem", PD in "update", YB in "keyboard", KG in "background".
|
||||
// #define RAMO_ADAPTIVE_TERM (TAPPING_TERM/4) // original: 50ms
|
||||
// #define RAMO_ADAPTIVE_TERM 100 // too tight for SFB rules
|
||||
#define RAMO_ADAPTIVE_TERM 175
|
||||
|
||||
|
||||
#define RAMO_ADAPT_SHIFT_TERM (TAPPING_TERM) // comma-leader one-shot-shift window (wider than adaptives)
|
||||
|
||||
|
||||
// Disable led stuff
|
||||
|
|
|
|||
1604
users/raphaelmor/corne-v4-1-ramo.json
Normal file
1604
users/raphaelmor/corne-v4-1-ramo.json
Normal file
File diff suppressed because one or more lines are too long
358
users/raphaelmor/hd-colemak.html
Normal file
358
users/raphaelmor/hd-colemak.html
Normal file
|
|
@ -0,0 +1,358 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Corne layers — current · full HD · proposed hybrid</title>
|
||||
<style>
|
||||
:root{
|
||||
--bg:#0f1117; --card:#171a22; --key:#222634;
|
||||
--ink:#e7e9ee; --dim:#8b90a0; --line:#2c3142;
|
||||
--alpha:#e7e9ee; --mod:#f6c177; --layer:#9ccfd8; --sem:#c4a7e7;
|
||||
--sym:#7fd6a6; --media:#eb6f92; --rgb:#f28fad; --num:#a3be8c; --mouse:#e0af68;
|
||||
--new:#a6e3a1; --chg:#f6c177; --ling:#c4a7e7;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
body{margin:0;background:var(--bg);color:var(--ink);
|
||||
font:14px/1.4 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;padding:0 28px 40px}
|
||||
h1{font-size:22px;margin:16px 0 4px}
|
||||
.sub{color:var(--dim);margin:0 0 14px;max-width:980px}
|
||||
.controls{position:sticky;top:0;z-index:10;background:rgba(15,17,23,.94);backdrop-filter:blur(6px);
|
||||
border-bottom:1px solid var(--line);padding:14px 0;margin:0 0 18px;display:flex;gap:26px;flex-wrap:wrap;align-items:flex-end}
|
||||
.ctl{display:flex;flex-direction:column;gap:5px}
|
||||
.ctl label{font-size:11px;color:var(--dim);text-transform:uppercase;letter-spacing:.04em}
|
||||
.seg{display:inline-flex;border:1px solid var(--line);border-radius:8px;overflow:hidden}
|
||||
.seg button{background:var(--key);color:var(--dim);border:0;padding:6px 12px;font:inherit;font-size:12px;cursor:pointer}
|
||||
.seg button.on{background:var(--layer);color:#0f1117;font-weight:700}
|
||||
.seg.big button{padding:8px 14px;font-size:13px}
|
||||
.controls:not(.prop) .proponly{display:none}
|
||||
.callout{background:#1b2030;border:1px solid var(--line);border-left:3px solid var(--layer);
|
||||
border-radius:8px;padding:12px 16px;margin:0 0 20px;max-width:980px;font-size:13px}
|
||||
.callout b{color:var(--ink)} .callout code{background:#20222c;padding:1px 5px;border-radius:4px}
|
||||
.legend{display:flex;flex-wrap:wrap;gap:14px;margin:0 0 22px;font-size:12px}
|
||||
.legend span{display:inline-flex;align-items:center;gap:6px;color:var(--dim)}
|
||||
.legend i{width:12px;height:12px;border-radius:3px;display:inline-block}
|
||||
.layer{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:18px 20px 22px;margin:0 0 22px}
|
||||
.layer h2{margin:0 0 2px;font-size:17px;display:flex;align-items:baseline;gap:10px;flex-wrap:wrap}
|
||||
.layer h2 b{font-size:12px;font-weight:600;color:#0f1117;padding:2px 8px;border-radius:20px}
|
||||
.layer h2 .badge{font-size:11px;font-weight:700;color:#0f1117;background:var(--chg);padding:2px 8px;border-radius:20px}
|
||||
.layer .how{color:var(--dim);font-size:12px;margin:0 0 16px}
|
||||
.board{display:flex;gap:44px;flex-wrap:wrap;justify-content:center}
|
||||
.half{display:grid;grid-template-columns:repeat(6,minmax(0,58px));gap:6px}
|
||||
.key{background:var(--key);border:1px solid var(--line);border-radius:8px;height:52px;padding:4px 5px;
|
||||
position:relative;display:flex;flex-direction:column;justify-content:center;align-items:center;text-align:center;overflow:hidden}
|
||||
.key .m{font-size:13px;font-weight:600;line-height:1.15;word-break:break-word}
|
||||
.key .h{position:absolute;top:2px;right:4px;font-size:9px;color:var(--dim);font-weight:700}
|
||||
.key .tag{position:absolute;bottom:1px;left:3px;font-size:8px;font-weight:800;letter-spacing:.03em;display:none}
|
||||
.key.trns{background:transparent;border-style:dashed} .key.trns .m{color:var(--dim);font-weight:400;font-size:15px}
|
||||
.key.none{background:transparent;border-color:#20222c} .key.none .m{color:#3a3f52}
|
||||
.key.mod .m{color:var(--mod)} .key.layer .m{color:var(--layer)}
|
||||
.key.sem .m{color:var(--sem);font-size:11px} .key.sym .m{color:var(--sym)}
|
||||
.key.media .m{color:var(--media);font-size:11px} .key.rgb .m{color:var(--rgb);font-size:10px}
|
||||
.key.num .m{color:var(--num)} .key.mouse .m{color:var(--mouse);font-size:11px}
|
||||
body.diff .key.NEW{border-color:var(--new)} body.diff .key.NEW .tag{display:block;color:var(--new)}
|
||||
body.diff .key.CHG{border-color:var(--chg)} body.diff .key.CHG .tag{display:block;color:var(--chg)}
|
||||
body.diff .key.LING{border-color:var(--ling)} body.diff .key.LING .tag{display:block;color:var(--ling)}
|
||||
body.diff .key.REM{border-color:var(--media);opacity:.55} body.diff .key.REM .tag{display:block;color:var(--media)}
|
||||
.thumbs{grid-column:1/7;display:grid;grid-template-columns:repeat(6,minmax(0,58px));gap:6px;margin-top:4px}
|
||||
.thumbs .key{height:46px}
|
||||
.engine{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:14px;margin:6px 0 26px}
|
||||
.ecard{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:14px 16px}
|
||||
.ecard h3{margin:0 0 6px;font-size:14px} .ecard p{margin:0;color:var(--dim);font-size:12px}
|
||||
.ecard .where{color:var(--ling);font-size:11px;margin-top:8px}
|
||||
h2.section{font-size:15px;color:var(--dim);margin:26px 0 10px;border-top:1px solid var(--line);padding-top:18px}
|
||||
.foot{color:var(--dim);font-size:12px;margin-top:8px;max-width:980px}
|
||||
code{background:#20222c;padding:1px 5px;border-radius:4px}
|
||||
</style>
|
||||
</head>
|
||||
<body class="diff">
|
||||
<div class="controls prop" id="controls">
|
||||
<div class="ctl"><label>View</label>
|
||||
<div class="seg big" id="segView">
|
||||
<button data-v="current">My current</button>
|
||||
<button data-v="hd">Full HD (canonical)</button>
|
||||
<button data-v="proposed" class="on">Proposed hybrid</button>
|
||||
</div></div>
|
||||
<div class="ctl proponly"><label>Base alpha</label>
|
||||
<div class="seg" id="segBase"><button data-v="vanilla" class="on">Vanilla Colemak</button><button data-v="dh">Colemak-DH</button></div></div>
|
||||
<div class="ctl proponly"><label>SYM layout</label>
|
||||
<div class="seg" id="segSym"><button data-v="current" class="on">Current (mirror)</button><button data-v="v2">Programming v2</button></div></div>
|
||||
<div class="ctl proponly"><label>Diff markers</label>
|
||||
<div class="seg" id="segDiff"><button data-v="on" class="on">Show</button><button data-v="off">Hide</button></div></div>
|
||||
</div>
|
||||
|
||||
<h1 id="title">Proposed hybrid — Miryoku chassis + HD engine</h1>
|
||||
<p class="sub" id="viewnote"></p>
|
||||
|
||||
<div class="legend">
|
||||
<span><i style="background:var(--alpha)"></i>alpha</span>
|
||||
<span><i style="background:var(--mod)"></i>mod (hold)</span>
|
||||
<span><i style="background:var(--layer)"></i>layer / switch</span>
|
||||
<span><i style="background:var(--sem)"></i>semantic / editing</span>
|
||||
<span><i style="background:var(--sym)"></i>symbol</span>
|
||||
<span><i style="background:var(--num)"></i>number</span>
|
||||
<span><i style="background:var(--mouse)"></i>mouse</span>
|
||||
<span><i style="background:var(--media)"></i>media</span>
|
||||
<span><i style="background:var(--rgb)"></i>RGB/cfg</span>
|
||||
<span class="proponly"><i style="border:1px solid var(--new)"></i>NEW</span>
|
||||
<span class="proponly"><i style="border:1px solid var(--chg)"></i>CHG</span>
|
||||
<span class="proponly"><i style="border:1px solid var(--ling)"></i>+hold</span>
|
||||
<span class="proponly"><i style="border:1px solid var(--media)"></i>removed</span>
|
||||
</div>
|
||||
|
||||
<div id="app"></div>
|
||||
|
||||
<div id="enginewrap">
|
||||
<h2 class="section">Engine features — code, not layout (✅ implemented)</h2>
|
||||
<div class="engine">
|
||||
<div class="ecard"><h3>✅ Adaptives — Hands Down base only</h3><p>Full PM set (~20 rules): PF→PS, KG→KL, KH→KN, GJ→"th", MK→LK, GV→GT, AE→AU… Gated so they run only on the HD base.</p><p class="where">ramo_adaptive.c (implemented)</p></div>
|
||||
<div class="ecard"><h3>✅ Combos</h3><p>qu (P+L), z (N+H), and now th (T+H) live. More digraphs/symbols easy to add.</p><p class="where">ramo_combos.c (implemented)</p></div>
|
||||
<div class="ecard"><h3>✅ Linger brackets — SYM</h3><p>Hold <code>(</code> or <code>{</code> → auto-pair, caret between: <code>(|)</code>. Tap = single. (<code>[</code> and <code>"</code> not yet — need a SYM home.)</p><p class="where">ramo_linger.c · RAMO_LINGER_TERM</p></div>
|
||||
<div class="ecard"><h3>✅ Smart caps-word</h3><p>Shifts letters, keeps digits/<code>-</code>/<code>_</code>, turns space→<code>_</code>, auto-exits on punctuation.</p><p class="where">ramo_linger.c (implemented)</p></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="foot" id="foot"></p>
|
||||
|
||||
<script>
|
||||
let state = {view:"proposed", base:"vanilla", sym:"current", diff:"on"};
|
||||
|
||||
/* ============ shared static layers (identical across current & proposed) ============ */
|
||||
const CMK_THUMBS=[["LT(L_MEDIA,KC_ESC)","LT(L_NAV,KC_SPC)","LT(L_MOUSE,KC_TAB)"],["LT(L_SYM,KC_ENT)","LT(L_NUM,KC_BSPC)","LT(L_FUN,KC_DEL)"]];
|
||||
const HDP_LAYER={purpose:"Alt base — Hands Down Promethium (canonical arrangement)",accent:"#e0af68",how:"moutis layout · Hyper/Meh kept on fixed top positions · GACS mods · adaptives + qu/z/th + accent/symbol combos · Option-A thumbs",
|
||||
top:[["KC_NO","KC_V","KC_W","HYPR_T(KC_G)","MEH_T(KC_M)","KC_J"],["KC_HASH","MEH_T(KC_DOT)","HYPR_T(KC_SLSH)","KC_DQUO","KC_QUOT","KC_NO"]],
|
||||
home:[["KC_NO","LCTL_T(KC_S)","LALT_T(KC_N)","LGUI_T(KC_T)","LSFT_T(KC_H)","KC_K"],["KC_COMM","RSFT_T(KC_A)","RGUI_T(KC_E)","RALT_T(KC_I)","RCTL_T(KC_C)","KC_NO"]],
|
||||
bot:[["KC_NO","KC_F","KC_P","KC_D","KC_L","KC_X"],["KC_MINS","KC_U","KC_O","KC_Y","KC_B","KC_NO"]],
|
||||
thumbs:[["LT(L_MEDIA,KC_ESC)","LT(L_NAV,KC_SPC)","LT(L_MOUSE,KC_TAB)"],["LT(L_SYM,KC_R)","LT(L_NUM,KC_BSPC)","LT(L_FUN,KC_ENT)"]]};
|
||||
const MEDIA_LAYER={purpose:"Media & RGB",accent:"#eb6f92",how:"hold Esc",
|
||||
top:[["KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO"],["RM_TOGG","RM_NEXT","RM_HUEU","RM_SATU","RM_VALU","KC_NO"]],
|
||||
home:[["KC_NO","KC_LCTL","KC_LALT","KC_LGUI","KC_LSFT","KC_NO"],["KC_NO","KC_MPRV","KC_VOLD","KC_VOLU","KC_MNXT","KC_NO"]],
|
||||
bot:[["KC_NO","KC_NO","KC_NO","KC_ALGR","KC_NO","KC_NO"],["KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO"]],
|
||||
thumbs:[["KC_NO","KC_NO","KC_NO"],["KC_MSTP","KC_MPLY","KC_MUTE"]]};
|
||||
const MOUSE_LAYER={purpose:"Mouse move / wheel / buttons",accent:"#e0af68",how:"hold Tab (CMK) / R (HD)",
|
||||
top:[["KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO"],["SCMD(KC_Z)","LCMD(KC_V)","LCMD(KC_C)","LCMD(KC_X)","LCMD(KC_Z)","KC_NO"]],
|
||||
home:[["KC_NO","KC_LCTL","KC_LALT","KC_LGUI","KC_LSFT","KC_NO"],["KC_NO","MS_LEFT","MS_DOWN","MS_UP","MS_RGHT","KC_NO"]],
|
||||
bot:[["KC_NO","KC_NO","KC_NO","KC_ALGR","KC_NO","KC_NO"],["KC_NO","MS_WHLL","MS_WHLD","MS_WHLU","MS_WHLR","KC_NO"]],
|
||||
thumbs:[["KC_NO","KC_NO","KC_NO"],["MS_BTN2","MS_BTN1","MS_BTN3"]]};
|
||||
const NUM_LAYER={purpose:"Numbers & math",accent:"#a3be8c",how:"hold Bksp (CMK) / Enter (HD)",
|
||||
top:[["KC_NO","KC_LBRC","KC_7","KC_8","KC_9","KC_RBRC"],["KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO"]],
|
||||
home:[["KC_NO","KC_SCLN","KC_4","KC_5","KC_6","KC_EQL"],["KC_NO","KC_RSFT","KC_RGUI","KC_RALT","KC_RCTL","KC_NO"]],
|
||||
bot:[["KC_NO","KC_GRV","KC_1","KC_2","KC_3","KC_BSLS"],["KC_NO","KC_NO","KC_NO","KC_ALGR","KC_NO","KC_NO"]],
|
||||
thumbs:[["KC_DOT","KC_0","KC_MINS"],["KC_NO","KC_NO","KC_NO"]]};
|
||||
const FUN_LAYER={purpose:"Function keys & system",accent:"#8b90a0",how:"hold Del (CMK) / Bksp (HD)",
|
||||
top:[["KC_NO","KC_F12","KC_F7","KC_F8","KC_F9","KC_PSCR"],["KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO"]],
|
||||
home:[["KC_NO","KC_F11","KC_F4","KC_F5","KC_F6","KC_SCRL"],["KC_NO","KC_RSFT","KC_RGUI","KC_RALT","KC_RCTL","KC_NO"]],
|
||||
bot:[["KC_NO","KC_F10","KC_F1","KC_F2","KC_F3","KC_PAUS"],["KC_NO","KC_NO","KC_NO","KC_ALGR","KC_NO","KC_NO"]],
|
||||
thumbs:[["KC_APP","KC_SPC","KC_TAB"],["KC_NO","KC_NO","KC_NO"]]};
|
||||
|
||||
/* ============ VIEW 1: my current firmware (live code) ============ */
|
||||
const CMK_V_TOP=[["KC_NO","KC_Q","KC_W","HYPR_T(KC_F)","MEH_T(KC_P)","KC_G"],["KC_J","MEH_T(KC_L)","HYPR_T(KC_U)","KC_Y","KC_QUOT","KC_NO"]];
|
||||
const CMK_V_HOME=[["KC_NO","LCTL_T(KC_A)","LALT_T(KC_R)","LGUI_T(KC_S)","LSFT_T(KC_T)","KC_D"],["KC_H","RSFT_T(KC_N)","RGUI_T(KC_E)","RALT_T(KC_I)","RCTL_T(KC_O)","KC_NO"]];
|
||||
const CMK_V_BOT=[["KC_NO","KC_Z","KC_X","KC_C","KC_V","KC_B"],["KC_K","KC_M","KC_COMM","KC_DOT","KC_SLSH","KC_NO"]];
|
||||
const CUR_NAV={purpose:"Navigation & base-switch",accent:"#9ccfd8",how:"hold Space (CMK) / Tab (HD)",
|
||||
top:[["KC_NO","KC_NO","KC_NO","TD(RAMO_TD_CMK)","TD(RAMO_TD_HD)","KC_NO"],["LCMD(KC_Z)","LCMD(KC_V)","LCMD(KC_C)","LCMD(KC_X)","SCMD(KC_Z)","KC_NO"]],
|
||||
home:[["KC_NO","KC_LCTL","KC_LALT","KC_LGUI","KC_LSFT","KC_NO"],["CW_TOGG","KC_LEFT","KC_DOWN","KC_UP","KC_RGHT","KC_NO"]],
|
||||
bot:[["KC_NO","KC_NO","KC_NO","KC_ALGR","KC_NO","KC_NO"],["KC_INS","KC_HOME","KC_PGDN","KC_PGUP","KC_END","KC_NO"]],
|
||||
thumbs:[["KC_NO","KC_NO","KC_NO"],["KC_ENT","KC_BSPC","KC_DEL"]]};
|
||||
const CUR_SYM={purpose:"Symbols",accent:"#7fd6a6",how:"hold Enter (CMK) / Space (HD)",
|
||||
top:[["KC_NO","KC_LCBR","KC_AMPR","KC_ASTR","KC_LPRN","KC_RCBR"],["KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO"]],
|
||||
home:[["KC_NO","KC_COLN","KC_DLR","KC_PERC","KC_CIRC","KC_PLUS"],["KC_NO","KC_RSFT","KC_RGUI","KC_RALT","KC_RCTL","KC_NO"]],
|
||||
bot:[["KC_NO","KC_TILD","KC_EXLM","KC_AT","KC_HASH","KC_PIPE"],["KC_NO","KC_NO","KC_NO","KC_ALGR","KC_NO","KC_NO"]],
|
||||
thumbs:[["KC_LPRN","KC_RPRN","KC_UNDS"],["KC_NO","KC_NO","KC_NO"]]};
|
||||
function CURRENT(){return [
|
||||
["COLEMAK",{purpose:"Base — vanilla Colemak",accent:"#7fd6a6",how:"GACS home-row mods · thumbs open the 6 layers",top:CMK_V_TOP,home:CMK_V_HOME,bot:CMK_V_BOT,thumbs:CMK_THUMBS}],
|
||||
["HANDSDOWN",HDP_LAYER],["MEDIA",MEDIA_LAYER],["NAV",CUR_NAV],["MOUSE",MOUSE_LAYER],["SYM",CUR_SYM],["NUM",NUM_LAYER],["FUN",FUN_LAYER],
|
||||
];}
|
||||
|
||||
/* ============ VIEW 2: full canonical HD (from HDPM-layers.html) ============ */
|
||||
function HD(){return [
|
||||
["HD",{purpose:"Base alpha — Hands Down Promethium (canonical)",accent:"#e0af68",how:"letters + HRM + thumb layer-taps",
|
||||
top:[["KC_LNG1","KC_V","KC_W","KC_G","KC_M","KC_J"],["KC_HASH","KC_DOT","KC_SLSH","KC_DQUO","KC_QUOT","KC_LNG2"]],
|
||||
home:[["KC_ESC","RCTL_T(KC_S)","RALT_T(KC_N)","RGUI_T(KC_T)","RSFT_T(KC_H)","LT(L_NUM,KC_K)"],["KC_COMM","RSFT_T(KC_A)","RGUI_T(KC_E)","RALT_T(KC_I)","RCTL_T(KC_C)","KC_Z"]],
|
||||
bot:[["KC_TAB","LT(L_FUN,KC_F)","KC_P","KC_D","KC_L","KC_X"],["KC_MINS","KC_U","KC_O","KC_Y","KC_B","KC_Q"]],
|
||||
thumbs:[["LT(L_FUN,KC_BSPC)","LT(L_NAV,KC_R)","LT(L_CFG,KC_MUTE)"],["LT(L_CFG,KC_MPLY)","LT(L_SYM,KC_SPC)","LT(L_NAV,KC_ENT)"]]}],
|
||||
["SYM",{purpose:"Symbols & punctuation",accent:"#7fd6a6",how:"hold Space",
|
||||
top:[["KC_TRNS","KC_Z","KC_Q","SK_PARA","SK_SCRS","SK_DCRS"],["SK_CENT","KC_DLR","SK_EURO","SK_BPND","SK_JPY","KC_TRNS"]],
|
||||
home:[["KC_TRNS","SK_SECT","KC_LBRC","KC_LPRN","KC_LCBR","SK_BBLT"],["KC_PSLS","RSFT_T(KC_PEQL)","RGUI_T(KC_PAST)","RALT_T(KC_PPLS)","RCTL_T(KC_PMNS)","KC_TRNS"]],
|
||||
bot:[["KC_TRNS","SK_DEGR","KC_RBRC","KC_RPRN","KC_RCBR","SK_SBLT"],["KC_CIRC","KC_PIPE","KC_AMPR","KC_LT","KC_GT","KC_TRNS"]],
|
||||
thumbs:[["SK_DELWDL","SK_DELWDR","KC_VOLD"],["KC_VOLU","KC_UNDS","SK_IEXC"]]}],
|
||||
["FUN",{purpose:"F-keys (left) · number row (right)",accent:"#8b90a0",how:"hold Bksp or F",
|
||||
top:[["RGB_TOG","KC_F13","KC_F7","KC_F8","KC_F9","KC_F10"],["KC_SLSH","KC_7","KC_8","KC_9","KC_MINS","RGB_MOD"]],
|
||||
home:[["RGB_VAI","LCTL_T(KC_F14)","LALT_T(KC_F1)","LGUI_T(KC_F2)","LSFT_T(KC_F3)","KC_F11"],["KC_ASTR","RSFT_T(KC_1)","RGUI_T(KC_2)","RALT_T(KC_3)","RCTL_T(KC_PPLS)","RGB_HUI"]],
|
||||
bot:[["RGB_VAD","KC_F15","KC_F4","KC_F5","KC_F3","KC_F12"],["KC_COMM","KC_4","KC_5","KC_6","KC_EQL","RGB_HUD"]],
|
||||
thumbs:[["KC_SCLN","TG(L_NUM)","KC_INS"],["KC_TRNS","KC_0","KC_DOT"]]}],
|
||||
["NUM",{purpose:"Nav (left) · numpad (right)",accent:"#a3be8c",how:"hold K → right numpad; toggle for two-handed",
|
||||
top:[["KC_TRNS","SK_DOCBEG","KC_HOME","KC_UP","KC_PGUP","SK_PARAPRV"],["KC_PSLS","KC_P7","KC_P8","KC_P9","KC_PMNS","KC_TRNS"]],
|
||||
home:[["KC_TRNS","SK_WORDPRV","KC_LEFT","KC_DOWN","KC_RGHT","SK_WORDNXT"],["KC_PAST","RSFT_T(KC_P4)","RGUI_T(KC_P5)","RALT_T(KC_P6)","RCTL_T(KC_PPLS)","KC_TRNS"]],
|
||||
bot:[["KC_TRNS","SK_DOCEND","KC_END","KC_SPC","KC_PGDN","SK_PARANXT"],["KC_PCMM","KC_P1","KC_P2","KC_P3","KC_PEQL","KC_TRNS"]],
|
||||
thumbs:[["LGUI_T(KC_BSPC)","LSFT_T(KC_ENT)","SK_CLOZ"],["KC_C","KC_P0","KC_PDOT"]]}],
|
||||
["NAV",{purpose:"Edit & mods (left) · arrows/nav (right)",accent:"#9ccfd8",how:"hold R or Enter",
|
||||
top:[["KC_TRNS","KC_TAB","SK_CLOZ","SK_SCAP","SK_SCLP","SK_PSTM"],["SK_DOCBEG","KC_HOME","KC_UP","KC_PGUP","SK_PARAPRV","KC_TRNS"]],
|
||||
home:[["KC_VOLU","KC_LCTL","KC_LALT","KC_LGUI","KC_LSFT","SK_FIND"],["SK_WORDPRV","KC_LEFT","KC_DOWN","KC_RGHT","SK_WORDNXT","KC_TRNS"]],
|
||||
bot:[["KC_VOLD","SK_UNDO","SK_CUT","SK_COPY","SK_PSTE","SK_FAGN"],["SK_DOCEND","KC_END","KC_DOWN","KC_PGDN","SK_PARANXT","KC_TRNS"]],
|
||||
thumbs:[["KC_BSPC","KC_ENTER","SK_ZOOMRST"],["SK_ZOOMRST","SK_HISTPRV","SK_HISTNXT"]]}],
|
||||
["CFG",{purpose:"Keyboard config (left) · media (right)",accent:"#f28fad",how:"hold Mute/Play, or CFG combo",
|
||||
top:[["KC_NO","SK_Lux","SK_Win","SK_Mac","QK_RBT","SK_KILL"],["SK_ZOOMRST","KC_NUM","KC_EJCT","KC_LALT","LCA(KC_DEL)","RGB_MOD"]],
|
||||
home:[["RGB_VAI","HD_AdaptKeyToggle","HD_L_QWERTY","HD_L_ALPHA","KC_VOLU","KC_BRIU"],["SK_ZOOMOUT","KC_MPRV","KC_MUTE","KC_MNXT","EE_CLR","RGB_SAI"]],
|
||||
bot:[["RGB_HUI","QK_BOOT","AG_SWAP","AG_NORM","KC_VOLD","KC_BRID"],["SK_ZOOMIN","KC_MRWD","KC_MPLY","KC_MFFD","QK_BOOT","RGB_HUD"]],
|
||||
thumbs:[["SK_ZOOMOUT","SK_ZOOMIN","SK_ZOOMRST"],["SK_ZOOMRST","SK_ZOOMIN","SK_ZOOMOUT"]]}],
|
||||
];}
|
||||
|
||||
/* ============ VIEW 3: proposed hybrid ============ */
|
||||
const CMK_DH_TOP=[["KC_NO","KC_Q","KC_W","HYPR_T(KC_F)","MEH_T(KC_P)",["KC_B","CHG"]],["KC_J","MEH_T(KC_L)","HYPR_T(KC_U)","KC_Y","KC_QUOT","KC_NO"]];
|
||||
const CMK_DH_HOME=[["KC_NO","LCTL_T(KC_A)","LALT_T(KC_R)","LGUI_T(KC_S)","LSFT_T(KC_T)",["KC_G","CHG"]],[["KC_M","CHG"],"RSFT_T(KC_N)","RGUI_T(KC_E)","RALT_T(KC_I)","RCTL_T(KC_O)","KC_NO"]];
|
||||
const CMK_DH_BOT=[["KC_NO","KC_Z","KC_X","KC_C",["KC_D","CHG"],["KC_V","CHG"]],["KC_K",["KC_H","CHG"],"KC_COMM","KC_DOT","KC_SLSH","KC_NO"]];
|
||||
// NAV stays unchanged in the trimmed proposal (Mac-only, arrows only) — uses CUR_NAV.
|
||||
const PROP_SYM={purpose:"Symbols + linger brackets",accent:"#7fd6a6",how:"hold Enter (CMK) / Space (HD)",
|
||||
top:[["KC_NO",["KC_LCBR","LING"],"KC_AMPR","KC_ASTR",["KC_LPRN","LING"],"KC_RCBR"],["KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO"]],
|
||||
home:[["KC_NO","KC_COLN","KC_DLR","KC_PERC","KC_CIRC","KC_PLUS"],["KC_NO","KC_RSFT","KC_RGUI","KC_RALT","KC_RCTL","KC_NO"]],
|
||||
bot:[["KC_NO","KC_TILD","KC_EXLM","KC_AT","KC_HASH","KC_PIPE"],["KC_NO","KC_NO","KC_NO","KC_ALGR","KC_NO","KC_NO"]],
|
||||
thumbs:[[["KC_LPRN","LING"],"KC_RPRN","KC_UNDS"],["KC_NO","KC_NO","KC_NO"]]};
|
||||
// SYM v2 — programming layer for Swift / Odin / Objective-C. Two-handed (mods dropped).
|
||||
// Left = operators; Right = delimiters + quotes (openers under index, closers under middle).
|
||||
// Openers (( { [ <) linger-pair on hold. High-freq on home row; digraph rolls: -> :: := == != <>.
|
||||
const SYM_V2={purpose:"Programming symbols v2 (Swift · Odin · ObjC) — two-handed",accent:"#7fd6a6",
|
||||
how:"hold Enter (CMK) / Space (HD) · openers ( { [ < linger-pair · optimized for -> :: := == !=",
|
||||
top:[["KC_NO","KC_NO","KC_TILD","KC_AMPR","KC_PIPE","KC_CIRC"],["KC_GT",["KC_LBRC","LING"],"KC_RBRC",["KC_LT","LING"],"KC_GRV","KC_NO"]],
|
||||
home:[["KC_NO","KC_EXLM","KC_ASTR","KC_PLUS","KC_MINS","KC_PERC"],["KC_EQL",["KC_LPRN","LING"],"KC_RPRN","KC_COLN","KC_DQUO","KC_NO"]],
|
||||
bot:[["KC_NO","KC_AT","KC_HASH","KC_SLSH","KC_QUES","KC_DLR"],["KC_BSLS",["KC_LCBR","LING"],"KC_RCBR","KC_SCLN","KC_QUOT","KC_NO"]],
|
||||
thumbs:[["KC_NO","KC_NO","KC_UNDS"],["KC_NO","KC_NO","KC_NO"]]};
|
||||
function PROPOSED(){
|
||||
const cmk = state.base==="dh"
|
||||
? {top:CMK_DH_TOP,home:CMK_DH_HOME,bot:CMK_DH_BOT}
|
||||
: {top:CMK_V_TOP,home:CMK_V_HOME,bot:CMK_V_BOT};
|
||||
// Hands Down base is already live/implemented (canonical arrangement, blanked
|
||||
// outer columns, Option-A thumbs) — show it as-is with an "implemented" badge.
|
||||
const hdp = Object.assign({},HDP_LAYER,{badge:"✅ implemented"});
|
||||
return [
|
||||
["COLEMAK",{purpose:"Base — "+(state.base==="dh"?"Colemak-DH (6 keys moved)":"vanilla Colemak"),accent:"#7fd6a6",
|
||||
how:"GACS home-row mods"+(state.base==="dh"?" · DH moves B/G/D/H/V/M positions only":"")+" · unchanged from today",top:cmk.top,home:cmk.home,bot:cmk.bot,thumbs:CMK_THUMBS}],
|
||||
["HANDSDOWN",hdp],["MEDIA",MEDIA_LAYER],
|
||||
["NAV",Object.assign({},CUR_NAV,{purpose:"Navigation & base-switch — unchanged",how:"hold Space (CMK) / Tab (HD) · same as today (you only use arrows)"})],
|
||||
["MOUSE",MOUSE_LAYER],["SYM", state.sym==="v2"?SYM_V2:PROP_SYM],["NUM",NUM_LAYER],["FUN",FUN_LAYER],
|
||||
];
|
||||
}
|
||||
|
||||
/* ============ labels ============ */
|
||||
const KC={SPC:"Space",BSPC:"⌫",ENT:"⏎",ENTER:"⏎",TAB:"⇥",ESC:"Esc",DEL:"⌦",INS:"Ins",APP:"▤ Menu",
|
||||
HOME:"Home",END:"End",PGUP:"PgUp",PGDN:"PgDn",UP:"↑",DOWN:"↓",LEFT:"←",RGHT:"→",RIGHT:"→",
|
||||
MUTE:"🔇",VOLU:"Vol+",VOLD:"Vol−",MPLY:"⏯",MPRV:"⏮",MNXT:"⏭",MSTP:"⏹",MRWD:"⏪",MFFD:"⏩",
|
||||
PSCR:"PrtSc",SCRL:"ScrLk",PAUS:"Pause",ALGR:"AltGr",EJCT:"⏏",NUM:"NumLk",BRIU:"☀+",BRID:"☀−",
|
||||
LNG1:"英/かな",LNG2:"かな",T:"T",C:"C",
|
||||
DLR:"$",HASH:"#",SLSH:"/",DQUO:'"',QUOT:"'",COMM:",",DOT:".",MINS:"−",UNDS:"_",EQL:"=",
|
||||
CIRC:"^",PIPE:"|",AMPR:"&",LT:"<",GT:">",LBRC:"[",RBRC:"]",LPRN:"(",RPRN:")",LCBR:"{",RCBR:"}",
|
||||
ASTR:"*",PLUS:"+",SCLN:";",COLN:":",TILD:"~",GRV:"`",BSLS:"\\",EXLM:"!",QUES:"?",AT:"@",PERC:"%",
|
||||
PSLS:"/",PAST:"*",PMNS:"−",PPLS:"+",PEQL:"=",PDOT:".",PCMM:",",
|
||||
P0:"0",P1:"1",P2:"2",P3:"3",P4:"4",P5:"5",P6:"6",P7:"7",P8:"8",P9:"9",
|
||||
LCTL:"⌃",LALT:"⌥",LGUI:"⌘",LSFT:"⇧",RCTL:"⌃",RALT:"⌥",RGUI:"⌘",RSFT:"⇧",
|
||||
F1:"F1",F2:"F2",F3:"F3",F4:"F4",F5:"F5",F6:"F6",F7:"F7",F8:"F8",F9:"F9",F10:"F10",F11:"F11",F12:"F12",F13:"F13",F14:"F14",F15:"F15",
|
||||
"1":"1","2":"2","3":"3","4":"4","5":"5","6":"6","7":"7","8":"8","9":"9","0":"0"};
|
||||
const SK={UNDO:"Undo",REDO:"Redo",CUT:"Cut",COPY:"Copy",PSTE:"Paste",PSTM:"Paste=",SALL:"Sel All",SWRD:"Sel Wd",
|
||||
FIND:"Find",FAGN:"Find→",NEW:"New",OPEN:"Open",CLOZ:"Close",QUIT:"Quit",
|
||||
DOCBEG:"Doc Top",DOCEND:"Doc End",WORDPRV:"Word◀",WORDNXT:"Word▶",LINEBEG:"Ln Beg",LINEEND:"Ln End",
|
||||
PARAPRV:"¶ ◀",PARANXT:"¶ ▶",HISTPRV:"◀ Back",HISTNXT:"Fwd ▶",ZOOMIN:"Zoom+",ZOOMOUT:"Zoom−",ZOOMRST:"Zoom·0",
|
||||
PARA:"¶",SCRS:"†",DCRS:"‡",CENT:"¢",EURO:"€",BPND:"£",JPY:"¥",SECT:"§",IEXC:"¡",IQUE:"¿",BBLT:"•",SBLT:"·",DEGR:"°",
|
||||
DELWDL:"Del Wd◀",DELWDR:"Del Wd▶",KILL:"Force-Quit",SCAP:"Shot▸file",SCLP:"Shot▸clip",SRCH:"Search",HENT:"Hard⏎",
|
||||
Mac:"OS·Mac",Win:"OS·Win",Lux:"OS·Linux",MHEN:"英数",HENK:"かな",DKT8:"Dictate",AIVC:"AI/Siri"};
|
||||
const MODSYM={SFT:"⇧",CTL:"⌃",ALT:"⌥",GUI:"⌘",HYPR:"✦",MEH:"◆"};
|
||||
const MS={LEFT:"Ms ←",DOWN:"Ms ↓",UP:"Ms ↑",RGHT:"Ms →",WHLL:"Wh ←",WHLD:"Wh ↓",WHLU:"Wh ↑",WHLR:"Wh →",BTN1:"Btn L",BTN2:"Btn R",BTN3:"Btn M"};
|
||||
const RM={TOGG:"RGB ⏻",NEXT:"RGB ▸",HUEU:"hue+",SATU:"sat+",VALU:"bright+"};
|
||||
const RGBOLD={TOG:"RGB ⏻",MOD:"RGB ▸",VAI:"RGB ☀+",VAD:"RGB ☀−",HUI:"hue+",HUD:"hue−",SAI:"sat+",SAD:"sat−"};
|
||||
const LAYERNAME={L_MEDIA:"MEDIA",L_NAV:"NAV",L_MOUSE:"MOUSE",L_SYM:"SYM",L_NUM:"NUM",L_FUN:"FUN",L_CFG:"CFG",L_HD:"HD"};
|
||||
const EXACT={
|
||||
"TD(RAMO_TD_CMK)":{m:"⇄ Colemak",h:"2×tap",cls:"layer"}, "TD(RAMO_TD_HD)":{m:"⇄ HandsDn",h:"2×tap",cls:"layer"},
|
||||
"LCMD(KC_Z)":{m:"Undo",h:"⌘Z",cls:"sem"}, "LCMD(KC_V)":{m:"Paste",h:"⌘V",cls:"sem"},
|
||||
"LCMD(KC_C)":{m:"Copy",h:"⌘C",cls:"sem"}, "LCMD(KC_X)":{m:"Cut",h:"⌘X",cls:"sem"},
|
||||
"SCMD(KC_Z)":{m:"Redo",h:"⇧⌘Z",cls:"sem"}, "CW_TOGG":{m:"Caps Word",cls:"sem"},
|
||||
"HD_AdaptKeyToggle":{m:"Adaptive",h:"on/off",cls:"layer"}, "HD_L_QWERTY":{m:"▸QWERTY",cls:"layer"}, "HD_L_ALPHA":{m:"▸HD",cls:"layer"},
|
||||
"QK_BOOT":{m:"Bootloader",cls:"rgb"}, "QK_RBT":{m:"Reboot",cls:"rgb"}, "EE_CLR":{m:"Clr EEPROM",cls:"rgb"},
|
||||
"AG_SWAP":{m:"⌥/⌘ swap",cls:"rgb"}, "AG_NORM":{m:"⌥/⌘ norm",cls:"rgb"}, "LCA(KC_DEL)":{m:"⌃⌥⌦",cls:"sym"},
|
||||
"KC_NO":{m:"∅",cls:"none"}, "KC_TRNS":{m:"▽",cls:"trns"} };
|
||||
|
||||
function fmt(kc){
|
||||
if(EXACT[kc]){const e=EXACT[kc];return {m:e.m,h:e.h||"",cls:e.cls||"base"};}
|
||||
let mt=kc.match(/^[LR](SFT|CTL|ALT|GUI|HYPR|MEH)_T\((.+)\)$/);
|
||||
if(mt){return {m:fmt(mt[2]).m,h:MODSYM[mt[1]],cls:"mod"};}
|
||||
let lt=kc.match(/^LT\((\w+),\s*(.+)\)$/);
|
||||
if(lt){return {m:fmt(lt[2]).m,h:"▸"+(LAYERNAME[lt[1]]||lt[1].replace("L_","")),cls:"layer"};}
|
||||
let mo=kc.match(/^(MO|TG|TO|TT|DF)\((\w+)\)$/);
|
||||
if(mo){return {m:"▸"+(LAYERNAME[mo[2]]||mo[2].replace("L_","")),h:mo[1].toLowerCase(),cls:"layer"};}
|
||||
let sk=kc.match(/^SK_(\w+)$/); if(sk){return {m:SK[sk[1]]||sk[1],h:"",cls:"sem"};}
|
||||
let ms=kc.match(/^MS_(\w+)$/); if(ms){return {m:MS[ms[1]]||ms[1],h:"",cls:"mouse"};}
|
||||
let rm=kc.match(/^RM_(\w+)$/); if(rm){return {m:RM[rm[1]]||("RGB "+rm[1]),h:"",cls:"rgb"};}
|
||||
let rg=kc.match(/^RGB_(\w+)$/); if(rg){return {m:RGBOLD[rg[1]]||("RGB "+rg[1]),h:"",cls:"rgb"};}
|
||||
let k=kc.match(/^KC_(\w+)$/);
|
||||
if(k){let m=KC[k[1]]!==undefined?KC[k[1]]:k[1];return {m:m,h:"",cls:classify(kc,m)};}
|
||||
return {m:kc,h:"",cls:"base"};
|
||||
}
|
||||
function classify(kc){
|
||||
if(/VOL|MPLY|MPRV|MNXT|MSTP|MRWD|MFFD|MUTE/.test(kc))return "media";
|
||||
if(/KC_(DLR|HASH|SLSH|DQUO|QUOT|COMM|DOT|MINS|UNDS|EQL|CIRC|PIPE|AMPR|LT|GT|LBRC|RBRC|LPRN|RPRN|LCBR|RCBR|ASTR|PLUS|SCLN|COLN|TILD|GRV|BSLS|EXLM|QUES|AT|PERC|PSLS|PAST|PMNS|PPLS|PEQL|PDOT|PCMM)$/.test(kc))return "sym";
|
||||
if(/KC_(P?\d|F\d)/.test(kc))return "num";
|
||||
if(/KC_[LR](CTL|ALT|GUI|SFT)$/.test(kc))return "mod";
|
||||
return "base";
|
||||
}
|
||||
function keyEl(cell){
|
||||
let kc=cell,diff="";
|
||||
if(Array.isArray(cell)){kc=cell[0];diff=cell[1]||"";}
|
||||
const f=fmt(kc);
|
||||
const d=document.createElement("div");
|
||||
d.className="key "+(f.cls||"base")+(diff?(" "+diff):"");
|
||||
d.title=kc+(diff?(" ["+diff+"]"):"");
|
||||
const tag = diff==="LING"?"+hold":diff==="REM"?"remove":diff;
|
||||
d.innerHTML=(f.h?`<span class="h">${f.h}</span>`:"")+`<span class="m">${f.m}</span>`+(diff?`<span class="tag">${tag}</span>`:"");
|
||||
return d;
|
||||
}
|
||||
function half(rows,thumbs,side){
|
||||
const h=document.createElement("div");h.className="half";
|
||||
rows.forEach(r=>r.forEach(c=>h.appendChild(keyEl(c))));
|
||||
const t=document.createElement("div");t.className="thumbs";
|
||||
const cells = side==="L" ? ["","","",...thumbs] : [...thumbs,"","",""];
|
||||
cells.forEach(c=>{ if(c===""){const s=document.createElement("div");s.style.visibility="hidden";t.appendChild(s);} else t.appendChild(keyEl(c)); });
|
||||
h.appendChild(t); return h;
|
||||
}
|
||||
function card(name,d){
|
||||
const sec=document.createElement("div");sec.className="layer";
|
||||
const badge=d.badge?`<span class="badge">${d.badge}</span>`:"";
|
||||
sec.innerHTML=`<h2><b style="background:${d.accent||'#9ccfd8'}">${name}</b> ${d.purpose} ${badge}</h2><p class="how">reach: ${d.how}</p>`;
|
||||
const board=document.createElement("div");board.className="board";
|
||||
board.appendChild(half([d.top[0],d.home[0],d.bot[0]],d.thumbs[0],"L"));
|
||||
board.appendChild(half([d.top[1],d.home[1],d.bot[1]],d.thumbs[1],"R"));
|
||||
sec.appendChild(board);return sec;
|
||||
}
|
||||
|
||||
const NOTES={
|
||||
current:"<b>My current code (live)</b> — what's in the tree now, including the HD-hybrid work: full PM adaptives (HDP base only), qu/z/th combos, linger brackets, smart caps-word, Option-A thumbs, blanked HD outer columns. SYM shown is the current mirror (v2 is still a proposal).",
|
||||
hd:"<b>Full canonical Hands Down Promethium</b> (moutis) for reference — its own 6 layers + Semantic-Keys engine. This is the 'ideal' we're borrowing ideas from, not something to adopt wholesale.",
|
||||
proposed:"<b>Proposal layer</b> — most of this is now <b>IMPLEMENTED</b> (adaptives, qu/z/th combos, linger, smart caps-word, Option-A thumbs, blanked HD columns). Still open proposals: <b>SYM Programming v2</b> and <b>Colemak-DH</b> (toggles above). Diff marks = change vs the old baseline."
|
||||
};
|
||||
const TITLES={current:"My current layout (as flashed)",hd:"Full Hands Down Promethium (canonical, for reference)",proposed:"Proposed hybrid — Miryoku chassis + HD engine"};
|
||||
|
||||
function render(){
|
||||
document.getElementById("controls").classList.toggle("prop", state.view==="proposed");
|
||||
document.getElementById("enginewrap").style.display = state.view==="proposed" ? "" : "none";
|
||||
document.body.classList.toggle("diff", state.view==="proposed" && state.diff!=="off");
|
||||
document.getElementById("title").textContent=TITLES[state.view];
|
||||
document.getElementById("viewnote").innerHTML=NOTES[state.view];
|
||||
const foot=document.getElementById("foot");
|
||||
foot.innerHTML = state.view==="proposed"
|
||||
? "Implemented: adaptives (HDP-only), qu/z/th combos, linger ( { , smart caps-word, Option-A thumbs, blanked HD outer columns. Still proposals: SYM Programming v2 · Colemak-DH. Linger [ and \" need a SYM home first."
|
||||
: state.view==="hd"
|
||||
? "Only the 3 Corne thumbs are drawn; canonical HD defines up to 6 (RH3/RH4/RH5 omitted). Purple = Semantic Keys."
|
||||
: "Outer pinky columns are mostly ∅ — you run a 34/36-key layout on a 42-key board.";
|
||||
const app=document.getElementById("app");app.innerHTML="";
|
||||
const set = state.view==="current"?CURRENT() : state.view==="hd"?HD() : PROPOSED();
|
||||
set.forEach(([n,d])=>app.appendChild(card(n,d)));
|
||||
}
|
||||
function wire(id,key){
|
||||
document.getElementById(id).addEventListener("click",e=>{
|
||||
if(e.target.tagName!=="BUTTON")return;
|
||||
[...e.currentTarget.children].forEach(b=>b.classList.remove("on"));
|
||||
e.target.classList.add("on"); state[key]=e.target.dataset.v; render();
|
||||
});
|
||||
}
|
||||
wire("segView","view"); wire("segBase","base"); wire("segSym","sym"); wire("segDiff","diff");
|
||||
render();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,3 +1,13 @@
|
|||
/*
|
||||
* CMK-defs.h
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Copyright (c) 2026 Raphaël Mor
|
||||
*
|
||||
* Part of a QMK userspace whose design follows Hands Down Promethium by moutis
|
||||
* (https://github.com/moutis/HandsDown, GPL-3.0). See ./LICENSE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// Override keys with HRM and Layer keys
|
||||
|
|
|
|||
|
|
@ -1,3 +1,13 @@
|
|||
/*
|
||||
* FUN-defs.h
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Copyright (c) 2026 Raphaël Mor
|
||||
*
|
||||
* Part of a QMK userspace whose design follows Hands Down Promethium by moutis
|
||||
* (https://github.com/moutis/HandsDown, GPL-3.0). See ./LICENSE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// Override keys with HRM and Layer keys
|
||||
|
|
|
|||
|
|
@ -1,3 +1,13 @@
|
|||
/*
|
||||
* HDG-defs.h
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Copyright (c) 2026 Raphaël Mor
|
||||
*
|
||||
* Part of a QMK userspace whose design follows Hands Down Promethium by moutis
|
||||
* (https://github.com/moutis/HandsDown, GPL-3.0). See ./LICENSE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// Override keys with HRM and Layer keys
|
||||
|
|
|
|||
|
|
@ -1,3 +1,13 @@
|
|||
/*
|
||||
* HDP-defs.h
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Copyright (c) 2026 Raphaël Mor
|
||||
*
|
||||
* Part of a QMK userspace whose design follows Hands Down Promethium by moutis
|
||||
* (https://github.com/moutis/HandsDown, GPL-3.0). See ./LICENSE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// Let's place these HD keycodes on the keymap
|
||||
|
|
@ -13,32 +23,34 @@
|
|||
// ╰─────────────╯ ╰─────────────╯
|
||||
//
|
||||
//
|
||||
// Base (alpha) Layer Hands Down Promethium (HRMs /+ thumb mods)
|
||||
// Base (alpha) Layer Hands Down Promethium — canonical arrangement (moutis)
|
||||
// ╭───────────────────────────────╮ ╭───────────────────────────────╮
|
||||
// │ ESC F P D L X LTA │ │ RTA ; U O Y B Z │
|
||||
// │ TAB S N T H K LMA │ │ RMA , A E I C Q │
|
||||
// │ \ V W G M J ╭─────╯ ╰─────╮ - . ' = / ENT │
|
||||
// ╰───────────╮ ESC TAB R │ │ SPC ENT BSP ╭───────────╯
|
||||
// │ · V W G M J LTA │ │ RTA # . / " ' · │
|
||||
// │ · S N T H K LMA │ │ RMA , A E I C · │
|
||||
// │ · F P D L X ╭─────╯ ╰─────╮ - U O Y B · │
|
||||
// ╰───────────╮ ESC SPC TAB │ │ R BSP ENT ╭───────────╯
|
||||
// ╰─────────────╯ ╰─────────────╯
|
||||
// Home-row mods (my GACS): pinky ⌃ · ring ⌥ · middle ⌘ · index ⇧
|
||||
// Kept extras: Hyper on D/O, Meh on L/U · outer columns blank · Option-A thumbs
|
||||
//
|
||||
|
||||
#define HDP_LT5 KC_TAB
|
||||
#define HDP_LT4 KC_F
|
||||
#define HDP_LT3 KC_P
|
||||
#define HDP_LT2 HYPR_T(KC_D)
|
||||
#define HDP_LT1 MEH_T(KC_L)
|
||||
#define HDP_LT0 KC_X
|
||||
#define HDP_LT5 KC_NO // outer column unused
|
||||
#define HDP_LT4 KC_V
|
||||
#define HDP_LT3 KC_W
|
||||
#define HDP_LT2 HYPR_T(KC_G)
|
||||
#define HDP_LT1 MEH_T(KC_M)
|
||||
#define HDP_LT0 KC_J
|
||||
#define HDP_LTA KC_NO // TODO: should be a real key once a use is found
|
||||
|
||||
#define HDP_RTA KC_NO // TODO: should be a real key once a use is found
|
||||
#define HDP_RT0 KC_SCLN
|
||||
#define HDP_RT1 MEH_T(KC_U)
|
||||
#define HDP_RT2 HYPR_T(KC_O)
|
||||
#define HDP_RT3 KC_Y
|
||||
#define HDP_RT4 KC_B
|
||||
#define HDP_RT5 KC_Z
|
||||
#define HDP_RT0 KC_HASH
|
||||
#define HDP_RT1 MEH_T(KC_DOT)
|
||||
#define HDP_RT2 HYPR_T(KC_SLSH)
|
||||
#define HDP_RT3 KC_DQUO
|
||||
#define HDP_RT4 KC_QUOT
|
||||
#define HDP_RT5 KC_NO // outer column unused (Z via N+H combo)
|
||||
|
||||
#define HDP_LM5 KC_TAB
|
||||
#define HDP_LM5 KC_NO // outer column unused (Tab is on thumb)
|
||||
#define HDP_LM4 LCTL_T(KC_S)
|
||||
#define HDP_LM3 LALT_T(KC_N)
|
||||
#define HDP_LM2 LGUI_T(KC_T)
|
||||
|
|
@ -52,29 +64,32 @@
|
|||
#define HDP_RM2 RGUI_T(KC_E)
|
||||
#define HDP_RM3 RALT_T(KC_I)
|
||||
#define HDP_RM4 RCTL_T(KC_C)
|
||||
#define HDP_RM5 KC_Q
|
||||
#define HDP_RM5 KC_NO // outer column unused (Q via W+M combo)
|
||||
|
||||
#define HDP_LB5 KC_BSLS
|
||||
#define HDP_LB4 KC_V
|
||||
#define HDP_LB3 KC_W
|
||||
#define HDP_LB2 KC_G
|
||||
#define HDP_LB1 KC_M
|
||||
#define HDP_LB0 KC_J
|
||||
#define HDP_LB5 KC_NO // outer column unused (\ is on NUM layer)
|
||||
#define HDP_LB4 KC_F
|
||||
#define HDP_LB3 KC_P
|
||||
#define HDP_LB2 KC_D
|
||||
#define HDP_LB1 KC_L
|
||||
#define HDP_LB0 KC_X
|
||||
|
||||
#define HDP_RB0 KC_MINS
|
||||
#define HDP_RB1 KC_DOT
|
||||
#define HDP_RB2 KC_QUOT
|
||||
#define HDP_RB3 KC_EQL
|
||||
#define HDP_RB4 KC_SLSH
|
||||
#define HDP_RB5 KC_ENT
|
||||
#define HDP_RB1 KC_U
|
||||
#define HDP_RB2 KC_O
|
||||
#define HDP_RB3 KC_Y
|
||||
#define HDP_RB4 KC_B
|
||||
#define HDP_RB5 KC_NO // outer column unused (Enter is on thumb)
|
||||
|
||||
//Primary Thumbs 1-3 (others are unique to the board)
|
||||
#define HDP_LH2 LT(L_MEDIA, KC_ESC)
|
||||
#define HDP_LH1 LT(L_NAV, KC_TAB)
|
||||
#define HDP_LH0 LT(L_MOUSE, KC_R)
|
||||
#define HDP_RH0 LT(L_SYM, KC_SPC)
|
||||
#define HDP_RH1 LT(L_NUM, KC_ENT)
|
||||
#define HDP_RH2 LT(L_FUN, KC_BSPC)
|
||||
// Option A: thumbs re-aligned to Colemak. Left hand now identical to Colemak;
|
||||
// only R (inner-right, Promethium's signature) and Enter (outer-right) differ.
|
||||
// Colemak's Del (outer-right) is not on a thumb here → use Shift+Bksp (see ramo_process_record.c).
|
||||
#define HDP_LH2 LT(L_MEDIA, KC_ESC) // Esc (= Colemak)
|
||||
#define HDP_LH1 LT(L_NAV, KC_SPC) // Space (= Colemak) [was Tab]
|
||||
#define HDP_LH0 LT(L_MOUSE, KC_TAB) // Tab (= Colemak) [was R]
|
||||
#define HDP_RH0 LT(L_SYM, KC_R) // R (Promethium R-on-thumb) [was Space]
|
||||
#define HDP_RH1 LT(L_NUM, KC_BSPC) // Bksp (= Colemak) [was Enter]
|
||||
#define HDP_RH2 LT(L_FUN, KC_ENT) // Enter (Colemak has Del here) [was Bksp]
|
||||
|
||||
#define HDP_RGB_LT5 BLACK
|
||||
#define HDP_RGB_LT4 ORANGE
|
||||
|
|
@ -131,9 +146,13 @@
|
|||
#define HDP_RGB_RH2 MAGENTA
|
||||
|
||||
|
||||
// type p + l to type q
|
||||
// type w + m to type qu (canonical PM position)
|
||||
#define HD_Q_keys HDP_LT3, HDP_LT1
|
||||
|
||||
// type n + h to type z
|
||||
#define HD_Z_keys HDP_LM3, HDP_LM1
|
||||
|
||||
// Enable the H-digraph roll-combo on Promethium
|
||||
#define USE_HD_H_DIGRAPH_COMBO
|
||||
#define HD_Th_keys HDP_LM2, HDP_LM1 // T + H -> "th"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,13 @@
|
|||
/*
|
||||
* MED-defs.h
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Copyright (c) 2026 Raphaël Mor
|
||||
*
|
||||
* Part of a QMK userspace whose design follows Hands Down Promethium by moutis
|
||||
* (https://github.com/moutis/HandsDown, GPL-3.0). See ./LICENSE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// Override keys with HRM and Layer keys
|
||||
|
|
@ -26,11 +36,11 @@
|
|||
#define MED_LTA KC_NO // TODO: should be a real key once a use is found
|
||||
|
||||
#define MED_RTA KC_NO // TODO: should be a real key once a use is found
|
||||
#define MED_RT0 RGB_TOG
|
||||
#define MED_RT1 RGB_MOD
|
||||
#define MED_RT2 RGB_HUI
|
||||
#define MED_RT3 RGB_SAI
|
||||
#define MED_RT4 RGB_VAI
|
||||
#define MED_RT0 RM_TOGG
|
||||
#define MED_RT1 RM_NEXT
|
||||
#define MED_RT2 RM_HUEU
|
||||
#define MED_RT3 RM_SATU
|
||||
#define MED_RT4 RM_VALU
|
||||
#define MED_RT5 MED_NO
|
||||
|
||||
#define MED_LM5 NAV_NO
|
||||
|
|
|
|||
|
|
@ -1,3 +1,13 @@
|
|||
/*
|
||||
* MOS-defs.h
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Copyright (c) 2026 Raphaël Mor
|
||||
*
|
||||
* Part of a QMK userspace whose design follows Hands Down Promethium by moutis
|
||||
* (https://github.com/moutis/HandsDown, GPL-3.0). See ./LICENSE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// Override keys with HRM and Layer keys
|
||||
|
|
@ -42,10 +52,10 @@
|
|||
|
||||
#define MOS_RMA KC_NO // TODO: should be a real key once a use is found
|
||||
#define MOS_RM0 MOS_NO
|
||||
#define MOS_RM1 KC_MS_L
|
||||
#define MOS_RM2 KC_MS_D
|
||||
#define MOS_RM3 KC_MS_U
|
||||
#define MOS_RM4 KC_MS_R
|
||||
#define MOS_RM1 MS_LEFT
|
||||
#define MOS_RM2 MS_DOWN
|
||||
#define MOS_RM3 MS_UP
|
||||
#define MOS_RM4 MS_RGHT
|
||||
#define MOS_RM5 MOS_NO
|
||||
|
||||
#define MOS_LB5 MOS_NO
|
||||
|
|
@ -56,10 +66,10 @@
|
|||
#define MOS_LB0 MOS_NO
|
||||
|
||||
#define MOS_RB0 MOS_NO
|
||||
#define MOS_RB1 KC_WH_L
|
||||
#define MOS_RB2 KC_WH_D
|
||||
#define MOS_RB3 KC_WH_U
|
||||
#define MOS_RB4 KC_WH_R
|
||||
#define MOS_RB1 MS_WHLL
|
||||
#define MOS_RB2 MS_WHLD
|
||||
#define MOS_RB3 MS_WHLU
|
||||
#define MOS_RB4 MS_WHLR
|
||||
#define MOS_RB5 MOS_NO
|
||||
|
||||
//Primary Thumbs 1-3 (others are unique to the board)
|
||||
|
|
@ -67,9 +77,9 @@
|
|||
#define MOS_LH1 MOS_NO
|
||||
#define MOS_LH0 MOS_NO
|
||||
|
||||
#define MOS_RH0 KC_BTN2
|
||||
#define MOS_RH1 KC_BTN1
|
||||
#define MOS_RH2 KC_BTN3
|
||||
#define MOS_RH0 MS_BTN2
|
||||
#define MOS_RH1 MS_BTN1
|
||||
#define MOS_RH2 MS_BTN3
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,13 @@
|
|||
/*
|
||||
* NAV-defs.h
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Copyright (c) 2026 Raphaël Mor
|
||||
*
|
||||
* Part of a QMK userspace whose design follows Hands Down Promethium by moutis
|
||||
* (https://github.com/moutis/HandsDown, GPL-3.0). See ./LICENSE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// Override keys with HRM and Layer keys
|
||||
|
|
|
|||
|
|
@ -1,3 +1,13 @@
|
|||
/*
|
||||
* NUM-defs.h
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Copyright (c) 2026 Raphaël Mor
|
||||
*
|
||||
* Part of a QMK userspace whose design follows Hands Down Promethium by moutis
|
||||
* (https://github.com/moutis/HandsDown, GPL-3.0). See ./LICENSE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// Override keys with HRM and Layer keys
|
||||
|
|
|
|||
|
|
@ -1,3 +1,13 @@
|
|||
/*
|
||||
* SYM-defs.h
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Copyright (c) 2026 Raphaël Mor
|
||||
*
|
||||
* Part of a QMK userspace whose design follows Hands Down Promethium by moutis
|
||||
* (https://github.com/moutis/HandsDown, GPL-3.0). See ./LICENSE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// Override keys with HRM and Layer keys
|
||||
|
|
|
|||
|
|
@ -1,61 +1,139 @@
|
|||
/*
|
||||
* ramo_adaptive.c
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Copyright (c) 2026 Raphaël Mor
|
||||
*
|
||||
* Part of a QMK userspace whose design follows Hands Down Promethium by moutis
|
||||
* (https://github.com/moutis/HandsDown, GPL-3.0). See ./LICENSE.
|
||||
*/
|
||||
|
||||
bool process_adaptive_key(uint16_t keycode, const keyrecord_t *record) {
|
||||
bool result = true;
|
||||
|
||||
if (timer_elapsed(prior_keydown_timer) > RAMO_ADAPTIVE_TERM) { // outside adaptive threshhold
|
||||
prior_keycode = prior_keydown_timer = 0; // turn off Adaptives.
|
||||
return true; // no adaptive conditions, so return.
|
||||
// Adaptives are tuned to Hands Down Promethium's physical rolls,
|
||||
// so only run them while the Hands Down base is the active default layer.
|
||||
// (On Colemak they'd "fix" bigrams that aren't even neighbors.)
|
||||
if (get_highest_layer(default_layer_state) != L_HANDSDOWN) {
|
||||
return true;
|
||||
}
|
||||
// We're within Adaptive time window
|
||||
|
||||
// Window is measured keydown-to-keydown using record->event.time, NOT
|
||||
// timer_read(). For a mod-tap/layer-tap, process_record_user only sees the
|
||||
// record once the tap resolves (on release, or after TAPPING_TERM), so
|
||||
// timer_read() here would measure prior-keydown → *this key's release* and
|
||||
// charge the hold time against the window. event.time is stamped when the
|
||||
// key was physically pressed, so 12 mod-tap keys (G M . / + both home rows)
|
||||
// get the same window as plain keys.
|
||||
if (TIMER_DIFF_16(record->event.time, prior_keydown_timer) > RAMO_ADAPTIVE_TERM) { // outside adaptive window
|
||||
prior_keycode = preprior_keycode = prior_keydown_timer = 0; // turn off adaptives, reset
|
||||
return true; // no adaptive conditions, continue processing.
|
||||
}
|
||||
// We're within the adaptive rolling window.
|
||||
|
||||
|
||||
switch (keycode) {
|
||||
|
||||
// ---------------- left hand ----------------
|
||||
case KC_D:
|
||||
switch (prior_keycode) {
|
||||
case KC_B:
|
||||
if (preprior_keycode == KC_P) { // P B D -> PWD
|
||||
RAMO_TAP(KC_BSPC); RAMO_TAP(KC_W); // del B, W, D continues
|
||||
}
|
||||
break;
|
||||
case KC_P: RAMO_TAP(KC_W); break; // PD -> PWD (insert W, D continues)
|
||||
}
|
||||
break;
|
||||
case KC_F:
|
||||
switch (prior_keycode) {
|
||||
case KC_P: RAMO_TAP(KC_S); result = false; break; // PF -> PS
|
||||
}
|
||||
break;
|
||||
case KC_G:
|
||||
switch (prior_keycode) {
|
||||
case KC_K: RAMO_TAP(KC_L); result = false; break; // KG -> KL
|
||||
case KC_M:
|
||||
if (preprior_keycode == KC_W) { // W M G -> WML
|
||||
RAMO_TAP(KC_L); result = false; // G becomes L
|
||||
break;
|
||||
}
|
||||
RAMO_TAP(KC_BSPC); RAMO_TAP(KC_L); break; // MG -> LG (del M, L, G continues)
|
||||
case KC_J: RAMO_TAP(KC_P); break; // JG -> JPG (insert P, G continues)
|
||||
case KC_W: RAMO_TAP(KC_D); result = false; break; // WG -> WD
|
||||
}
|
||||
break;
|
||||
case KC_H:
|
||||
switch (prior_keycode) {
|
||||
case KC_K: RAMO_TAP(KC_N); result = false; break; // KH -> KN
|
||||
}
|
||||
break;
|
||||
case KC_J:
|
||||
switch (prior_keycode) {
|
||||
case KC_G: RAMO_SEND_STRING("th"); result = false; break; // GJ -> ...g + "th" (e.g. "length")
|
||||
// V J removed: V+J is the ˜ tilde combo (HD_tilde_combo), same
|
||||
// unreachability as W M above. W J kept — no combo on those keys.
|
||||
case KC_W: RAMO_TAP(KC_L); result = false; break; // WJ -> WL
|
||||
}
|
||||
break;
|
||||
case KC_K:
|
||||
switch (prior_keycode) {
|
||||
case KC_M: RAMO_TAP(KC_BSPC); RAMO_TAP(KC_L); break; // MK -> LK (del M, L, K continues)
|
||||
case KC_H: RAMO_TAP(KC_BSPC); RAMO_TAP(KC_N); break; // HK -> NK (del H, N, K continues)
|
||||
}
|
||||
break;
|
||||
case KC_M:
|
||||
switch (prior_keycode) {
|
||||
case KC_G: // GM -> GL
|
||||
case KC_V: RAMO_TAP(KC_L); result = false; break; // VM -> VL
|
||||
// W M (and the 3-key M W M) removed: W+M is the `qu` combo
|
||||
// (HD_Q_keys). Fast enough to roll for the adaptive is also fast
|
||||
// enough to land inside COMBO_TERM, so `qu` won and the adaptive
|
||||
// was unreachable. Kept the combo — Q needs a home.
|
||||
}
|
||||
break;
|
||||
case KC_P:
|
||||
switch (prior_keycode) {
|
||||
case KC_F: RAMO_TAP(KC_BSPC); RAMO_TAP(KC_S); break; // FP -> SP (del F, S, P continues)
|
||||
}
|
||||
break;
|
||||
case KC_V:
|
||||
switch (prior_keycode) {
|
||||
case KC_G: RAMO_TAP(KC_T); result = false; break; // GV -> GT
|
||||
case KC_M: RAMO_TAP(KC_BSPC); RAMO_TAP(KC_L); break; // MV -> LV (del M, L, V continues)
|
||||
}
|
||||
break;
|
||||
case KC_W:
|
||||
switch (prior_keycode) {
|
||||
case KC_G: RAMO_TAP(KC_D); result = false; break; // GW -> GD
|
||||
case KC_M: RAMO_TAP(KC_P); result = false; break; // MW -> MP
|
||||
}
|
||||
break;
|
||||
|
||||
// ---------------- right hand ----------------
|
||||
case KC_B:
|
||||
switch (prior_keycode) {
|
||||
case KC_Y: RAMO_TAP(KC_BSPC); RAMO_TAP(KC_I); break; // YB -> IB (del Y, I, B continues)
|
||||
}
|
||||
break;
|
||||
case KC_E:
|
||||
switch (prior_keycode) {
|
||||
case KC_A: // "AE" yields "AU" (8x more common) keeping it on home row
|
||||
tap_code(KC_U);
|
||||
result = false; // done.
|
||||
break;
|
||||
case KC_A: RAMO_TAP(KC_U); result = false; break; // AE -> AU (keeps home row)
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// true: did not trigger adaptive. Continue processing.
|
||||
// false: did trigger adaptive. Do not continue.
|
||||
// true: no adaptive triggered, continue processing.
|
||||
// false: adaptive replaced the key, stop processing.
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
PBD -> PWD
|
||||
Skipped by choice (low value on this HDP arrangement):
|
||||
P/B/S then L -> unshifted L
|
||||
|
||||
PF -> PS
|
||||
|
||||
KG -> KL
|
||||
WMG -> WML
|
||||
MG -> LG
|
||||
JG -> JPG
|
||||
WG -> WD
|
||||
|
||||
KH -> KN
|
||||
|
||||
GJ -> GTH
|
||||
VJ -> VL
|
||||
WJ -> WL
|
||||
|
||||
MK -> LK
|
||||
HK -> NK
|
||||
|
||||
GM -> GL
|
||||
VM -> VL
|
||||
MWM -> MWL
|
||||
(.)WM -> (.)LM
|
||||
|
||||
FP -> SP
|
||||
|
||||
GV -> GT
|
||||
MV -> LV
|
||||
|
||||
GW -> GD
|
||||
MW -> MP
|
||||
|
||||
YB -> IB
|
||||
AE -> AU
|
||||
Removed — unreachable, the same two keys are a combo (combos resolve in
|
||||
pre_process_record_quantum, before this engine ever runs):
|
||||
W M -> lm and M W M -> MWL (W+M is the `qu` combo)
|
||||
V J -> VL (V+J is the ˜ tilde combo)
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,3 +1,13 @@
|
|||
/*
|
||||
* ramo_combos.c
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Copyright (c) 2026 Raphaël Mor
|
||||
*
|
||||
* Part of a QMK userspace whose design follows Hands Down Promethium by moutis
|
||||
* (https://github.com/moutis/HandsDown, GPL-3.0). See ./LICENSE.
|
||||
*/
|
||||
|
||||
// combos
|
||||
#ifdef USE_HD_H_DIGRAPH_COMBO
|
||||
const uint16_t PROGMEM HD_Th_combo[] = {HD_Th_keys, COMBO_END}; // TYPE "th" in hands down
|
||||
|
|
@ -6,6 +16,33 @@ const uint16_t PROGMEM HD_Qu_combo[] = {HD_Q_keys, COMBO_END}; // TYPE "qu" (Lin
|
|||
const uint16_t PROGMEM HD_Z_combo[] = {HD_Z_keys, COMBO_END}; // TYPE "z"
|
||||
const uint16_t PROGMEM CMK_Th_combo[] = {CMK_Th_keys, COMBO_END}; // TYPE "th" in colemak
|
||||
|
||||
// --- Hands Down base only (gated in combo_should_trigger) -------------------
|
||||
// All at moutis' CANONICAL PM positions (layout now matches). Diacritics/ligatures
|
||||
// assume the stock US Mac layout (⌥-dead-keys).
|
||||
// 2g diacritics — tap the combo (dead key / glyph), then type the letter:
|
||||
const uint16_t PROGMEM HD_acut_combo[] = {HDP_RM1, HDP_RM2, COMBO_END}; // A+E → ´ (⌥E) é
|
||||
const uint16_t PROGMEM HD_grv_combo[] = {HDP_RM2, HDP_RM3, COMBO_END}; // E+I → ` (⌥`) è
|
||||
const uint16_t PROGMEM HD_circ_combo[] = {HDP_RM1, HDP_RM3, COMBO_END}; // A+I → ˆ (⌥I) ê
|
||||
const uint16_t PROGMEM HD_umla_combo[] = {HDP_RB1, HDP_RB2, COMBO_END}; // U+O → ¨ (⌥U) ü
|
||||
const uint16_t PROGMEM HD_tilde_combo[] = {HDP_LT4, HDP_LT0, COMBO_END}; // V+J → ˜ (⌥N) ñ
|
||||
const uint16_t PROGMEM HD_cedil_combo[] = {HDP_LM4, HDP_LM2, COMBO_END}; // S+T → ç (⌥C direct)
|
||||
// 2f ligatures:
|
||||
const uint16_t PROGMEM HD_oe_combo[] = {HDP_RM2, HDP_RB2, COMBO_END}; // E+O → œ (⌥Q)
|
||||
const uint16_t PROGMEM HD_ae_combo[] = {HDP_RM1, HDP_RB1, COMBO_END}; // A+U → æ (⌥')
|
||||
// 2c whitespace (Tab/Enter already on thumbs → only these):
|
||||
const uint16_t PROGMEM HD_stab_combo[] = {HDP_LT4, HDP_LT3, HDP_LT2, COMBO_END}; // V+W+G → Shift-Tab
|
||||
const uint16_t PROGMEM HD_app_combo[] = {HDP_LT1, HDP_LT0, COMBO_END}; // M+J → App menu
|
||||
// 2e symbols — canonical top-right punctuation cluster (# . / " '):
|
||||
const uint16_t PROGMEM HD_scln_combo[] = {HDP_RM0, HDP_RM1, COMBO_END}; // ,+A → ;
|
||||
const uint16_t PROGMEM HD_coln_combo[] = {HDP_RT1, HDP_RT3, COMBO_END}; // .+" → :
|
||||
const uint16_t PROGMEM HD_exlm_combo[] = {HDP_RT1, HDP_RT2, COMBO_END}; // .+/ → !
|
||||
const uint16_t PROGMEM HD_ques_combo[] = {HDP_RT2, HDP_RT3, COMBO_END}; // /+" → ?
|
||||
// 2h caps word — canonical position (moutis: H_CAPW_combo on HD_RB3+HD_RB4).
|
||||
// Same two keys as the YB → IB adaptive, but combos are resolved in
|
||||
// pre_process_record_quantum before process_record_user ever runs: a
|
||||
// simultaneous press is a combo, a sequential roll is the adaptive.
|
||||
const uint16_t PROGMEM HD_capw_combo[] = {HDP_RB3, HDP_RB4, COMBO_END}; // Y+B → Caps Word
|
||||
|
||||
enum ramo_combos {
|
||||
NO_COMBO = 0, // Sentinel value (0) to signify no combo is currently pressed
|
||||
#ifdef USE_HD_H_DIGRAPH_COMBO
|
||||
|
|
@ -14,6 +51,12 @@ enum ramo_combos {
|
|||
HD_COMBO_QU,
|
||||
HD_COMBO_Z,
|
||||
CMK_COMBO_Th,
|
||||
// Hands Down base only (contiguous — see combo_should_trigger)
|
||||
HD_COMBO_ACUT, HD_COMBO_GRV, HD_COMBO_CIRC, HD_COMBO_UMLA, HD_COMBO_TILDE, HD_COMBO_CEDIL,
|
||||
HD_COMBO_OE, HD_COMBO_AE,
|
||||
HD_COMBO_STAB, HD_COMBO_APP,
|
||||
HD_COMBO_SCLN, HD_COMBO_COLN, HD_COMBO_QUES, HD_COMBO_EXLM,
|
||||
HD_COMBO_CAPW, // keep last: combo_should_trigger gates ACUT ... CAPW
|
||||
};
|
||||
|
||||
combo_t key_combos[] = {
|
||||
|
|
@ -25,8 +68,34 @@ combo_t key_combos[] = {
|
|||
[HD_COMBO_Z] = COMBO(HD_Z_combo, KC_Z),
|
||||
// Colemak combos
|
||||
[CMK_COMBO_Th] = COMBO_ACTION(CMK_Th_combo),
|
||||
// Hands Down base only (gated in combo_should_trigger)
|
||||
[HD_COMBO_ACUT] = COMBO(HD_acut_combo, A(KC_E)),
|
||||
[HD_COMBO_GRV] = COMBO(HD_grv_combo, A(KC_GRV)),
|
||||
[HD_COMBO_CIRC] = COMBO(HD_circ_combo, A(KC_I)),
|
||||
[HD_COMBO_UMLA] = COMBO(HD_umla_combo, A(KC_U)),
|
||||
[HD_COMBO_TILDE] = COMBO(HD_tilde_combo, A(KC_N)),
|
||||
[HD_COMBO_CEDIL] = COMBO(HD_cedil_combo, A(KC_C)),
|
||||
[HD_COMBO_OE] = COMBO(HD_oe_combo, A(KC_Q)),
|
||||
[HD_COMBO_AE] = COMBO(HD_ae_combo, A(KC_QUOT)),
|
||||
[HD_COMBO_STAB] = COMBO(HD_stab_combo, S(KC_TAB)),
|
||||
[HD_COMBO_APP] = COMBO(HD_app_combo, KC_APP),
|
||||
[HD_COMBO_SCLN] = COMBO(HD_scln_combo, KC_SCLN),
|
||||
[HD_COMBO_COLN] = COMBO(HD_coln_combo, KC_COLN),
|
||||
[HD_COMBO_QUES] = COMBO(HD_ques_combo, KC_QUES),
|
||||
[HD_COMBO_EXLM] = COMBO(HD_exlm_combo, KC_EXLM),
|
||||
[HD_COMBO_CAPW] = COMBO(HD_capw_combo, CW_TOGG),
|
||||
};
|
||||
|
||||
// Restrict the added diacritic/ligature/symbol/whitespace combos to the Hands
|
||||
// Down base; qu/z/th keep their existing (both-base) behavior.
|
||||
bool combo_should_trigger(uint16_t combo_index, combo_t *combo, uint16_t keycode, keyrecord_t *record) {
|
||||
switch (combo_index) {
|
||||
case HD_COMBO_ACUT ... HD_COMBO_CAPW:
|
||||
return get_highest_layer(default_layer_state) == L_HANDSDOWN;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t current_combo = 0; // for combo actions to hold before triggering
|
||||
|
||||
uint8_t ramo_process_combo_pressed(uint16_t combo_index);
|
||||
|
|
@ -57,23 +126,23 @@ uint8_t ramo_process_combo_pressed(uint16_t combo_index) {
|
|||
#endif
|
||||
case CMK_COMBO_Th:
|
||||
if (is_caps_word_on()) {
|
||||
tap_code16(S(KC_T)); // send "T"
|
||||
tap_code16(S(KC_H)); // send "H"
|
||||
RAMO_TAP16(S(KC_T)); // send "T"
|
||||
RAMO_TAP16(S(KC_H)); // send "H"
|
||||
} else {
|
||||
tap_code(KC_T); // send "T" honoring caps
|
||||
RAMO_TAP(KC_T); // send "T" honoring caps
|
||||
unregister_mods(MOD_MASK_SHIFT);
|
||||
tap_code(KC_H); // send "h" honoring CAPSLK state
|
||||
RAMO_TAP(KC_H); // send "h" honoring CAPSLK state
|
||||
}
|
||||
break;
|
||||
case HD_COMBO_QU:
|
||||
println("RAMO: Pressed N + H to type qu");
|
||||
println("RAMO: W + M -> qu");
|
||||
if (is_caps_word_on()) {
|
||||
tap_code16(S(KC_Q));
|
||||
tap_code16(S(KC_U));
|
||||
RAMO_TAP16(S(KC_Q));
|
||||
RAMO_TAP16(S(KC_U));
|
||||
} else {
|
||||
tap_code16(KC_Q);
|
||||
RAMO_TAP16(KC_Q);
|
||||
unregister_mods(MOD_MASK_SHIFT);
|
||||
tap_code16(KC_U);
|
||||
RAMO_TAP16(KC_U);
|
||||
}
|
||||
return combo_index; // if held, delete the 'u' in matrix_scan_user_process_combo
|
||||
}
|
||||
|
|
@ -88,7 +157,7 @@ void ramo_process_combo_released(uint16_t combo_index) {
|
|||
case CMK_COMBO_Th:
|
||||
break;
|
||||
case HD_COMBO_QU:
|
||||
println("RAMO: Released P + L to type qu");
|
||||
println("RAMO: released W + M (qu)");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -99,7 +168,7 @@ void combo_check_for_linger(void) {
|
|||
clear_mods();
|
||||
switch (current_combo) {
|
||||
case HD_COMBO_QU:
|
||||
tap_code16(KC_BSPC); // held, so delete u
|
||||
RAMO_TAP16(KC_BSPC); // held, so delete u
|
||||
break;
|
||||
}
|
||||
current_combo = NO_COMBO;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,13 @@
|
|||
/*
|
||||
* ramo_layers.h
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Copyright (c) 2026 Raphaël Mor
|
||||
*
|
||||
* Part of a QMK userspace whose design follows Hands Down Promethium by moutis
|
||||
* (https://github.com/moutis/HandsDown, GPL-3.0). See ./LICENSE.
|
||||
*/
|
||||
|
||||
#include "layers/CMK-defs.h"
|
||||
#ifdef RAMO_USE_HD_PROMETHIUM
|
||||
#include "layers/HDP-defs.h"
|
||||
|
|
|
|||
|
|
@ -1,3 +1,12 @@
|
|||
/*
|
||||
* ramo_ledmap.c
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Copyright (c) 2026 Raphaël Mor
|
||||
*
|
||||
* Part of a QMK userspace whose design follows Hands Down Promethium by moutis
|
||||
* (https://github.com/moutis/HandsDown, GPL-3.0). See ./LICENSE.
|
||||
*/
|
||||
|
||||
// Macro that defines a ledmap per layer (e.g. CMK_RGB_LT5, NAV_RGB_LB1)
|
||||
// Then each layer simply has to define the derired color per key
|
||||
|
|
|
|||
107
users/raphaelmor/ramo_linger.c
Normal file
107
users/raphaelmor/ramo_linger.c
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* ramo_linger.c
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Copyright (c) 2026 Raphaël Mor
|
||||
*
|
||||
* Part of a QMK userspace whose design follows Hands Down Promethium by moutis
|
||||
* (https://github.com/moutis/HandsDown, GPL-3.0). See ./LICENSE.
|
||||
*/
|
||||
|
||||
// Linger brackets + smart Caps Word (casemods)
|
||||
//
|
||||
// Linger: tap ( or { for a normal single bracket; HOLD it past RAMO_LINGER_TERM
|
||||
// and it auto-closes with the caret between the pair: (|) {|} .
|
||||
// We consume the press and TAP the open bracket immediately (so the key is never
|
||||
// held in the HID report → no OS auto-repeat), then add the closing bracket + Left
|
||||
// once the hold threshold is reached. No event deferral, no roll-over ordering bugs.
|
||||
|
||||
// returns true if it consumed the key press (caller should stop processing).
|
||||
bool linger_track(uint16_t keycode, keyrecord_t *record) {
|
||||
if (record->event.pressed) {
|
||||
uint16_t close = 0;
|
||||
switch (keycode) {
|
||||
case KC_LPRN: close = KC_RPRN; break;
|
||||
case KC_LCBR: close = KC_RCBR; break;
|
||||
}
|
||||
if (close) {
|
||||
RAMO_TAP16(keycode); // type the open bracket now (tap → no auto-repeat)
|
||||
linger_key = keycode;
|
||||
linger_close = close;
|
||||
linger_ktimer = timer_read();
|
||||
linger_fired = false;
|
||||
return true; // consumed
|
||||
}
|
||||
linger_key = 0; // any other key press cancels a pending linger
|
||||
} else if (keycode == linger_key) {
|
||||
linger_key = 0; // released before/after threshold
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void linger_scan(void) {
|
||||
if (linger_key && !linger_fired && timer_elapsed(linger_ktimer) > RAMO_LINGER_TERM) {
|
||||
RAMO_TAP16(linger_close); // add the closing bracket
|
||||
RAMO_TAP16(KC_LEFT); // move caret between the pair
|
||||
linger_fired = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Smart Caps Word: letters get shifted; digits / _ / - / bspc keep it on;
|
||||
// anything else ends it. Space is handled by caps_word_space_track() below
|
||||
// (space -> _, double space -> real space + exit), so KC_SPC deliberately does
|
||||
// NOT appear here: if a space ever reaches this hook the intercept missed it,
|
||||
// and ending the word is the right fallback.
|
||||
bool caps_word_press_user(uint16_t keycode) {
|
||||
switch (keycode) {
|
||||
case KC_A ... KC_Z:
|
||||
add_weak_mods(MOD_BIT(KC_LSFT)); // shift the letter
|
||||
return true;
|
||||
case KC_1 ... KC_0:
|
||||
case KC_BSPC:
|
||||
case KC_DEL:
|
||||
case KC_MINS:
|
||||
case KC_UNDS:
|
||||
return true; // keep Caps Word active, don't shift
|
||||
default:
|
||||
return false; // turn Caps Word off
|
||||
}
|
||||
}
|
||||
|
||||
// --- Caps Word space handling (SCREAMING_SNAKE) ------------------------------
|
||||
// space -> `_`, Caps Word stays on
|
||||
// space space -> deletes the `_`, sends a real space, Caps Word turns off
|
||||
//
|
||||
// This lives in process_record_user rather than caps_word_press_user because
|
||||
// that hook can't suppress a keypress — it can only allow it or end the word.
|
||||
//
|
||||
// NOTE: the space key is LT(L_NAV, KC_SPC) on both bases, so this only sees
|
||||
// KC_SPC because process_record_user strips mod-taps/layer-taps to their tap
|
||||
// keycode first. Keep this call *after* that strip.
|
||||
static bool cw_space_pending = false; // did the last press emit a `_`?
|
||||
|
||||
// QMK calls this whenever Caps Word turns on or off (including idle timeout),
|
||||
// so the pending-space state can never outlive the word.
|
||||
void caps_word_set_user(bool active) {
|
||||
cw_space_pending = false;
|
||||
}
|
||||
|
||||
// returns true if it consumed the key press (caller should stop processing).
|
||||
bool caps_word_space_track(uint16_t keycode, keyrecord_t *record) {
|
||||
if (!record->event.pressed || !is_caps_word_on()) {
|
||||
return false;
|
||||
}
|
||||
if (keycode != KC_SPC) {
|
||||
cw_space_pending = false; // any other key ends the double-space window
|
||||
return false;
|
||||
}
|
||||
if (cw_space_pending) { // second space in a row: end the word
|
||||
RAMO_TAP(KC_BSPC); // remove the `_` we just typed
|
||||
caps_word_off(); // clears cw_space_pending via caps_word_set_user
|
||||
RAMO_TAP(KC_SPC); // ...and leave a real space behind
|
||||
return true;
|
||||
}
|
||||
RAMO_TAP16(KC_UNDS); // first space: SCREAMING_SNAKE separator
|
||||
cw_space_pending = true;
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1,4 +1,14 @@
|
|||
/*
|
||||
* ramo_matrix.c
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Copyright (c) 2026 Raphaël Mor
|
||||
*
|
||||
* Part of a QMK userspace whose design follows Hands Down Promethium by moutis
|
||||
* (https://github.com/moutis/HandsDown, GPL-3.0). See ./LICENSE.
|
||||
*/
|
||||
|
||||
void matrix_scan_user(void) {
|
||||
combo_check_for_linger();
|
||||
linger_scan();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,24 @@
|
|||
/*
|
||||
* ramo_process_record.c
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Copyright (c) 2026 Raphaël Mor
|
||||
*
|
||||
* Part of a QMK userspace whose design follows Hands Down Promethium by moutis
|
||||
* (https://github.com/moutis/HandsDown, GPL-3.0). See ./LICENSE.
|
||||
*/
|
||||
|
||||
// This is called everytime a key is pressed or released
|
||||
bool process_record_user(uint16_t keycode, keyrecord_t *record) {
|
||||
bool result = true;
|
||||
// uint8_t saved_modifiers;
|
||||
// saved_modifiers = get_mods(); // share modifiers
|
||||
|
||||
// Do we need to filter multi-function keys?
|
||||
|
||||
|
||||
if (record->event.pressed) {
|
||||
uprintf("RAMO: USER: Pressed kc: %s\n", get_keycode_string(keycode));
|
||||
} else {
|
||||
uprintf("RAMO: USER: Released kc: %s\n", get_keycode_string(keycode));
|
||||
}
|
||||
|
||||
switch (keycode) {
|
||||
case QK_MOD_TAP ... QK_MOD_TAP_MAX:
|
||||
case QK_LAYER_TAP ... QK_LAYER_TAP_MAX:
|
||||
|
|
@ -12,20 +26,66 @@ bool process_record_user(uint16_t keycode, keyrecord_t *record) {
|
|||
if (!record->tap.count) // if not tapped yet…
|
||||
return true; // let QMK do that first
|
||||
keycode &= QK_BASIC_MAX; // mods & taps have been handled.
|
||||
uprintf("RAMO: USER: Key is complex -> Keycode &= QK_BASIC_MAX: %s\n", get_keycode_string(keycode));
|
||||
}
|
||||
|
||||
// Linger brackets: track ( and { presses for hold-to-autopair (see ramo_linger.c)
|
||||
if (linger_track(keycode, record)) {
|
||||
return false; // consumed: open bracket already tapped
|
||||
}
|
||||
|
||||
// Caps Word spaces: space -> _, double space -> real space + exit.
|
||||
// (see caps_word_space_track in ramo_linger.c)
|
||||
if (caps_word_space_track(keycode, record)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Shift + Backspace = Delete. Keeps Del available on the Hands Down base,
|
||||
// where it's no longer on a thumb (Option A). Only fires with shift alone.
|
||||
if (record->event.pressed && keycode == KC_BSPC
|
||||
&& (get_mods() & MOD_MASK_SHIFT) && !(get_mods() & ~MOD_MASK_SHIFT)) {
|
||||
uint8_t saved = get_mods();
|
||||
clear_mods();
|
||||
RAMO_TAP(KC_DEL);
|
||||
set_mods(saved);
|
||||
return false;
|
||||
}
|
||||
|
||||
// ADAPT_SHIFT (Hands Down base only): comma-leader one-shot Shift.
|
||||
// Type `,` then a letter within RAMO_ADAPT_SHIFT_TERM → delete the comma and
|
||||
// capitalize the letter (", the" -> "The"). Skipped while Caps Word is active.
|
||||
// Gated to HDP because `,` without a trailing space is common in code (arg lists).
|
||||
if (record->event.pressed
|
||||
&& get_highest_layer(default_layer_state) == L_HANDSDOWN
|
||||
&& prior_keycode == KC_COMM
|
||||
&& !is_caps_word_on()
|
||||
&& TIMER_DIFF_16(record->event.time, prior_keydown_timer) <= RAMO_ADAPT_SHIFT_TERM
|
||||
&& keycode >= KC_A && keycode <= KC_Z) {
|
||||
RAMO_TAP(KC_BSPC); // remove the leader comma
|
||||
RAMO_TAP16(S(keycode)); // send the capitalized letter
|
||||
preprior_keycode = prior_keycode; // keep history consistent for 3-key rules
|
||||
prior_keycode = keycode; // record for the next adaptive check
|
||||
prior_keydown_timer = record->event.time;
|
||||
return false; // handled
|
||||
}
|
||||
|
||||
if (record->event.pressed) {
|
||||
if (!process_adaptive_key(keycode, record)) { // false means we handled the key press
|
||||
prior_keycode = keycode &= QK_BASIC_MAX; // this keycode is stripped of mods+taps
|
||||
prior_keydown_timer = timer_read(); // reset the adaptive timer
|
||||
preprior_keycode = prior_keycode;
|
||||
prior_keycode = keycode; // this keycode is stripped of mods+taps
|
||||
// Store the PRESS time, not timer_read(): mod-taps arrive late (see
|
||||
// the note in ramo_adaptive.c), and using read-time here would make
|
||||
// the *next* key's window start from this key's release instead.
|
||||
prior_keydown_timer = record->event.time;
|
||||
|
||||
uprintf("RAMO: USER: Adaptive succeed. Setting prior to %s and reset timer\n", get_keycode_string(prior_keycode));
|
||||
return false; // took care of that key
|
||||
}
|
||||
|
||||
// RAMO: is this needed ?
|
||||
// prior_keycode = keycode &= QK_BASIC_MAX; // this keycode is stripped of mods+taps
|
||||
// prior_keydown_timer = timer_read(); // reset the adaptive timer
|
||||
} else {
|
||||
|
||||
preprior_keycode = prior_keycode;
|
||||
prior_keycode = keycode; // this keycode is stripped of mods+taps
|
||||
prior_keydown_timer = record->event.time; // press time (see above)
|
||||
uprintf("RAMO: USER: Adaptive failed. setting prior to %s and reset timer\n", get_keycode_string(prior_keycode));
|
||||
}
|
||||
return result;
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,13 @@
|
|||
/*
|
||||
* ramo_tap_dance.c
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Copyright (c) 2026 Raphaël Mor
|
||||
*
|
||||
* Part of a QMK userspace whose design follows Hands Down Promethium by moutis
|
||||
* (https://github.com/moutis/HandsDown, GPL-3.0). See ./LICENSE.
|
||||
*/
|
||||
|
||||
void ramo_TD_make_colemak_default(tap_dance_state_t *state, void *user_data) {
|
||||
if (state->count == 2) {
|
||||
set_single_default_layer(L_COLEMAK);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,13 @@
|
|||
/*
|
||||
* ramo_tap_dance.h
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Copyright (c) 2026 Raphaël Mor
|
||||
*
|
||||
* Part of a QMK userspace whose design follows Hands Down Promethium by moutis
|
||||
* (https://github.com/moutis/HandsDown, GPL-3.0). See ./LICENSE.
|
||||
*/
|
||||
|
||||
// Tap Dance definitions
|
||||
enum {
|
||||
RAMO_TD_CMK,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,13 @@
|
|||
/*
|
||||
* raphaelmor.c
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Copyright (c) 2026 Raphaël Mor
|
||||
*
|
||||
* Part of a QMK userspace whose design follows Hands Down Promethium by moutis
|
||||
* (https://github.com/moutis/HandsDown, GPL-3.0). See ./LICENSE.
|
||||
*/
|
||||
|
||||
#include QMK_KEYBOARD_H
|
||||
|
||||
#include "raphaelmor.h"
|
||||
|
|
@ -11,7 +21,13 @@ uint8_t saved_modifiers;
|
|||
uint32_t linger_timer = 0; // time elapsed since combo was pressed
|
||||
// Adaptive
|
||||
uint16_t prior_keycode = KC_NO;
|
||||
uint16_t preprior_keycode = KC_NO; // key before prior (for 3-key adaptives)
|
||||
uint16_t prior_keydown_timer = 0; // timer of keydown for adaptive threshhold.
|
||||
// Linger brackets
|
||||
uint16_t linger_key = 0; // currently-lingering open bracket (0 = none)
|
||||
uint16_t linger_close = 0; // its matching close bracket
|
||||
uint16_t linger_ktimer = 0; // keydown timer for the linger hold
|
||||
bool linger_fired = false; // has the pair already been inserted?
|
||||
|
||||
void keyboard_post_init_user(void) {
|
||||
// Customise these values to desired behaviour
|
||||
|
|
@ -27,6 +43,9 @@ void keyboard_post_init_user(void) {
|
|||
// Include combo processing code
|
||||
#include "ramo_combos.c"
|
||||
|
||||
// Include linger-bracket + casemods code (must precede matrix + process_record)
|
||||
#include "ramo_linger.c"
|
||||
|
||||
// Include matrix processing code
|
||||
#include "ramo_matrix.c"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,39 @@
|
|||
/*
|
||||
* raphaelmor.h
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Copyright (c) 2026 Raphaël Mor
|
||||
*
|
||||
* Part of a QMK userspace whose design follows Hands Down Promethium by moutis
|
||||
* (https://github.com/moutis/HandsDown, GPL-3.0). See ./LICENSE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ramo_tap_dance.h"
|
||||
|
||||
#include "ramo_layers.h"
|
||||
|
||||
// --- Firmware-verification logging ------------------------------------------
|
||||
// Log the ACTUAL keycodes/strings sent to the host, as distinct from the
|
||||
// "Pressed kc" logged in process_record_user (which is the keymap keycode —
|
||||
// the intent — before combos/adaptives/linger transform it). This lets the
|
||||
// touchtutor `tt verify` tool cross-check "what was pressed" vs "what was sent"
|
||||
// and catch transformation bugs, including for keys that emit no character.
|
||||
//
|
||||
// Wrap tap_code/tap_code16/send_string at the userspace emission sites (combos,
|
||||
// adaptives, linger, and the process_record transforms). Plain keys are sent by
|
||||
// core QMK unchanged, so their output already equals the logged "Pressed kc".
|
||||
//
|
||||
// Uses uprintf (CONSOLE_ENABLE) + get_keycode_string (KEYCODE_STRING_ENABLE),
|
||||
// both already enabled in rules.mk. Define RAMO_NO_SENT_LOG for a quiet build.
|
||||
#ifdef RAMO_NO_SENT_LOG
|
||||
# define RAMO_TAP(kc) tap_code(kc)
|
||||
# define RAMO_TAP16(kc) tap_code16(kc)
|
||||
# define RAMO_SEND_STRING(s) send_string(s)
|
||||
#else
|
||||
# define RAMO_TAP(kc) do { uprintf("RAMO: SENT: %s\n", get_keycode_string(kc)); tap_code(kc); } while (0)
|
||||
# define RAMO_TAP16(kc) do { uprintf("RAMO: SENT: %s\n", get_keycode_string(kc)); tap_code16(kc); } while (0)
|
||||
# define RAMO_SEND_STRING(s) do { uprintf("RAMO: SENT_STR: %s\n", s); send_string(s); } while (0)
|
||||
#endif
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@ CAPS_WORD_ENABLE = yes
|
|||
COMBO_ENABLE = yes
|
||||
TAP_DANCE_ENABLE = yes
|
||||
|
||||
// TEMP: for debugging adaptive
|
||||
KEYCODE_STRING_ENABLE = yes
|
||||
CONSOLE_ENABLE = yes
|
||||
|
||||
INTROSPECTION_KEYMAP_C += raphaelmor.c
|
||||
|
||||
|
||||
|
|
|
|||
145
users/raphaelmor/tools/kle_docs.py
Normal file
145
users/raphaelmor/tools/kle_docs.py
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: MIT
|
||||
# Copyright (c) 2026 Raphaël Mor
|
||||
"""Write both layout documents from the QMK sources.
|
||||
|
||||
Two targets, one command, because they must not disagree:
|
||||
|
||||
../../../keyboard-layout/README.md the layout, no board in it, plus docs/*.svg
|
||||
users/raphaelmor/README.md what is true of this Corne and this firmware
|
||||
|
||||
Each file keeps everything outside
|
||||
|
||||
<!-- layout:begin -->
|
||||
<!-- layout:end -->
|
||||
|
||||
so the prose you write around the generated block survives a regeneration.
|
||||
|
||||
python3 tools/kle_docs.py # write both
|
||||
python3 tools/kle_docs.py --check # exit 1 and name anything stale
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
import kle_export as E # noqa: E402
|
||||
import kle_render as R # noqa: E402
|
||||
|
||||
QMK_README = E.USERSPACE / "README.md"
|
||||
LAYOUT_REPO = E.USERSPACE.parents[2] / "keyboard-layout"
|
||||
BEGIN = "<!-- layout:begin -->"
|
||||
END = "<!-- layout:end -->"
|
||||
GENERATED = ("<!-- Generated by qmk_userspace/users/raphaelmor/tools/kle_docs.py. "
|
||||
"Do not hand-edit this section; edit around it. -->")
|
||||
|
||||
LAYOUT_PREAMBLE = """# Colemak + Hands Down Promethium
|
||||
|
||||
A 36-key layout, on two alpha bases that share one set of layers.
|
||||
|
||||
**[Hands Down Promethium](https://github.com/moutis/HandsDown)** is the base I
|
||||
type on. **Colemak** is kept alongside it because it is what everything else in
|
||||
the world assumes, and because switching back costs one double tap.
|
||||
|
||||
The layers follow **[Miryoku](https://github.com/manna-harbour/miryoku)**: hold a
|
||||
thumb, get a layer, and the payload sits on the hand opposite the thumb that
|
||||
activated it. On top of that sit combos for the letters and glyphs that have no
|
||||
home on 36 keys, and adaptive keys that rewrite same-finger bigrams as you roll
|
||||
them.
|
||||
|
||||
Nothing below names a switch. Everything is described by position, so it holds
|
||||
for any 3x5 + 3 split.
|
||||
|
||||
<!-- Write whatever you like here. Only the block below is generated. -->
|
||||
"""
|
||||
|
||||
QMK_PREAMBLE_MARKER = "# HANDS DOWN GOLD and COLEMAK MIRYOKU CUSTOM SETUP FOR CORNE"
|
||||
|
||||
|
||||
def render_layers(source: dict) -> dict[str, str]:
|
||||
"""Layer prefix -> SVG text, one board per file."""
|
||||
return {prefix: R.render(E.one_board(source, enum, prefix))
|
||||
for enum, prefix in source["layers"]}
|
||||
|
||||
|
||||
def section(body: str) -> str:
|
||||
return "\n".join([BEGIN, "", GENERATED, "", body, "", END])
|
||||
|
||||
|
||||
def splice(existing: str, body: str, fallback_header: str = "") -> str:
|
||||
"""Replace the marked region, or create the file around it."""
|
||||
start, end = existing.find(BEGIN), existing.find(END)
|
||||
if start == -1 or end == -1:
|
||||
head = existing.rstrip() or fallback_header.rstrip()
|
||||
return head + "\n\n" + section(body) + "\n"
|
||||
return existing[:start] + section(body) + existing[end + len(END):]
|
||||
|
||||
|
||||
def read_or_empty(path: Path) -> str:
|
||||
return path.read_text() if path.exists() else ""
|
||||
|
||||
|
||||
def planned_files(source: dict, layout_repo: Path | None) -> dict[Path, str]:
|
||||
"""Every path this run would write, and what it would put there."""
|
||||
files: dict[Path, str] = {
|
||||
QMK_README: splice(read_or_empty(QMK_README),
|
||||
E.qmk_doc(source),
|
||||
QMK_PREAMBLE_MARKER),
|
||||
}
|
||||
if layout_repo is None:
|
||||
return files
|
||||
|
||||
body = E.layout_doc(source, "docs/layer-{prefix}.svg")
|
||||
files[layout_repo / "README.md"] = splice(
|
||||
read_or_empty(layout_repo / "README.md"), body, LAYOUT_PREAMBLE)
|
||||
for prefix, svg in render_layers(source).items():
|
||||
files[layout_repo / "docs" / f"layer-{prefix.lower()}.svg"] = svg
|
||||
return files
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser(description=__doc__,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
ap.add_argument("--layout-repo", type=Path, default=LAYOUT_REPO,
|
||||
help=f"default {LAYOUT_REPO}")
|
||||
ap.add_argument("--check", action="store_true",
|
||||
help="report what is stale and change nothing")
|
||||
ap.add_argument("--gold", action="store_true",
|
||||
help="render Hands Down Gold instead of Promethium")
|
||||
args = ap.parse_args()
|
||||
|
||||
repo = args.layout_repo if args.layout_repo.is_dir() else None
|
||||
if repo is None:
|
||||
print(f"kle_docs: {args.layout_repo} is not there — writing the QMK "
|
||||
f"README only", file=sys.stderr)
|
||||
|
||||
try:
|
||||
source = E.read_all(promethium=not args.gold)
|
||||
files = planned_files(source, repo)
|
||||
except E.ExportError as err:
|
||||
print(f"kle_docs: {err}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
stale = [p for p, text in files.items()
|
||||
if not p.exists() or p.read_text() != text]
|
||||
if args.check:
|
||||
for path in stale:
|
||||
print(f"out of date: {path}")
|
||||
print("up to date" if not stale
|
||||
else f"{len(stale)} file(s) need `python3 tools/kle_docs.py`")
|
||||
return 1 if stale else 0
|
||||
|
||||
for path, text in files.items():
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(text)
|
||||
print(f"wrote {len(files)} file(s), {len(stale)} changed")
|
||||
for path in stale:
|
||||
print(f" {path}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
1073
users/raphaelmor/tools/kle_export.py
Normal file
1073
users/raphaelmor/tools/kle_export.py
Normal file
File diff suppressed because it is too large
Load diff
259
users/raphaelmor/tools/kle_legends.py
Normal file
259
users/raphaelmor/tools/kle_legends.py
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
# SPDX-License-Identifier: MIT
|
||||
# Copyright (c) 2026 Raphaël Mor
|
||||
"""Display data for kle_export.py: keycode legends and hand-written notes.
|
||||
|
||||
Everything here is presentation. The structure of the keymap comes from the C
|
||||
sources; this module only decides how a resolved keycode should read on a keycap.
|
||||
"""
|
||||
|
||||
# --- Key appearance ----------------------------------------------------------
|
||||
# No colour. One neutral cap for everything, and a ghosted cap for a position
|
||||
# that has nothing bound to it, so the shape of each layer reads at a glance.
|
||||
# The greys are the ones from the original hand-made
|
||||
# corne-v4-1-miryoku-adaptation.json.
|
||||
|
||||
KEY_COLOR = "#424242"
|
||||
KEY_TEXT = "#ffffff"
|
||||
# The key you hold to reach a layer, shown on that layer's own board. Ghosted,
|
||||
# because it is how you got here rather than something the layer binds.
|
||||
ACTIVATOR_COLOR = "#8c5656"
|
||||
|
||||
UNBOUND = {"KC_NO", "KC_TRNS"}
|
||||
|
||||
|
||||
def is_unbound(keycode: str) -> bool:
|
||||
return keycode.strip() in UNBOUND
|
||||
|
||||
|
||||
# --- Board chrome ------------------------------------------------------------
|
||||
|
||||
BOARD_BG = "#626262"
|
||||
TITLE_COLOR = "#424242" # a decal draws no cap, so this only matters if d is off
|
||||
TITLE_TEXT = "#ffffff"
|
||||
|
||||
|
||||
# --- Keycode legends ---------------------------------------------------------
|
||||
# A legend is either a string (one centred label) or a (top, bottom) pair for a
|
||||
# key whose shifted glyph is worth showing.
|
||||
|
||||
BASIC: dict[str, object] = {
|
||||
"KC_NO": "",
|
||||
"KC_TRNS": "▽",
|
||||
|
||||
# letters and digits fill themselves in below
|
||||
"KC_QUOT": ('"', "'"),
|
||||
"KC_COMM": ("<", ","),
|
||||
"KC_DOT": (">", "."),
|
||||
"KC_SLSH": ("?", "/"),
|
||||
"KC_SCLN": (":", ";"),
|
||||
"KC_MINS": ("_", "-"),
|
||||
"KC_EQL": ("+", "="),
|
||||
"KC_LBRC": ("{", "["),
|
||||
"KC_RBRC": ("}", "]"),
|
||||
"KC_BSLS": ("|", "\\"),
|
||||
"KC_GRV": ("~", "`"),
|
||||
|
||||
"KC_DQUO": '"',
|
||||
"KC_COLN": ":",
|
||||
"KC_UNDS": "_",
|
||||
"KC_PLUS": "+",
|
||||
"KC_TILD": "~",
|
||||
"KC_PIPE": "|",
|
||||
"KC_EXLM": "!",
|
||||
"KC_QUES": "?",
|
||||
"KC_AT": "@",
|
||||
"KC_HASH": "#",
|
||||
"KC_DLR": "$",
|
||||
"KC_PERC": "%",
|
||||
"KC_CIRC": "^",
|
||||
"KC_AMPR": "&",
|
||||
"KC_ASTR": "*",
|
||||
"KC_LPRN": "(",
|
||||
"KC_RPRN": ")",
|
||||
"KC_LCBR": "{",
|
||||
"KC_RCBR": "}",
|
||||
|
||||
"KC_ESC": "Esc",
|
||||
"KC_TAB": "Tab",
|
||||
"KC_SPC": "␣",
|
||||
"KC_ENT": "⏎",
|
||||
"KC_BSPC": "⌫",
|
||||
"KC_DEL": "⌦",
|
||||
"KC_INS": "Ins",
|
||||
"KC_APP": "▤",
|
||||
"KC_PSCR": "PrtSc",
|
||||
"KC_SCRL": "ScrLk",
|
||||
"KC_PAUS": "Pause",
|
||||
|
||||
"KC_LEFT": "←",
|
||||
"KC_DOWN": "↓",
|
||||
"KC_UP": "↑",
|
||||
"KC_RGHT": "→",
|
||||
"KC_RIGHT": "→",
|
||||
"KC_HOME": "Home",
|
||||
"KC_END": "End",
|
||||
"KC_PGUP": "PgUp",
|
||||
"KC_PGDN": "PgDn",
|
||||
|
||||
"KC_LCTL": "⌃",
|
||||
"KC_RCTL": "⌃",
|
||||
"KC_LALT": "⌥",
|
||||
"KC_RALT": "⌥",
|
||||
"KC_ALGR": "AltGr",
|
||||
"KC_LGUI": "⌘",
|
||||
"KC_RGUI": "⌘",
|
||||
"KC_LSFT": "⇧",
|
||||
"KC_RSFT": "⇧",
|
||||
"KC_HYPR": "Hyper",
|
||||
"KC_MEH": "Meh",
|
||||
|
||||
"KC_MPRV": "Prev",
|
||||
"KC_MNXT": "Next",
|
||||
"KC_MPLY": "Play",
|
||||
"KC_MSTP": "Stop",
|
||||
"KC_MUTE": "Mute",
|
||||
"KC_VOLD": "Vol−",
|
||||
"KC_VOLU": "Vol+",
|
||||
|
||||
"MS_LEFT": "Ms ←",
|
||||
"MS_DOWN": "Ms ↓",
|
||||
"MS_UP": "Ms ↑",
|
||||
"MS_RGHT": "Ms →",
|
||||
"MS_WHLL": "Wh ←",
|
||||
"MS_WHLD": "Wh ↓",
|
||||
"MS_WHLU": "Wh ↑",
|
||||
"MS_WHLR": "Wh →",
|
||||
"MS_BTN1": "LMB",
|
||||
"MS_BTN2": "RMB",
|
||||
"MS_BTN3": "MMB",
|
||||
|
||||
"RM_TOGG": "RGB on",
|
||||
"RM_NEXT": "RGB mode",
|
||||
"RM_HUEU": "RGB hue",
|
||||
"RM_SATU": "RGB sat",
|
||||
"RM_VALU": "RGB val",
|
||||
|
||||
"CW_TOGG": "Caps Word",
|
||||
"QK_BOOT": "Boot",
|
||||
}
|
||||
|
||||
for _i in range(26):
|
||||
BASIC["KC_" + chr(ord("A") + _i)] = chr(ord("A") + _i)
|
||||
for _d, _shifted in zip("1234567890", "!@#$%^&*()"):
|
||||
BASIC["KC_" + _d] = (_shifted, _d)
|
||||
for _n in range(1, 25):
|
||||
BASIC[f"KC_F{_n}"] = f"F{_n}"
|
||||
|
||||
# Modifier wrappers: keycode prefix -> the symbol shown on the front face.
|
||||
MOD_TAPS = {
|
||||
"LCTL_T": "⌃", "RCTL_T": "⌃",
|
||||
"LALT_T": "⌥", "RALT_T": "⌥",
|
||||
"LGUI_T": "⌘", "RGUI_T": "⌘",
|
||||
"LSFT_T": "⇧", "RSFT_T": "⇧",
|
||||
"HYPR_T": "Hyper", "MEH_T": "Meh",
|
||||
"ALL_T": "Hyper",
|
||||
}
|
||||
|
||||
# Chord wrappers: keycode prefix -> prefix string put in front of the inner legend.
|
||||
CHORDS = {
|
||||
"LCTL": "⌃", "C": "⌃",
|
||||
"LALT": "⌥", "A": "⌥",
|
||||
"LGUI": "⌘", "G": "⌘", "LCMD": "⌘",
|
||||
"LSFT": "⇧", "S": "⇧",
|
||||
"SCMD": "⇧⌘", "SGUI": "⇧⌘",
|
||||
"RCTL": "⌃", "RALT": "⌥", "RGUI": "⌘", "RSFT": "⇧",
|
||||
"HYPR": "Hyper+", "MEH": "Meh+",
|
||||
}
|
||||
|
||||
# Layer enum -> short name shown on a layer-tap front face.
|
||||
LAYER_NAMES = {
|
||||
"L_COLEMAK": "Colemak",
|
||||
"L_HANDSDOWN": "HandsDown",
|
||||
"L_MEDIA": "Media",
|
||||
"L_NAV": "Nav",
|
||||
"L_MOUSE": "Mouse",
|
||||
"L_SYM": "Sym",
|
||||
"L_NUM": "Num",
|
||||
"L_FUN": "Fun",
|
||||
}
|
||||
|
||||
# Tap-dance enum -> what a double tap does. Short: this goes on a 1u cap.
|
||||
TAP_DANCES = {
|
||||
"RAMO_TD_CMK": "2× CMK",
|
||||
"RAMO_TD_HD": "2× HDP",
|
||||
}
|
||||
|
||||
|
||||
# --- Hand-written notes ------------------------------------------------------
|
||||
# COMBO_ACTION combos run C code, so their effect cannot be read off key_combos[].
|
||||
# Keyed by the enum name in ramo_combos.c.
|
||||
|
||||
COMBO_ACTION_NOTES = {
|
||||
"HD_COMBO_Th": "th",
|
||||
"CMK_COMBO_Th": "th",
|
||||
"HD_COMBO_QU": "qu",
|
||||
}
|
||||
|
||||
# What each combo produces. Kept short — these go on a 1u keycap. The dead-key
|
||||
# combos show the dead key and the letter it makes: "´é" is ⌥E, then e.
|
||||
COMBO_RESULT_NOTES = {
|
||||
"HD_COMBO_ACUT": "´é",
|
||||
"HD_COMBO_GRV": "`è",
|
||||
"HD_COMBO_CIRC": "ˆê",
|
||||
"HD_COMBO_UMLA": "¨ü",
|
||||
"HD_COMBO_TILDE": "˜ñ",
|
||||
"HD_COMBO_CEDIL": "ç",
|
||||
"HD_COMBO_OE": "œ",
|
||||
"HD_COMBO_AE": "æ",
|
||||
"HD_COMBO_STAB": "⇧Tab",
|
||||
"HD_COMBO_APP": "▤",
|
||||
"HD_COMBO_CAPW": "CapsW",
|
||||
}
|
||||
|
||||
# The long form, for the list board. Anything absent falls back to the short form.
|
||||
COMBO_LONG_NOTES = {
|
||||
"HD_COMBO_ACUT": "acute dead key (⌥E) — é",
|
||||
"HD_COMBO_GRV": "grave dead key (⌥`) — è",
|
||||
"HD_COMBO_CIRC": "circumflex dead key (⌥I) — ê",
|
||||
"HD_COMBO_UMLA": "umlaut dead key (⌥U) — ü",
|
||||
"HD_COMBO_TILDE": "tilde dead key (⌥N) — ñ",
|
||||
"HD_COMBO_CEDIL": "ç (⌥C)",
|
||||
"HD_COMBO_OE": "œ (⌥Q)",
|
||||
"HD_COMBO_AE": "æ (⌥')",
|
||||
"HD_COMBO_STAB": "Shift-Tab",
|
||||
"HD_COMBO_APP": "App menu",
|
||||
"HD_COMBO_QU": "qu — hold past 200 ms to drop the u and leave q",
|
||||
"HD_COMBO_CAPW": "Caps Word on or off",
|
||||
}
|
||||
|
||||
# Behaviors that are always on. These live in process_record / matrix_scan, not in
|
||||
# any table a parser could read, so they are transcribed here.
|
||||
#
|
||||
# The last two fields are a file and a string to find in it. The generator turns
|
||||
# them into a file:line anchor, so the line number cannot rot when the file moves
|
||||
# — and if the string ever disappears, the export stops rather than lying.
|
||||
# {TAPPING_TERM} is filled in from config.h.
|
||||
ALWAYS_ON = [
|
||||
("Linger brackets",
|
||||
"Tap ( or {{ for one bracket. Hold past {TAPPING_TERM} ms for the pair, "
|
||||
"caret inside: (|)",
|
||||
"ramo_linger.c", "bool linger_track("),
|
||||
("Caps Word",
|
||||
"Letters get ⇧. Digits, -, _, ⌫ keep it on. "
|
||||
"Space → _, double space → real space + off.",
|
||||
"ramo_linger.c", "bool caps_word_press_user("),
|
||||
("Shift + ⌫ = ⌦",
|
||||
"Keeps Delete reachable on Hands Down, where it left the thumb row.",
|
||||
"ramo_process_record.c", "keycode == KC_BSPC"),
|
||||
("Comma-leader shift",
|
||||
"Hands Down only. Type , then a letter within {TAPPING_TERM} ms → "
|
||||
"the comma goes, the letter gets ⇧.",
|
||||
"ramo_process_record.c", "prior_keycode == KC_COMM"),
|
||||
("Combo hold on qu",
|
||||
"Hold the W+M combo past {TAPPING_TERM} ms and the u is deleted, leaving q.",
|
||||
"ramo_combos.c", "void combo_check_for_linger("),
|
||||
("Base switch",
|
||||
"NAV layer, two left keys. Double-tap only, so a stray press cannot "
|
||||
"change your base.",
|
||||
"ramo_tap_dance.c", "ramo_TD_make_colemak_default"),
|
||||
]
|
||||
247
users/raphaelmor/tools/kle_render.py
Normal file
247
users/raphaelmor/tools/kle_render.py
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: MIT
|
||||
# Copyright (c) 2026 Raphaël Mor
|
||||
"""Draw a KLE document as an SVG, in KLE's own style, with the legends included.
|
||||
|
||||
KLE's "Download SVG" produces good-looking keycaps and **no text at all** — the
|
||||
export carries 296 keycap groups and zero <text> nodes. So this module emits the
|
||||
same markup KLE does, copied from one of its exports, and then draws the legends
|
||||
on top.
|
||||
|
||||
python3 tools/kle_render.py corne-v4-1-ramo.json -o /tmp/board.svg
|
||||
|
||||
Everything below is at KLE's native 54 px key unit, so the numbers match its
|
||||
output one for one:
|
||||
|
||||
shell x*54 + 1, y*54 + 1, w*54 - 2, h*54 - 2 rx 5
|
||||
plate shell + (6, 3), shell - (12, 12) rx 5, lightness + 0.05
|
||||
ghost shell only, no plate
|
||||
decal nothing but its legends
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from xml.sax.saxutils import escape
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
from kle_export import LABEL_MAP # noqa: E402
|
||||
|
||||
UNIT = 54 # KLE's key unit, in pixels
|
||||
PLATE_DX, PLATE_DY = 6, 3
|
||||
PLATE_DW, PLATE_DH = 12, 12
|
||||
OUTER_PAD = 10 # around the plate rectangle
|
||||
INNER_PAD = 5 # between the plate rectangle and the keys
|
||||
TEXT_PAD = 1.5
|
||||
FRONT_SCALE = 0.78 # the front lip is much shorter than the keytop
|
||||
DESCENDER = 0.22 # of an em; the tail of a comma has to stay on the keytop
|
||||
BOTTOM_LIFT = 0.45 # of an em, on top of the descender. The front lip carries
|
||||
# visual weight below the plate, so a legend pinned to the
|
||||
# plate's bottom edge reads lower than it measures.
|
||||
|
||||
# Copied verbatim from a KLE export, so the caps render the way KLE draws them.
|
||||
STYLE = """ <style type='text/css'>
|
||||
.keycap .border { stroke: black; stroke-width: 2; }
|
||||
.keycap .inner.border { stroke: rgba(0,0,0,.1); }
|
||||
</style>
|
||||
<defs>
|
||||
<linearGradient id="DCS">
|
||||
<stop offset="0%" stop-color="black" stop-opacity="0"/>
|
||||
<stop offset="40%" stop-color="black" stop-opacity="0.1"/>
|
||||
<stop offset="60%" stop-color="black" stop-opacity="0.1"/>
|
||||
<stop offset="100%" stop-color="black" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="DSA">
|
||||
<stop offset="0%" stop-color="black" stop-opacity="0.1"/>
|
||||
<stop offset="10%" stop-color="black" stop-opacity="0.1"/>
|
||||
<stop offset="100%" stop-color="black" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="SA" xlink:href="#DSA" />
|
||||
</defs>"""
|
||||
|
||||
|
||||
def deserialize(document: list) -> tuple[dict, list[dict]]:
|
||||
"""KLE document -> (metadata, keys with absolute x/y/w and their properties)."""
|
||||
meta = document[0] if isinstance(document[0], dict) else {}
|
||||
rows = document[1:] if isinstance(document[0], dict) else document
|
||||
|
||||
keys: list[dict] = []
|
||||
state = {"c": "#cccccc", "t": "#000000", "a": 4, "w": 1.0, "f": 3,
|
||||
"d": False, "g": False}
|
||||
y = 0.0
|
||||
for row in rows:
|
||||
if isinstance(row, dict):
|
||||
continue
|
||||
x = 0.0
|
||||
for item in row:
|
||||
if isinstance(item, dict):
|
||||
x += item.get("x", 0)
|
||||
y += item.get("y", 0)
|
||||
for prop in ("c", "t", "a", "d", "g", "w", "f"):
|
||||
if prop in item:
|
||||
state[prop] = item[prop]
|
||||
continue
|
||||
keys.append({"x": x, "y": y, "w": state["w"], "labels": item,
|
||||
"c": state["c"], "t": state["t"], "a": state["a"],
|
||||
"f": state["f"], "d": state["d"], "g": state["g"]})
|
||||
x += state["w"]
|
||||
state["w"] = 1.0 # KLE resets width and decal after every key
|
||||
state["d"] = False
|
||||
y += 1.0
|
||||
return meta, keys
|
||||
|
||||
|
||||
def place(labels: str, align: int) -> dict[int, str]:
|
||||
"""Label string -> {visual position: text}, using KLE's label map."""
|
||||
out = {}
|
||||
for slot, text in enumerate(labels.split("\n")):
|
||||
if not text or slot >= 12:
|
||||
continue
|
||||
visual = LABEL_MAP[align][slot]
|
||||
if visual >= 0:
|
||||
out[visual] = text
|
||||
return out
|
||||
|
||||
|
||||
def keytop_color(color: str) -> str:
|
||||
"""KLE's keytop is the cap colour with 0.05 added to its HSL lightness.
|
||||
|
||||
Checked against its own export: #424242 becomes #4f4f4f.
|
||||
"""
|
||||
if not (color.startswith("#") and len(color) == 7):
|
||||
return color
|
||||
rgb = [int(color[i:i + 2], 16) / 255 for i in (1, 3, 5)]
|
||||
low, high = min(rgb), max(rgb)
|
||||
lightness = (low + high) / 2
|
||||
target = min(1.0, lightness + 0.05)
|
||||
if high == low: # grey: no hue to preserve
|
||||
return "#" + "%02x" % round(target * 255) * 3
|
||||
delta = high - low
|
||||
sat = delta / (2 - high - low if lightness > 0.5 else high + low)
|
||||
hue = {high: 0.0}
|
||||
if high == rgb[0]:
|
||||
hue = ((rgb[1] - rgb[2]) / delta) % 6
|
||||
elif high == rgb[1]:
|
||||
hue = (rgb[2] - rgb[0]) / delta + 2
|
||||
else:
|
||||
hue = (rgb[0] - rgb[1]) / delta + 4
|
||||
chroma = (1 - abs(2 * target - 1)) * sat
|
||||
second = chroma * (1 - abs(hue % 2 - 1))
|
||||
base = target - chroma / 2
|
||||
parts = [(chroma, second, 0), (second, chroma, 0), (0, chroma, second),
|
||||
(0, second, chroma), (second, 0, chroma), (chroma, 0, second)]
|
||||
return "#" + "".join("%02x" % round((c + base) * 255)
|
||||
for c in parts[int(hue)])
|
||||
|
||||
|
||||
def wrap(text: str, size: float, room: float) -> list[str]:
|
||||
"""Split on a space when a legend is too wide. SVG will not wrap for us."""
|
||||
if len(text) * size * 0.55 <= room or " " not in text:
|
||||
return [text]
|
||||
words = text.split(" ")
|
||||
best, score = 1, None
|
||||
for cut in range(1, len(words)):
|
||||
spread = abs(len(" ".join(words[:cut])) - len(" ".join(words[cut:])))
|
||||
if score is None or spread < score:
|
||||
best, score = cut, spread
|
||||
return [" ".join(words[:best]), " ".join(words[best:])]
|
||||
|
||||
|
||||
def draw_text(x, y, anchor, text, size, color, room) -> list[str]:
|
||||
lines = wrap(text, size, room)
|
||||
step = size * 1.05
|
||||
top = y - step * (len(lines) - 1) / 2
|
||||
return [f'<text x="{x:.1f}" y="{top + i * step:.1f}" font-size="{size:.1f}" '
|
||||
f'fill="{color}" text-anchor="{anchor}">{escape(line)}</text>'
|
||||
for i, line in enumerate(lines)]
|
||||
|
||||
|
||||
def keycap(key: dict) -> list[str]:
|
||||
"""One keycap group, in the markup KLE's own SVG export uses."""
|
||||
x, y = key["x"] * UNIT + 1, key["y"] * UNIT + 1
|
||||
w, h = key["w"] * UNIT - 2, UNIT - 2
|
||||
classes = ("ghosted keycap" if key["g"]
|
||||
else " decal keycap" if key["d"] else " keycap")
|
||||
out = [f"<g class='{classes}'>"]
|
||||
if not key["d"]:
|
||||
shell = (f'x="{x:g}" y="{y:g}" width="{w:g}" height="{h:g}" '
|
||||
f'rx="5" fill="{key["c"]}"')
|
||||
out += [f' <rect {shell} class="outer border"/>',
|
||||
f' <rect {shell}/>']
|
||||
if not key["g"]:
|
||||
top = (f'x="{x + PLATE_DX:g}" y="{y + PLATE_DY:g}" '
|
||||
f'width="{w - PLATE_DW:g}" height="{h - PLATE_DH:g}" '
|
||||
f'rx="5" fill="{keytop_color(key["c"])}"')
|
||||
out += [f' <rect {top} class="inner border"/>',
|
||||
f' <rect {top}/>']
|
||||
|
||||
# KLE stops here. Everything below is the text it does not export.
|
||||
px, py = x + PLATE_DX, y + PLATE_DY
|
||||
pw, ph = w - PLATE_DW, h - PLATE_DH
|
||||
size = 6 + 2 * key["f"]
|
||||
color = key["t"] if isinstance(key["t"], str) else "#000000"
|
||||
color = color.split("\n")[0] or "#000000"
|
||||
columns = [(px + TEXT_PAD, "start"), (px + pw / 2, "middle"),
|
||||
(px + pw - TEXT_PAD, "end")]
|
||||
# The front lip is only 9 px tall on a 52 px cap, so its legends are smaller
|
||||
# than the ones on the keytop or they spill over the plate edge.
|
||||
lip_top, lip_h = py + ph, h - PLATE_DY - ph
|
||||
lip_size = size * FRONT_SCALE
|
||||
rows = [(py + TEXT_PAD + size * 0.85, size), # top of the keytop
|
||||
(py + ph / 2 + size * 0.35, size), # its middle
|
||||
(py + ph - TEXT_PAD - size * (DESCENDER + BOTTOM_LIFT), size),
|
||||
(lip_top + lip_h / 2 + lip_size * 0.35, lip_size)]
|
||||
for visual, text in place(key["labels"], key["a"]).items():
|
||||
cx, anchor = columns[visual % 3]
|
||||
baseline, font = rows[visual // 3]
|
||||
room = pw - 2 * TEXT_PAD if visual % 3 == 1 else pw
|
||||
out += [" " + line for line in
|
||||
draw_text(cx, baseline, anchor, text, font, color, room)]
|
||||
out.append("</g>")
|
||||
return out
|
||||
|
||||
|
||||
def render(document: list) -> str:
|
||||
meta, keys = deserialize(document)
|
||||
content_w = max(k["x"] + k["w"] for k in keys) * UNIT
|
||||
content_h = (max(k["y"] for k in keys) + 1) * UNIT
|
||||
plate_w = content_w + 2 * INNER_PAD
|
||||
plate_h = content_h + 2 * INNER_PAD
|
||||
width = plate_w + 2 * OUTER_PAD
|
||||
height = plate_h + 2 * OUTER_PAD
|
||||
|
||||
parts = [
|
||||
f"<svg width='{width:g}px' height='{height:g}px' "
|
||||
f"viewBox='0 0 {width:g} {height:g}' "
|
||||
f"xmlns='http://www.w3.org/2000/svg' "
|
||||
f'xmlns:xlink="http://www.w3.org/1999/xlink" '
|
||||
f"font-family='Helvetica Neue, Helvetica, Arial, sans-serif'>",
|
||||
STYLE,
|
||||
f" <g transform='translate({OUTER_PAD},{OUTER_PAD})'>",
|
||||
f' <rect width="{plate_w:g}" height="{plate_h:g}" stroke="#ddd" '
|
||||
f'stroke-width="1" fill="{meta.get("backcolor", "#eeeeee")}" rx="6"/>',
|
||||
f" <g transform='translate({INNER_PAD},{INNER_PAD})'>",
|
||||
]
|
||||
for key in keys:
|
||||
parts += [" " + line for line in keycap(key)]
|
||||
parts += [" </g>", " </g>", "</svg>"]
|
||||
return "\n".join(parts) + "\n"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser(description=__doc__,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
ap.add_argument("input", type=Path)
|
||||
ap.add_argument("-o", "--output", type=Path, default=Path("/tmp/kle.svg"))
|
||||
args = ap.parse_args()
|
||||
|
||||
args.output.write_text(render(json.loads(args.input.read_text())))
|
||||
print(f"wrote {args.output}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Loading…
Reference in a new issue