module Twiddler class Config class KeyTable class KeyData ModAliases = Hash.new{|h,k| k}.merge!( "rsft" => "shift", "lsft" => "shift", "rctl" => "control", "lctl" => "control", "lalt" => "alt", "ralt" => "alt", "lgui" => "gui", "rgui" => "gui" ) def initialize(num, name) @code = num @data = { :unmod => name } @tags = {} end attr_reader :code def tag(name) @tags[name.to_sym] = true end def tag?(name) @tags.has_key?(name) end def modified(mod, value) @data[normalized(mod)] = value end def string(mod=nil) return @data[:unmod] if mod.nil? or mod.empty? if data = @data[normalized(mod)] return data else return "#{mod}-#{@data[:unmod]}" end end def has_mod?(mod) @data.has_key?(normalized(mod)) end def ===(chord) chord.single? and chord[0][0] == @code end def self.normalized(mod) unless Array === mod mod = mod.split("-") end return mod.map{|i| ModAliases[i.downcase]}.sort.join("-") end def normalized(mod) self.class.normalized(mod) end end def initialize @data = Hash.new{|h,k| KeyData.new(k, "U/#{k.inspect}")} @ordered = [] @current_tags = {} end def normalized(stroke) mods = KeyData::normalized(stroke[1]) return [stroke[0], mods] end def unknowns(list) return list.reject do |item| @data.has_key?(item) end end def start_tag(name) @current_tags[name.to_sym] = true end def end_tag(name) @current_tags.delete(name.to_sym) end def clear_tags @current_tags.clear end def tagged(name) @ordered.find_all do |key| key.tag?(name) end end def is_tagged?(idx, tag) @data[idx].tag?(tag) end def [](idx, mod=nil) return @data[idx].string(mod) end def []=(idx, value) if @data.has_key?(idx) @ordered.delete(@data[idx]) end data = KeyData.new(idx, value) @current_tags.each_key do |name| data.tag(name) end @data[idx] = data @ordered << data end include Enumerable def each(&block) @ordered.each(&block) end def mod(idx, mod, value) @data[idx].modified(mod, value) end end def self.keytable @table ||= begin table = KeyTable.new table.start_tag(:normal) table.start_tag(:letters) (("a".."z").to_a).each_with_index do |ltr, idx| table[idx+4] = ltr end (("A".."Z").to_a).each_with_index do |ltr, idx| table.mod(idx+4, "shift", ltr) end table.end_tag(:letters) table.start_tag(:numbers) (("1".."9").to_a + ["0"]).each_with_index do |num, idx| table[idx+30] = num end %w[! @ # $ % ^ & * ( )].each_with_index do |shift_num, idx| table.mod(idx+30, "shift", shift_num) end table.end_tag(:numbers) table.start_tag(:punctuation) table[45] = "-" table.mod(45, "shift", "_") table[46] = "=" table.mod(46, "shift", "+") table[47] = "[" table.mod(47, "shift", "{") table[48] = "]" table.mod(48, "shift", "}") table[49] = "\\" table.mod(49, "shift", "|") table[56] = "/" table.mod(56, "shift", "?") table[51] = ";" table.mod(51, "shift", ":") table[52] = "'" table.mod(52, "shift", "\"") table[53] = "`" table.mod(53, "shift", "~") table[54] = "," table.mod(54, "shift", "<") table[55] = "." table.mod(55, "shift", ">") table[73] = "~" table.clear_tags table.start_tag(:special) table[40] = "" table[41] = "" table[42] = "" table[43] = "" table[44] = "" table[57] = "" table[71] = "" table[74] = "" table[75] = "" table[76] = "" table[77] = "" table[78] = "" table[79] = "" table[80] = "" table[81] = "" table[82] = "" table[83] = "" table.start_tag(:fkeys) ("1".."12").to_a.each_with_index do |fkey, idx| table[idx+58] = "F" + fkey end table.end_tag(:fkeys) table.start_tag(:hardware_control) table[-2] = "<>" table[-3] = "<>" table[-4] = "<>" table[-5] = "<>" table.clear_tags table end end class Chord def initialize @rows = [:open, :open, :open, :open] @mods = {:num => false, :shift => false, :ctrl => false, :alt => false} end attr_reader :rows, :mods def extract_key(bits, idx, mod, range) @rows[idx] = case bits[range] when /1.../; :left when /.1../; :middle when /..1./; :right else :open end @mods[mod] = (bits[range.last] == ?1) end def keydata=(bits) extract_key(bits, 0, :num, 4..7) extract_key(bits, 1, :alt, 0..3) extract_key(bits, 2, :ctrl, 12..15) extract_key(bits, 3, :shift, 8..11) end def render_keys mods = (@mods[:num] ? "N" : "") + (@mods[:alt] ? "A" : "") + (@mods[:ctrl] ? "C" : "") + (@mods[:shift] ? "S" : "") if mods.empty? mods = "O" end chord = rows.map do |row| case row when :open; "O" when :left; "L" when :right; "R" when :middle; "M" else "wtf?" end end.join("") return "#{mods} #{chord}" end def render_action "" end def render "#{render_keys} => #{render_action}" end end class KeyChord < Chord ModKeys = %w{RGui RAlt RSft RCtl LGui LAlt LSft LCtl} def initialize super @keystrokes = [] end attr_reader :keystrokes def [](idx) @keystrokes[idx] end def single? @keystrokes.length == 1 end def add_keystroke(mod_bits, idx) mods = ModKeys.zip(mod_bits.split('')).delete_if{|mod,bit| bit != "1"}.map{|mod,bit| mod} @keystrokes << [idx, mods] end def keytable Config.keytable end def render_action output = [] (@keystrokes || []).each do |code, mods| output << keytable[code, mods] end return output.join() end end class MouseChord < Chord def initialize super @mods = { :ctrl => false, :alt => false, :shift => false, :double => false, :toggle => false } @buttons = { :left => false, :middle => false, :right => false } end attr_reader :mods, :buttons def data=(bits) if bits[0] == ?1 @mods[:ctrl] = true end if bits[1] == ?1 @mods[:alt] = true end if bits[2] == ?1 @mods[:shift] = true end if bits[3] == ?1 @mods[:double] = true end if bits[4] == ?1 @mods[:toggle] = true end if bits[5] == ?1 @buttons[:middle] = true end if bits[6] == ?1 @buttons[:right] = true end if bits[7] == ?1 @buttons[:left] = true end end def render_action action = "" if @mods[:ctrl] action << "Ctl-" end if @mods[:alt] action << "Alt-" end if @mods[:shift] action << "Shf-" end if @mods[:double] action << "Dbl-" end if @mods[:toggle] action << "Tgl-" end if @buttons[:middle] action << "M" end if @buttons[:right] action << "R" end if @buttons[:left] action << "L" end return action end end def initialize() @mouse = [] @keyboard = [] @configs = {} end def keytable self.class.keytable end attr_reader :mouse, :keyboard, :configs def inspect "" end end end