ergodox-firmware/generate_layout.rb

411 lines
23 KiB
Ruby
Executable File

#!/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>
# 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}..."
Layer = Struct.new :code, :up, :down
class Key
attr_reader :layers
Layers = [ :basic, :punc, :nav, :func ]
Functions = {
# down up
"basic" => [ "&kbfun_normal_press_release", ],
"media" => [ "&kbfun_mediakey_press_release", ],
# down up
"mod" => [ "&kbfun_modifier_press_release", ],
"sticky_mod" => [ "&kbfun_modifier_sticky", ],
# down up
"layer" => [ "&kbfun_layer_press_release", ],
"sticky" => [ "&kbfun_layer_sticky", ],
# down up
"shifted" => [ "&kbfun_shift_press_release", ],
"ctrled" => [ "&kbfun_control_press_release", ],
"capslock" => [ "&kbfun_capslock_press_release", ],
}
Keys = {
"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",
#
"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",
#
"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",
#
"\\" => "KEY_Backslash_Pipe",
"{" => ["KEY_LeftBracket_LeftBrace", "shifted"],
"}" => ["KEY_RightBracket_RightBrace", "shifted"],
"[" => "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", "shifted"],
"%" => ["KEY_5_Percent", "shifted"],
"*" => ["KEY_8_Asterisk", "shifted"],
":" => ["KEY_Semicolon_Colon", "shifted"],
"^" => ["KEY_6_Caret", "shifted"],
"<" => ["KEY_Comma_LessThan", "shifted"],
">" => ["KEY_Period_GreaterThan", "shifted"],
"?" => ["KEY_Slash_Question", "shifted"],
"!" => ["KEY_1_Exclamation", "shifted"],
"(" => ["KEY_9_LeftParenthesis", "shifted"],
")" => ["KEY_0_RightParenthesis", "shifted"],
"|" => ["KEY_Backslash_Pipe", "shifted"],
"@" => ["KEY_2_At", "shifted"],
"\"" => ["KEY_SingleQuote_DoubleQuote", "shifted"],
"_" => ["KEY_Dash_Underscore", "shifted"],
"+" => ["KEY_Equal_Plus", "shifted"],
"$" => ["KEY_4_Dollar", "shifted"],
"&" => ["KEY_7_Ampersand", "shifted"],
"#" => ["KEY_3_Pound", "shifted"],
#
"enter" => "KEY_ReturnEnter",
"return" => "KEY_ReturnEnter",
"space" => "KEY_Spacebar",
"tab" => "KEY_Tab",
"backspace" => "KEY_DeleteBackspace",
"delete" => "KEY_DeleteForward",
"home" => "KEY_Home",
"end" => "KEY_End",
"page_up" => "KEY_PageUp",
"page_down" => "KEY_PageDown",
"up" => "KEY_UpArrow",
"down" => "KEY_DownArrow",
"left" => "KEY_LeftArrow",
"right" => "KEY_RightArrow",
"escape" => "KEY_Escape",
"insert" => "KEY_Insert",
"menu" => "KEY_Application",
#
"alt" => ["MOD_KEY_LeftAlt", "mod"],
"alt_gr" => ["MOD_KEY_RightAlt", "mod"],
"umlaut" => ["MOD_KEY_RightAlt", "mod"],
"control" => ["MOD_KEY_LeftControl", "mod"],
"control_l" => ["MOD_KEY_LeftControl", "mod"],
"control_r" => ["MOD_KEY_RightControl", "mod"],
"win" => ["MOD_KEY_LeftGUI", "mod"],
"shift_l" => ["MOD_KEY_LeftShift", "mod"],
"shift_r" => ["MOD_KEY_RightShift", "mod"],
"scroll_lock" => "KEY_ScrollLock",
#
"NULL" => "KEY_NULL",
#
"audio_mute" => ["MEDIAKEY_AUDIO_MUTE", "media"],
"audio_vol_up" => ["MEDIAKEY_AUDIO_VOL_UP", "media"],
"audio_vol_down" => ["MEDIAKEY_AUDIO_VOL_DOWN", "media"],
"next_track" => ["MEDIAKEY_NEXT_TRACK", "media"],
"prev_track" => ["MEDIAKEY_PREV_TRACK", "media"],
"stop" => ["MEDIAKEY_STOP", "media"],
"play_pause" => ["MEDIAKEY_PLAY_PAUSE", "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"],
"browser_search" => ["MEDIAKEY_BROWSER_SEARCH", "media"],
"browser_home" => ["MEDIAKEY_BROWSER_HOME", "media"],
"browser_back" => ["MEDIAKEY_BROWSER_BACK", "media"],
"browser_forward" => ["MEDIAKEY_BROWSER_FORWARD", "media"],
"browser_stop" => ["MEDIAKEY_BROWSER_STOP", "media"],
"browser_refresh" => ["MEDIAKEY_BROWSER_REFRESH", "media"],
"browser_bookmarks" => ["MEDIAKEY_BROWSER_BOOKMARKS", "media"],
}
Layers.each.with_index do |layer, i|
Keys["#{layer}"] = ["#{i}", "layer"]
end
def initialize layers
@layers = Array.new(layers.size)
layers.each.with_index do |(key, type), i|
if type.nil? and key.nil?
@layers[i] = @layers[i-1]
next
end
# keycode and default keyfunc
keycode, default_type = Keys[key]
# keyfunc
type ||= default_type || "basic"
up, down = Functions[type]
down ||= up
raise "overwriting shifted key: #{key}, #{type}" if default_type == "shifted" and type != default_type
raise "key not found: #{key}" if keycode.nil?
raise "type not found: #{type}" if up.nil? or down.nil?
case type
when "layer", "sticky"
raise "invalid layer: #{key}" unless key.to_i < Key::Layers.size
when "mod", "sticky_mod"
raise "invalid modifier: #{key}" unless keycode.start_with? "MOD_KEY"
end
case keycode
when /^MOD_KEY/
case type
when "mod", "sticky_mod", "capslock"; # pass
else
raise "invalid modifier keyfunc: #{key}, #{type}"
end
end
@layers[i] = Layer.new(keycode, down, up)
end
raise "transparency error: #{layers}" if @layers.any?(&:nil?)
end
end
class Layout
def initialize name, keys
@name = name
@keys = keys
end
def keys_to_matrix type, method
Key::Layers.map.with_index do |_, layer|
"KB_MATRIX_LAYER(\n#{@keys.map{|key| "(#{type}) " + key.layers[layer].send(method)}.join(",\n")}),\n"
end.join("\n")
end
def save! file
puts "saving #{@name}..."
header =<<HEADER
// ----------------------------------------------------------------------------
// ergoDOX layout : saneo (generated)
// ----------------------------------------------------------------------------
HEADER
keys = keys_to_matrix "keycode", :code
downs = keys_to_matrix "keyfunc", :down
ups = keys_to_matrix "keyfunc", :up
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[KB_LAYERS][KB_ROWS][KB_COLUMNS] = { #{keys} };"
f.puts "static const keyfunc PROGMEM _kb_layout_press[KB_LAYERS][KB_ROWS][KB_COLUMNS] = { #{downs} };"
f.puts "static const keyfunc PROGMEM _kb_layout_release[KB_LAYERS][KB_ROWS][KB_COLUMNS] = { #{ups} };"
end
end
end
keys = [
# letter type punc type nav type func type
%w{ NULL }, %w{ }, %w{ }, %w{ }, # dummy key
#
# left hand
# number
# letter type punc type nav type func type
%w{ 0 }, %w{ f11 }, %w{ f11 }, %w{ f11 }, # 1.5
%w{ 1 }, %w{ f1 }, %w{ f1 }, %w{ f1 },
%w{ 2 }, %w{ f2 }, %w{ f2 }, %w{ f2 },
%w{ 3 }, %w{ f3 }, %w{ f3 }, %w{ f3 },
%w{ 4 }, %w{ f4 }, %w{ f4 }, %w{ f4 },
%w{ 5 }, %w{ f5 }, %w{ f5 }, %w{ f5 },
%w{ 6 }, %w{ f6 }, %w{ f6 }, %w{ f6 },
# top
# letter type punc type nav type func type
%w{ x }, %w{ ~ }, %w{ escape }, %w{ }, # 1.5
%w{ x }, %w{ ~ }, %w{ escape }, %w{ },
%w{ v }, %w{ [ }, %w{ backspace }, %w{ },
%w{ l }, %w{ ' }, %w{ enter }, %w{ },
%w{ c }, %w{ < }, %w{ delete }, %w{ },
%w{ w }, %w{ \\ }, %w{ insert }, %w{ },
%w{ tab }, %w{ }, %w{ }, %w{ }, # 1.5
# home
# letter type punc type nav type func type
%w{ umlaut }, %w{ }, %w{ }, %w{ }, # 1.5
%w{ u }, %w{ , }, %w{ left }, %w{ audio_mute },
%w{ i }, %w{ \{ }, %w{ up }, %w{ audio_vol_up },
%w{ a }, %w{ ? }, %w{ down }, %w{ audio_vol_down },
%w{ e }, %w{ ! }, %w{ right }, %w{ next_track },
%w{ o }, %w{ ( }, %w{ tab }, %w{ prev_track },
# bottom
# letter type punc type nav type func type
%w{ shift_l capslock }, %w{ }, %w{ }, %w{ }, # 1.5
%w{ % }, %w{ ` }, %w{ home }, %w{ },
%w{ * }, %w{ ^ }, %w{ page_up }, %w{ },
%w{ : }, %w{ | }, %w{ page_down }, %w{ },
%w{ p }, %w{ - }, %w{ end }, %w{ },
%w{ z }, %w{ @ }, %w{ }, %w{ },
%w{ enter }, %w{ }, %w{ }, %w{ }, # 1.5
# underbottom
# letter type punc type nav type func type
%w{ left }, %w{ }, %w{ }, %w{ },
%w{ up }, %w{ }, %w{ }, %w{ },
%w{ down }, %w{ }, %w{ }, %w{ },
%w{ right }, %w{ }, %w{ }, %w{ },
%w{ win }, %w{ }, %w{ }, %w{ },
# thumb-top
# letter type punc type nav type func type
%w{ scroll_lock }, %w{ }, %w{ }, %w{ },
%w{ func }, %w{ }, %w{ }, %w{ },
# thumb-double
# letter type punc type nav type func type
%w{ space }, %w{ }, %w{ }, %w{ },
%w{ control }, %w{ }, %w{ }, %w{ },
%w{ alt }, %w{ }, %w{ }, %w{ },
# thumb-home
# letter type punc type nav type func type
%w{ space }, %w{ }, %w{ }, %w{ },
%w{ control sticky_mod }, %w{ }, %w{ }, %w{ },
%w{ alt sticky_mod }, %w{ }, %w{ }, %w{ },
#
# right hand
#
# number
# letter type punc type nav type func type
%w{ 5 }, %w{ f5 }, %w{ f5 }, %w{ f5 }, # 1.5
%w{ 6 }, %w{ f6 }, %w{ f6 }, %w{ f6 },
%w{ 7 }, %w{ f7 }, %w{ f7 }, %w{ f7 },
%w{ 8 }, %w{ f8 }, %w{ f8 }, %w{ f8 },
%w{ 9 }, %w{ f9 }, %w{ f9 }, %w{ f9 },
%w{ 0 }, %w{ f10 }, %w{ f10 }, %w{ f10 },
%w{ 0 }, %w{ f12 }, %w{ f12 }, %w{ f12 },
# top
# letter type punc type nav type func type
%w{ NULL }, %w{ }, %w{ }, %w{ }, # 1.5
%w{ k }, %w{ = }, %w{ 9 }, %w{ f9 },
%w{ h }, %w{ > }, %w{ 5 }, %w{ f5 },
%w{ g }, %w{ " }, %w{ 6 }, %w{ f6 },
%w{ f }, %w{ ] }, %w{ 7 }, %w{ f7 },
%w{ q }, %w{ ` }, %w{ 8 }, %w{ f8 },
%w{ q }, %w{ ` }, %w{ 8 }, %w{ f8 }, # 1.5
# home
# letter type punc type nav type func type
%w{ s }, %w{ ) }, %w{ 0 }, %w{ f10 },
%w{ n }, %w{ _ }, %w{ 1 }, %w{ f1 },
%w{ r }, %w{ / }, %w{ 2 }, %w{ f2 },
%w{ t }, %w{ \} }, %w{ 3 }, %w{ f3 },
%w{ d }, %w{ . }, %w{ 4 }, %w{ f4 },
%w{ umlaut }, %w{ }, %w{ }, %w{ }, # 1.5
# bottom
# letter type punc type nav type func type
%w{ enter }, %w{ }, %w{ }, %w{ }, # 1.5
%w{ b }, %w{ + }, %w{ 9 }, %w{ f9 },
%w{ m }, %w{ $ }, %w{ 5 }, %w{ f5 },
%w{ j }, %w{ & }, %w{ 6 }, %w{ f6 },
%w{ y }, %w{ # }, %w{ 7 }, %w{ f7 },
%w{ ; }, %w{ ^ }, %w{ 8 }, %w{ f8 },
%w{ shift_r capslock }, %w{ }, %w{ }, %w{ }, # 1.5
# underbottom
# letter type punc type nav type func type
%w{ nav }, %w{ }, %w{ }, %w{ },
%w{ left }, %w{ }, %w{ }, %w{ },
%w{ up }, %w{ }, %w{ }, %w{ },
%w{ down }, %w{ }, %w{ }, %w{ },
%w{ right }, %w{ }, %w{ }, %w{ },
# thumb-top
# letter type punc type nav type func type
%w{ punc }, %w{ }, %w{ func }, %w{ },
%w{ nav }, %w{ func }, %w{ }, %w{ },
# thumb-double
# letter type punc type nav type func type
%w{ menu }, %w{ }, %w{ }, %w{ },
%w{ func }, %w{ }, %w{ }, %w{ },
%w{ punc sticky }, %w{ punc }, %w{ NULL }, %w{ NULL },
# thumb-home
# letter type punc type nav type func type
%w{ menu }, %w{ }, %w{ }, %w{ },
%w{ func }, %w{ }, %w{ }, %w{ },
%w{ punc sticky }, %w{ punc }, %w{ NULL }, %w{ NULL },
].each_slice(Key::Layers.size).map do |layers|
Key.new layers
end
saneo = Layout.new :saneo, keys
saneo.save! LayoutFile