ergodox-firmware/generate_layout.rb

701 lines
32 KiB
Ruby
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#!/usr/bin/env ruby
# -*- encoding: utf-8 -*-
# Copyright muflax <mail@muflax.com>, 2015
# License: GNU GPLv3 (or later) <http://www.gnu.org/copyleft/gpl.html>
GC.disable
# avoid having to require muflax
class Object
def blank?
respond_to?(:empty?) ? !!empty? : !self
end
end
class String
BLANK_RE = /\A[[:space:]]*\z/
def blank?
BLANK_RE === self
end
end
LayoutDir = "src/keyboard"
LayoutFile = "#{LayoutDir}/layout.c"
puts "generating #{LayoutFile}..."
class Layer
@@layers = {}
@@primitives = 0
def initialize name
case name
when Symbol
raise "layer already defined: #{name}" if @@layers.include? name
@@primitives += 1
@@layers[name] = @@primitives
when Array
name.each do |prim|
raise "unknown layers: #{prim}" if not @@layers.include? prim
raise "can only combo primitive layers: #{prim}" if not prim.is_a? Symbol
end
combo = name.reduce(0) do |code, prim|
code | (@@layers[prim])
end
combo_name = name.sort_by{|p| @@layers[p]}.join("_").to_sym
@@layers[combo_name] = combo
else
raise "invalid layer definition: #{name}"
end
end
end
Keydef = Struct.new :code, :func
class Key
attr_reader :keydefs, :name, :row, :col
Layers = [ :LB, :LP, :LN, :LF, :LD ]
Functions = {
#
"basic" => "&kbfun_normal_press_release",
"media" => "&kbfun_mediakey_press_release",
#
"mod" => "&kbfun_modifier_press_release",
"sticky_mod" => "&kbfun_modifier_sticky",
#
"layer" => "&kbfun_layer_press_release",
"sticky" => "&kbfun_layer_sticky",
#
"shift" => "&kbfun_shift_press_release",
"ctrl" => "&kbfun_control_press_release",
"alt" => "&kbfun_alt_press_release",
"win" => "&kbfun_win_press_release",
#
"shift_once" => "&kbfun_shift_press_release_once",
"ctrl_once" => "&kbfun_control_press_release_once",
"alt_once" => "&kbfun_alt_press_release_once",
"win_once" => "&kbfun_win_press_release_once",
#
"shift_layer" => "&kbfun_shift_layer_press_release",
"ctrl_layer" => "&kbfun_control_layer_press_release",
"alt_layer" => "&kbfun_alt_layer_press_release",
"win_layer" => "&kbfun_win_layer_press_release",
#
"capslock" => "&kbfun_capslock_press_release",
#
"S" => "&kbfun_shift_press_release",
"C" => "&kbfun_control_press_release",
"A" => "&kbfun_alt_press_release",
"M" => "&kbfun_alt_press_release",
"W" => "&kbfun_win_press_release",
"CL" => "&kbfun_capslock_press_release",
"_L" => "&kbfun_layer_sticky",
"_M" => "&kbfun_modifier_sticky",
"_C" => "&kbfun_control_layer_press_release",
"_A" => "&kbfun_alt_layer_press_release",
"_W" => "&kbfun_win_layer_press_release",
}
Keys = {
# letters
"a" => "KEY_a_A",
"b" => "KEY_b_B",
"c" => "KEY_c_C",
"d" => "KEY_d_D",
"e" => "KEY_e_E",
"f" => "KEY_f_F",
"g" => "KEY_g_G",
"h" => "KEY_h_H",
"i" => "KEY_i_I",
"j" => "KEY_j_J",
"k" => "KEY_k_K",
"l" => "KEY_l_L",
"m" => "KEY_m_M",
"n" => "KEY_n_N",
"o" => "KEY_o_O",
"p" => "KEY_p_P",
"q" => "KEY_q_Q",
"r" => "KEY_r_R",
"s" => "KEY_s_S",
"t" => "KEY_t_T",
"u" => "KEY_u_U",
"v" => "KEY_v_V",
"w" => "KEY_w_W",
"x" => "KEY_x_X",
"y" => "KEY_y_Y",
"z" => "KEY_z_Z",
# numbers
"0" => "KEY_0_RightParenthesis",
"1" => "KEY_1_Exclamation",
"2" => "KEY_2_At",
"3" => "KEY_3_Pound",
"4" => "KEY_4_Dollar",
"5" => "KEY_5_Percent",
"6" => "KEY_6_Caret",
"7" => "KEY_7_Ampersand",
"8" => "KEY_8_Asterisk",
"9" => "KEY_9_LeftParenthesis",
# punctuation
"\\" => "KEY_Backslash_Pipe",
"{" => ["KEY_LeftBracket_LeftBrace", "shift_once"],
"}" => ["KEY_RightBracket_RightBrace", "shift_once"],
"[" => "KEY_LeftBracket_LeftBrace",
"]" => "KEY_RightBracket_RightBrace",
"," => "KEY_Comma_LessThan",
"-" => "KEY_Dash_Underscore",
"=" => "KEY_Equal_Plus",
"`" => "KEY_GraveAccent_Tilde",
"." => "KEY_Period_GreaterThan",
"\'" => "KEY_SingleQuote_DoubleQuote",
";" => "KEY_Semicolon_Colon",
"/" => "KEY_Slash_Question",
"~" => ["KEY_GraveAccent_Tilde", "shift_once"],
"%" => ["KEY_5_Percent", "shift_once"],
"*" => ["KEY_8_Asterisk", "shift_once"],
":" => ["KEY_Semicolon_Colon", "shift_once"],
"^" => ["KEY_6_Caret", "shift_once"],
"<" => ["KEY_Comma_LessThan", "shift_once"],
">" => ["KEY_Period_GreaterThan", "shift_once"],
"?" => ["KEY_Slash_Question", "shift_once"],
"!" => ["KEY_1_Exclamation", "shift_once"],
"(" => ["KEY_9_LeftParenthesis", "shift_once"],
")" => ["KEY_0_RightParenthesis", "shift_once"],
"|" => ["KEY_Backslash_Pipe", "shift_once"],
"@" => ["KEY_2_At", "shift_once"],
"\"" => ["KEY_SingleQuote_DoubleQuote", "shift_once"],
"_" => ["KEY_Dash_Underscore", "shift_once"],
"+" => ["KEY_Equal_Plus", "shift_once"],
"$" => ["KEY_4_Dollar", "shift_once"],
"&" => ["KEY_7_Ampersand", "shift_once"],
"#" => ["KEY_3_Pound", "shift_once"],
"hash" => ["KEY_3_Pound", "shift_once"],
"hsh" => ["KEY_3_Pound", "shift_once"],
# enter etc
"enter" => "KEY_ReturnEnter",
"return" => "KEY_ReturnEnter",
"ret" => "KEY_ReturnEnter",
"space" => "KEY_Spacebar",
"spc" => "KEY_Spacebar",
"tab" => "KEY_Tab",
"backspace" => "KEY_DeleteBackspace",
"bksp" => "KEY_DeleteBackspace",
"bs" => "KEY_DeleteBackspace",
"delete" => "KEY_DeleteForward",
"del" => "KEY_DeleteForward",
"home" => "KEY_Home",
"hom" => "KEY_Home",
"end" => "KEY_End",
"page_up" => "KEY_PageUp",
"pgup" => "KEY_PageUp",
"p-^" => "KEY_PageUp",
"page_down" => "KEY_PageDown",
"pgdn" => "KEY_PageDown",
"p-v" => "KEY_PageDown",
"up" => "KEY_UpArrow",
"a-^" => "KEY_UpArrow",
"down" => "KEY_DownArrow",
"a-v" => "KEY_DownArrow",
"left" => "KEY_LeftArrow",
"a-<" => "KEY_LeftArrow",
"right" => "KEY_RightArrow",
"a->" => "KEY_RightArrow",
"escape" => "KEY_Escape",
"esc" => "KEY_Escape",
"insert" => "KEY_Insert",
"ins" => "KEY_Insert",
"menu" => "KEY_Application",
"men" => "KEY_Application",
# modifiers
"alt" => ["MOD_KEY_LeftAlt", "mod" ],
"A" => ["MOD_KEY_LeftAlt", "mod" ],
"M" => ["MOD_KEY_LeftAlt", "mod" ],
"alt_gr" => ["MOD_KEY_RightAlt", "mod" ],
"umlaut" => ["MOD_KEY_RightAlt", "mod" ],
# "UM" => ["MOD_KEY_RightAlt", "mod" ],
"UM" => ["MOD_KEY_RightAlt", "sticky_mod" ],
"ctrl" => ["MOD_KEY_LeftControl", "mod" ],
"C" => ["MOD_KEY_LeftControl", "mod" ],
"control_l" => ["MOD_KEY_LeftControl", "mod" ],
"control_r" => ["MOD_KEY_RightControl", "mod" ],
"win" => ["MOD_KEY_LeftGUI", "mod" ],
"W" => ["MOD_KEY_LeftGUI", "mod" ],
"shift_l" => ["MOD_KEY_LeftShift", "mod" ],
"S_l" => ["MOD_KEY_LeftShift", "mod" ],
"shift_r" => ["MOD_KEY_RightShift", "mod" ],
"S_r" => ["MOD_KEY_RightShift", "mod" ],
"scroll_lock" => "KEY_ScrollLock",
"SCL" => "KEY_ScrollLock",
# null
"NULL" => "KEY_NULL",
"NUL" => "KEY_NULL",
"" => "KEY_NULL",
# functions 1..24
"f1" => "KEY_F1",
"f2" => "KEY_F2",
"f3" => "KEY_F3",
"f4" => "KEY_F4",
"f5" => "KEY_F5",
"f6" => "KEY_F6",
"f7" => "KEY_F7",
"f8" => "KEY_F8",
"f9" => "KEY_F9",
"f10" => "KEY_F10",
"f11" => "KEY_F11",
"f12" => "KEY_F12",
"f13" => "KEY_F13",
"f14" => "KEY_F14",
"f15" => "KEY_F15",
"f16" => "KEY_F16",
"f17" => "KEY_F17",
"f18" => "KEY_F18",
"f19" => "KEY_F19",
"f20" => "KEY_F20",
"f21" => "KEY_F21",
"f22" => "KEY_F22",
"f23" => "KEY_F23",
"f24" => "KEY_F24",
# keypad stuff
"kp_1" => "KEYPAD_1_End",
"kp_2" => "KEYPAD_2_DownArrow",
"kp_3" => "KEYPAD_3_PageDown",
"kp_4" => "KEYPAD_4_LeftArrow",
"kp_5" => "KEYPAD_5",
"kp_6" => "KEYPAD_6_RightArrow",
"kp_7" => "KEYPAD_7_Home",
"kp_8" => "KEYPAD_8_UpArrow",
"kp_9" => "KEYPAD_9_PageUp",
"kp_0" => "KEYPAD_0_Insert",
#
"kp_." => "KEYPAD_Period_Delete",
"kp_/" => "KEYPAD_Slash",
"kp_*" => "KEYPAD_Asterisk",
"kp_-" => "KEYPAD_Minus",
"kp_+" => "KEYPAD_Plus",
"kp_en" => "KEYPAD_ENTER",
# mediakeys
"MD_PP" => ["MEDIAKEY_PLAY_PAUSE", "media"],
"MD_PT" => ["MEDIAKEY_PREV_TRACK", "media"],
"MD_NT" => ["MEDIAKEY_NEXT_TRACK", "media"],
"MD_S" => ["MEDIAKEY_STOP", "media"],
"MD_M" => ["MEDIAKEY_AUDIO_MUTE", "media"],
"MD_VU" => ["MEDIAKEY_AUDIO_VOL_UP", "media"],
"MD_VD" => ["MEDIAKEY_AUDIO_VOL_DOWN", "media"],
"record" => ["MEDIAKEY_RECORD", "media"],
"rewind" => ["MEDIAKEY_REWIND", "media"],
"eject" => ["MEDIAKEY_EJECT", "media"],
"cc_config" => ["MEDIAKEY_CC_CONFIG", "media"],
"email" => ["MEDIAKEY_EMAIL", "media"],
"calculator" => ["MEDIAKEY_CALCULATOR", "media"],
"local_browser" => ["MEDIAKEY_LOCAL_BROWSER", "media"],
"bs_search" => ["MEDIAKEY_BROWSER_SEARCH", "media"],
"bs_home" => ["MEDIAKEY_BROWSER_HOME", "media"],
"bs_b" => ["MEDIAKEY_BROWSER_BACK", "media"],
"bs_f" => ["MEDIAKEY_BROWSER_FORWARD", "media"],
"bs_stop" => ["MEDIAKEY_BROWSER_STOP", "media"],
"bs_refresh" => ["MEDIAKEY_BROWSER_REFRESH", "media"],
"bs_bookmarks" => ["MEDIAKEY_BROWSER_BOOKMARKS", "media"],
# other weird shit
"pause" => "KEY_Pause",
"sysreq" => "KEY_SysReq_Attention",
"sreq" => "KEY_SysReq_Attention",
"SR" => "KEY_SysReq_Attention",
#
"clear" => "KEY_Clear",
# "Clear_Again" => "KEY_Clear_Again", # wut
"printscreen" => "KEY_PrintScreen",
"prtscr" => "KEY_PrintScreen",
"PS" => "KEY_PrintScreen",
#
"execute" => "KEY_Execute",
"exe" => "KEY_Execute",
"help" => "KEY_Help",
# "menu" => "KEY_Menu", # NOT normal menu!
"select" => "KEY_Select",
"selct" => "KEY_Select",
"sel" => "KEY_Select",
"stop" => "KEY_Stop",
#
"again" => "KEY_Again",
"redo" => "KEY_Again",
"undo" => "KEY_Undo",
#
"cut" => "KEY_Cut",
"copy" => "KEY_Copy",
"paste" => "KEY_Paste",
#
"find" => "KEY_Find",
#
"mute" => "KEY_Mute",
#
"volume_up" => "KEY_VolumeUp",
"volume_down" => "KEY_VolumeDown",
"vol_up" => "KEY_VolumeUp",
"vol_down" => "KEY_VolumeDown",
"volumeup" => "KEY_VolumeUp",
"volumedown" => "KEY_VolumeDown",
"voldown" => "KEY_VolumeDown",
"volup" => "KEY_VolumeUp",
"voldn" => "KEY_VolumeDown",
"vlup" => "KEY_VolumeUp",
"vldn" => "KEY_VolumeDown",
"vup" => "KEY_VolumeUp",
"vdn" => "KEY_VolumeDown",
}
Names = {
# unique key identifiers, must have a name for each of the 80 physical keys
# NOTE: the mapping to the matrix is a bit weird, so be careful
# name row col
"Ln┳o" => [ 5, 0 ],
"Ln┳4" => [ 5, 1 ],
"Ln┳3" => [ 5, 2 ],
"Ln┳2" => [ 5, 3 ],
"Ln┳1" => [ 5, 4 ],
"Ln┳0" => [ 5, 5 ],
"Ln┳d" => [ 5, 6 ],
"Lt┳o" => [ 4, 0 ],
"Lt┳4" => [ 4, 1 ],
"Lt┳3" => [ 4, 2 ],
"Lt┳2" => [ 4, 3 ],
"Lt┳1" => [ 4, 4 ],
"Lt┳0" => [ 4, 5 ],
"Lt┳d" => [ 4, 6 ],
"Lh┳o" => [ 3, 0 ],
"Lh┳4" => [ 3, 1 ],
"Lh┳3" => [ 3, 2 ],
"Lh┳2" => [ 3, 3 ],
"Lh┳1" => [ 3, 4 ],
"Lh┳0" => [ 3, 5 ],
"Lb┳o" => [ 2, 0 ],
"Lb┳4" => [ 2, 1 ],
"Lb┳3" => [ 2, 2 ],
"Lb┳2" => [ 2, 3 ],
"Lb┳1" => [ 2, 4 ],
"Lb┳0" => [ 2, 5 ],
"Lb┳d" => [ 2, 6 ],
"Lu┳o" => [ 1, 0 ],
"Lu┳4" => [ 1, 1 ],
"Lu┳3" => [ 1, 2 ],
"Lu┳2" => [ 1, 3 ],
"Lu┳1" => [ 1, 4 ],
"Lz┳b" => [ 0, 5 ],
"Lz┳c" => [ 0, 6 ],
"Ly┳a" => [ 1, 5 ],
"Ly┳b" => [ 1, 6 ],
"Ly┳c" => [ 0, 4 ],
"Lx┳a" => [ 0, 3 ],
"Lx┳b" => [ 0, 2 ],
"Lx┳c" => [ 0, 1 ],
"Rn┳d" => [ 5, 7 ],
"Rn┳0" => [ 5, 8 ],
"Rn┳1" => [ 5, 9 ],
"Rn┳2" => [ 5, 10 ],
"Rn┳3" => [ 5, 11 ],
"Rn┳4" => [ 5, 12 ],
"Rn┳o" => [ 5, 13 ],
"Rt┳d" => [ 4, 7 ],
"Rt┳0" => [ 4, 8 ],
"Rt┳1" => [ 4, 9 ],
"Rt┳2" => [ 4, 10 ],
"Rt┳3" => [ 4, 11 ],
"Rt┳4" => [ 4, 12 ],
"Rt┳o" => [ 4, 13 ],
"Rh┳0" => [ 3, 8 ],
"Rh┳1" => [ 3, 9 ],
"Rh┳2" => [ 3, 10 ],
"Rh┳3" => [ 3, 11 ],
"Rh┳4" => [ 3, 12 ],
"Rh┳o" => [ 3, 13 ],
"Rb┳d" => [ 2, 7 ],
"Rb┳0" => [ 2, 8 ],
"Rb┳1" => [ 2, 9 ],
"Rb┳2" => [ 2, 10 ],
"Rb┳3" => [ 2, 11 ],
"Rb┳4" => [ 2, 12 ],
"Rb┳o" => [ 2, 13 ],
"Ru┳1" => [ 1, 9 ],
"Ru┳2" => [ 1, 10 ],
"Ru┳3" => [ 1, 11 ],
"Ru┳4" => [ 1, 12 ],
"Ru┳o" => [ 1, 13 ],
"Rz┳c" => [ 0, 7 ],
"Rz┳b" => [ 0, 8 ],
"Ry┳c" => [ 0, 9 ],
"Ry┳b" => [ 1, 7 ],
"Ry┳a" => [ 1, 8 ],
"Rx┳c" => [ 0, 12 ],
"Rx┳b" => [ 0, 11 ],
"Rx┳a" => [ 0, 10 ],
}
Layers.each.with_index do |layer, i|
Keys["#{layer}"] = ["#{i}", "layer"]
end
def initialize layers, name
@keydefs = Array.new(layers.size)
@name = name
@pos = Key::Names[name]
@row, @col = @pos
if layers.all?(&:empty?)
layers[0] = ["NUL"]
end
layers.each.with_index do |(key, type), i|
if type.nil? and key.nil?
@keydefs[i] = @keydefs[i-1]
next
end
# keycode and default keyfunc
keycode, default_type = Keys[key]
# keyfunc
type ||= default_type || "basic"
keyfunc = Functions[type]
raise "overwriting shift key: #{key}, #{type}" if default_type == "shift" and type != default_type
raise "key not found: #{key}" if keycode.nil?
raise "func not found: #{type}" if keyfunc.nil?
case type
when "layer", "sticky", "_L"
raise "invalid layer: #{key}" unless key.to_i < Key::Layers.size
when "mod", "sticky_mod", "_M"
raise "invalid modifier: #{key}" unless keycode.start_with? "MOD_KEY"
end
case keycode
when /^MOD_KEY/
case type
when "mod", "sticky_mod", "CL", "_M"; # pass
else
raise "invalid modifier keyfunc: #{key}, #{type}"
end
end
@keydefs[i] = Keydef.new(keycode, keyfunc)
end
raise "transparency error: #{layers}" if @keydefs.any?(&:nil?)
end
Dummy = Key.new(Key::Layers.map{"NULL"}, "dummy")
end
class Layout
def initialize name, keys
@name = name
@keys = keys
@rows = @keys.map(&:row).max + 1
@cols = @keys.map(&:col).max + 1
@matrix = @rows.times.map{Array.new(@cols)}
@keys.each do |key|
@matrix[key.row][key.col] = key
end
end
def keys_to_matrix type, method
matrix = []
@matrix.each.with_index do |row, row_i|
matrix << "{"
row.each.with_index do |key, col_i|
key ||= Key::Dummy # some matrix positions are empty
matrix << "\t{ // row #{row_i} x col #{col_i}"
Key::Layers.each.with_index do |layer_name, layer|
matrix << "\t\t(#{type}) #{key.keydefs[layer].send(method)},".ljust(60) + "// #{key.name} on #{layer_name}"
end
matrix << "\t},"
end
matrix << "},"
end
matrix.join("\n")
end
def save! file
puts "saving #{@name}..."
header =<<HEADER
// ----------------------------------------------------------------------------
// ergoDOX layout : saneo (generated)
// ----------------------------------------------------------------------------
HEADER
keys = keys_to_matrix "keycode", :code
funcs = keys_to_matrix "keyfunc", :func
File.open(LayoutFile, "w+") do |f|
f.puts header
f.puts "#define KB_LAYERS #{Key::Layers.size}"
f.puts "static const keycode PROGMEM _kb_layout_code[KB_ROWS][KB_COLUMNS][KB_LAYERS] = {", keys, "};"
f.puts "static const keyfunc PROGMEM _kb_layout_func[KB_ROWS][KB_COLUMNS][KB_LAYERS] = {", funcs, "};"
end
end
end
left =<<EOL
Lno ___ __ Ln4 ___ __ Ln3 ___ __ Ln2 ___ __ Ln1 ___ __ Ln0 ___ __ Lnd ___ __
_LB 0 1 2 3 4 5 6
_LP f11 f1 f2 f3 f4 f5 f6
_LN f11 f1 f2 f3 f4 f5 f6
_LF f11 f1 f2 f3 f4 f5 f6
_LD 0 1 2 3 4 5 6
Lto ___ __ Lt4 ___ __ Lt3 ___ __ Lt2 ___ __ Lt1 ___ __ Lt0 ___ __ Ltd ___ __
_LB tab x v l c w pause
_LP ~ [ ' ┃ < ┃ \\ ┃
_LN ┃ ┃ esc ┃ bs ┃ ret ┃ del ┃ ins ┃
_LF ┃ ┃ f24 ┃ f23 ┃ f22 ┃ f21 ┃ f24 ┃
_LD ┃ ┃ 8 ┃ 7 ┃ 6 ┃ 5 ┃ 9 ┃
Lh┳o ___ __ Lh┳4 ___ __ Lh┳3 ___ __ Lh┳2 ___ __ Lh┳1 ___ __ Lh┳0 ___ __
_LB ┃ UM ┃ u ┃ i ┃ a ┃ e ┃ o
_LP ┃ ┃ , ┃ \{ ┃ ? ┃ ! ┃ (
_LN ┃ ┃ a-< ┃ a-^ ┃ a-v ┃ a-> ┃ tab
_LF ┃ ┃ f14 ┃ f13 ┃ f12 ┃ f11 ┃ f20
_LD ┃ ┃ 4 ┃ 3 ┃ 2 ┃ 1 ┃ 0
Lb┳o ___ __ Lb┳4 ___ __ Lb┳3 ___ __ Lb┳2 ___ __ Lb┳1 ___ __ Lb┳0 ___ __ Lb┳d ___ __
_LB ┃ S_l CL ┃ % ┃ * ┃ : ┃ p ┃ z ┃ ret
_LP ┃ ┃ ` ┃ ^ ┃ | ┃ - ┃ @ ┃
_LN ┃ ┃ hom ┃ p-^ ┃ p-v ┃ end ┃ NUL ┃
_LF ┃ ┃ f18 ┃ f17 ┃ f16 ┃ f15 ┃ f19 ┃
_LD ┃ ┃ 8 ┃ 7 ┃ 6 ┃ 5 ┃ 9 ┃
Lu┳o ___ __ Lu┳4 ___ __ Lu┳3 ___ __ Lu┳2 ___ __ Lu┳1 ___ __
_LB ┃ a-< ┃ a-^ ┃ a-v ┃ a-> ┃ W
_LP ┃ ┃ ┃ ┃ ┃
_LN ┃ ┃ ┃ ┃ ┃
_LF ┃ ┃ ┃ ┃ ┃
_LD ┃ ┃ ┃ ┃ ┃
Lz┳b ___ __ Lz┳c ___ __
_LB ┃ esc ┃ SCL
_LP ┃ ┃
_LN ┃ ┃
_LF ┃ ┃
_LD ┃ ┃
Ly┳a ___ __ Ly┳b ___ __ Ly┳c ___ __
_LB ┃ spc ┃ C ┃ A
_LP ┃ ┃ ┃
_LN ┃ ┃ ┃
_LF ┃ ┃ ┃
_LD ┃ ┃ ┃
Lx┳a ___ __ Lx┳b ___ __ Lx┳c ___ __
_LB ┃ spc ┃ C ┃ A
_LP ┃ ┃ ┃
_LN ┃ ┃ ┃
_LF ┃ ┃ ┃
_LD ┃ ┃ ┃
EOL
right =<<EOL
Rn┳d ___ __ Rn┳0 ___ __ Rn┳1 ___ __ Rn┳2 ___ __ Rn┳3 ___ __ Rn┳4 ___ __ Rn┳o ___ __
_LB ┃ 5 ┃ 6 ┃ 7 ┃ 8 ┃ 9 ┃ 0 ┃ 0
_LP ┃ f5 ┃ f6 ┃ f7 ┃ f8 ┃ f9 ┃ f10 ┃ f12
_LN ┃ f5 ┃ f6 ┃ f7 ┃ f8 ┃ f9 ┃ f10 ┃ f12
_LF ┃ f5 ┃ f6 ┃ f7 ┃ f8 ┃ f9 ┃ f10 ┃ f12
_LD ┃ 5 ┃ 6 ┃ 7 ┃ 8 ┃ 9 ┃ 0 ┃ 0
Rt┳d ___ __ Rt┳0 ___ __ Rt┳1 ___ __ Rt┳2 ___ __ Rt┳3 ___ __ Rt┳4 ___ __ Rt┳o ___ __
_LB ┃ NUL ┃ k ┃ h ┃ g ┃ f ┃ q ┃ q
_LP ┃ ┃ = ┃ > ┃ " ┃ ] ┃ ` ┃ `
_LN ┃ ┃ 9 ┃ 5 ┃ 6 ┃ 7 ┃ 8 ┃ 8
_LF ┃ ┃ f24 ┃ f21 ┃ f22 ┃ f23 ┃ f24 ┃ f24
_LD ┃ ┃ 9 ┃ 5 ┃ 6 ┃ 7 ┃ 8 ┃ 8
Rh┳0 ___ __ Rh┳1 ___ __ Rh┳2 ___ __ Rh┳3 ___ __ Rh┳4 ___ __ Rh┳o ___ __
_LB ┃ s ┃ n ┃ r ┃ t ┃ d ┃ UM
_LP ┃ ) ┃ _ ┃ / ┃ \} ┃ . ┃
_LN ┃ 0 ┃ 1 ┃ 2 ┃ 3 ┃ 4 ┃
_LF ┃ f10 ┃ f1 ┃ f2 ┃ f3 ┃ f4 ┃
_LD ┃ 0 ┃ 1 ┃ 2 ┃ 3 ┃ 4 ┃
Rb┳d ___ __ Rb┳0 ___ __ Rb┳1 ___ __ Rb┳2 ___ __ Rb┳3 ___ __ Rb┳4 ___ __ Rb┳o ___ __
_LB ┃ ret ┃ b ┃ m ┃ j ┃ y ┃ ; ┃ S_r CL
_LP ┃ ┃ + ┃ $ ┃ & ┃ hsh ┃ ^ ┃
_LN ┃ ┃ 9 ┃ 5 ┃ 6 ┃ 7 ┃ 8 ┃
_LF ┃ ┃ f9 ┃ f5 ┃ f6 ┃ f7 ┃ f8 ┃
_LD ┃ ┃ 9 ┃ 5 ┃ 6 ┃ 7 ┃ 8 ┃
Ru┳1 ___ __ Ru┳2 ___ __ Ru┳3 ___ __ Ru┳4 ___ __ Ru┳o ___ __
_LB ┃ LN ┃ a-< ┃ a-^ ┃ a-v ┃ a->
_LP ┃ ┃ ┃ ┃ ┃
_LN ┃ ┃ ┃ ┃ ┃
_LF ┃ ┃ ┃ ┃ ┃
_LD ┃ ┃ ┃ ┃ ┃
Rz┳c ___ __ Rz┳b ___ __
_LB ┃ LF ┃ LF _C
_LP ┃ ┃
_LN ┃ ┃
_LF ┃ ┃
_LD ┃ ┃
Ry┳c ___ __ Ry┳b ___ __ Ry┳a ___ __
_LB ┃ men ┃ LF ┃ LP _L
_LP ┃ ┃ ┃
_LN ┃ ┃ ┃
_LF ┃ ┃ ┃
_LD ┃ ┃ ┃
Rx┳c ___ __ Rx┳b ___ __ Rx┳a ___ __
_LB ┃ men ┃ LF ┃ LP _L
_LP ┃ ┃ ┃
_LN ┃ ┃ ┃
_LF ┃ ┃ ┃
_LD ┃ ┃ ┃
EOL
keys_string = [
left,
right,
]
groups = keys_string.flat_map do |str|
str.gsub(/#.*$/, '').split(//).reject(&:blank?).map(&:lines)
end
keys_parsed = {}
groups.each.with_index(1) do |group, group_id|
layers = Key::Layers.size
# extract names
names = group.first
names = names.scan(/(\S+)(?:[ ]*\t[^\t]*){2}/).map{|name, _| name.strip}
keyrow = group[1..-1].reject(&:blank?)
# sanity check
if keyrow.size != layers
raise "wrong number of key layers in group ##{group_id}: #{keyrow}"
end
# remove first label
keyrow = keyrow.map{|l| l.split("").drop(1)}
(0..(keyrow.size-1)).step(layers).each do |row|
keyrow[row].size.times do |col|
key = layers.times.map do |layer|
keyrow[row+layer][col]
end
key = key.map{|l| l.strip.split(/\s+/, 2)}
name = names[col]
raise "name not unique: #{name}" if keys_parsed.include? name
raise "unknown name: #{name}" unless Key::Names.include? name
keys_parsed[name] = key
end
end
end
keys = keys_parsed.map {|name, layers| Key.new(layers, name)}
saneo = Layout.new :saneo, keys
saneo.save! LayoutFile