rbindkeys 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +23 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +52 -0
- data/Rakefile +11 -0
- data/bin/rbindkeys +5 -0
- data/lib/rbindkeys.rb +38 -0
- data/lib/rbindkeys/bind_resolver.rb +58 -0
- data/lib/rbindkeys/bind_set.rb +10 -0
- data/lib/rbindkeys/bind_tree.rb +148 -0
- data/lib/rbindkeys/cli.rb +103 -0
- data/lib/rbindkeys/device.rb +25 -0
- data/lib/rbindkeys/device_operator.rb +78 -0
- data/lib/rbindkeys/fix_resolver.rb +26 -0
- data/lib/rbindkeys/key_bind.rb +21 -0
- data/lib/rbindkeys/key_event_handler.rb +243 -0
- data/lib/rbindkeys/key_event_handler/configurer.rb +142 -0
- data/lib/rbindkeys/log_utils.rb +51 -0
- data/lib/rbindkeys/observer.rb +140 -0
- data/lib/rbindkeys/version.rb +3 -0
- data/lib/rbindkeys/virtual_device.rb +14 -0
- data/lib/rbindkeys/window_matcher.rb +35 -0
- data/rbindkeys.gemspec +26 -0
- data/sample/emacs.rb +100 -0
- data/sample/swap_left_ctrl_and_caps.rb +4 -0
- data/spec/bind_resolver_spec.rb +101 -0
- data/spec/bind_tree_bind_spec.rb +48 -0
- data/spec/bind_tree_resolve_for_pressed_event_spec.rb +74 -0
- data/spec/bind_tree_resolve_for_released_event_spec.rb +63 -0
- data/spec/cli_spec.rb +81 -0
- data/spec/device_operator_spec.rb +57 -0
- data/spec/fix_resolver_spec.rb +57 -0
- data/spec/key_event_handler/configurer_spec.rb +201 -0
- data/spec/key_event_handler/handle_spec.rb +222 -0
- metadata +190 -0
data/.gitignore
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
*.o
|
4
|
+
*.so
|
5
|
+
*.dll
|
6
|
+
*~
|
7
|
+
.bundle
|
8
|
+
.config
|
9
|
+
.yardoc
|
10
|
+
Gemfile.lock
|
11
|
+
InstalledFiles
|
12
|
+
Makefile
|
13
|
+
_yardoc
|
14
|
+
core
|
15
|
+
coverage
|
16
|
+
doc/
|
17
|
+
lib/bundler/man
|
18
|
+
pkg
|
19
|
+
rdoc
|
20
|
+
spec/reports
|
21
|
+
test/tmp
|
22
|
+
test/version_tmp
|
23
|
+
tmp
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Keiichiro Ui
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# rbindkeys
|
2
|
+
|
3
|
+
a key remapper, which is configurable in ruby, for Linux and X Window System
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'rbindkeys'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install rbindkeys
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
1. `rbindkeys -e > ~/.rbindkeys.rb`
|
22
|
+
2. edit `~.rbindkeys.rb`
|
23
|
+
3. select a keyboard device (see `sudo rbindkeys --evdev-list`)
|
24
|
+
4. `sudo rbindkeys /dev/input/event2` if you selected "/dev/input/event2"
|
25
|
+
as a target keyboard
|
26
|
+
|
27
|
+
## Contributing
|
28
|
+
|
29
|
+
1. Fork it
|
30
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
31
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
32
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
33
|
+
5. Create new Pull Request
|
34
|
+
|
35
|
+
## TODO
|
36
|
+
|
37
|
+
* write documents and publish on rubygem.org
|
38
|
+
* a daemonize script
|
39
|
+
* remove @two_storoke and add PrefixBindResolver class
|
40
|
+
* change BindResolver on input method system
|
41
|
+
* simplify config file (e.g. `bind_key [:ctrl, :m], :enter`, `bind_key "ctrl+m", "enter"` )
|
42
|
+
* integrate ibus controller (e.g. `bind_key "alt-grave", "toggle_ibus"` )
|
43
|
+
* notification when active a prefix key, changing ibus status, etc..
|
44
|
+
* the LED manipulation does not work for bluetooth devices
|
45
|
+
* fix bug
|
46
|
+
* the enter key cannot be release when `rbindkey` is executed
|
47
|
+
|
48
|
+
## Other Configurable Key Remappers For Linux
|
49
|
+
|
50
|
+
* [x11keymacs](http://yashiromann.sakura.ne.jp/x11keymacs/index-en.html)
|
51
|
+
* [xfumble](http://endoh-namazu.tierra.ne.jp/xfumble/)
|
52
|
+
* [私家版 窓使いの憂鬱 Linux & Mac (Darwin) 対応版](http://www42.tok2.com/home/negidakude/)
|
data/Rakefile
ADDED
data/bin/rbindkeys
ADDED
data/lib/rbindkeys.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# -*- coding:undecided-unix; mode:ruby; -*-
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'rbindkeys/version'
|
5
|
+
require 'rbindkeys/log_utils'
|
6
|
+
|
7
|
+
require 'rbindkeys/key_bind'
|
8
|
+
require 'rbindkeys/bind_tree'
|
9
|
+
require 'rbindkeys/observer'
|
10
|
+
require 'rbindkeys/device'
|
11
|
+
require 'rbindkeys/virtual_device'
|
12
|
+
|
13
|
+
require 'rbindkeys/device_operator'
|
14
|
+
require 'rbindkeys/key_event_handler'
|
15
|
+
require 'rbindkeys/window_matcher'
|
16
|
+
require 'rbindkeys/bind_resolver'
|
17
|
+
require 'rbindkeys/fix_resolver'
|
18
|
+
|
19
|
+
require 'rbindkeys/cli'
|
20
|
+
|
21
|
+
module Rbindkeys
|
22
|
+
|
23
|
+
class BindTree; end
|
24
|
+
class Observer; end
|
25
|
+
class Devicie; end
|
26
|
+
class VirtualDevice; end
|
27
|
+
|
28
|
+
class DeviceOperator; end
|
29
|
+
class WindowMatcher; end
|
30
|
+
class KeyEventHandler; end
|
31
|
+
class BindResolver; end
|
32
|
+
class FixResolver; end
|
33
|
+
|
34
|
+
class CLI; end
|
35
|
+
|
36
|
+
class DuplicateNodeError < ArgumentError; end
|
37
|
+
class UnknownKeyValue < Exception; end
|
38
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# -*- coding:utf-8; mode:ruby; -*-
|
2
|
+
|
3
|
+
module Rbindkeys
|
4
|
+
|
5
|
+
class BindResolver
|
6
|
+
|
7
|
+
LOG = LogUtils.get_logger name
|
8
|
+
DEFAULT_VALUE = :through
|
9
|
+
|
10
|
+
attr_reader :tree
|
11
|
+
|
12
|
+
# delegate if cannot resolved
|
13
|
+
attr_reader :upper_resolver
|
14
|
+
|
15
|
+
# if this resolver is set by prefix key, then true
|
16
|
+
# else, false
|
17
|
+
attr_reader :two_stroke
|
18
|
+
alias :two_stroke? :two_stroke
|
19
|
+
|
20
|
+
def initialize upper_resolver=:through, two_stroke=false
|
21
|
+
@tree = {}
|
22
|
+
if upper_resolver.kind_of? Symbol
|
23
|
+
upper_resolver = FixResolver.instance upper_resolver
|
24
|
+
end
|
25
|
+
@upper_resolver = upper_resolver
|
26
|
+
@two_stroke = two_stroke
|
27
|
+
end
|
28
|
+
|
29
|
+
def bind input, output
|
30
|
+
@tree[input.last] ||= []
|
31
|
+
@tree[input.last].each do |b|
|
32
|
+
if b.input == input
|
33
|
+
raise DuplicateNodeError, "already this input(#{input.inspect}) was binded"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
kb = KeyBind.new input, output
|
38
|
+
@tree[input.last] << kb # TODO implement a bubble insertion
|
39
|
+
@tree[input.last].sort!{|a,b| b.input.length <=> a.input.length}
|
40
|
+
kb
|
41
|
+
end
|
42
|
+
|
43
|
+
def resolve key_code, key_code_set
|
44
|
+
just_resolve(key_code, key_code_set) or
|
45
|
+
@upper_resolver.resolve(key_code, key_code_set)
|
46
|
+
end
|
47
|
+
|
48
|
+
def just_resolve key_code, key_code_set
|
49
|
+
arr = @tree[key_code]
|
50
|
+
arr.each do |kb|
|
51
|
+
sub = kb.input - key_code_set
|
52
|
+
sub.first == kb.input.last and
|
53
|
+
return kb
|
54
|
+
end if not arr.nil?
|
55
|
+
nil
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
# -*- coding:utf-8; mode:ruby; -*-
|
2
|
+
|
3
|
+
module Rbindkeys
|
4
|
+
class BindTree
|
5
|
+
|
6
|
+
DEFAULT_DEFAULT_VALUE = :through
|
7
|
+
AVAIVABLE_DEFAULT_VALUE = [:through, :ignore]
|
8
|
+
|
9
|
+
# a tree structure which that nodes are Fixnum(keycode) and
|
10
|
+
# leaves are Leaf
|
11
|
+
attr_reader :tree
|
12
|
+
|
13
|
+
attr_reader :main_tree
|
14
|
+
|
15
|
+
# active KeyBind
|
16
|
+
# TODO create KeyEventHandler which exist between Observer and BindTree
|
17
|
+
# TODO move out @active_key_binds to KeyEventHandler
|
18
|
+
attr_reader :active_key_binds
|
19
|
+
|
20
|
+
# a value if no binds hit
|
21
|
+
attr_reader :default_value
|
22
|
+
|
23
|
+
def initialize default_value=DEFAULT_DEFAULT_VALUE
|
24
|
+
@tree = {}
|
25
|
+
@active_key_binds = []
|
26
|
+
if AVAIVABLE_DEFAULT_VALUE.include? default_value
|
27
|
+
@default_value = default_value
|
28
|
+
else
|
29
|
+
raise ArgumentError, "expect #{AVAIVABLE_DEFAULT_VALUE.join('/')}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# register an input-output pair
|
34
|
+
# _input_: Array of (Array of) input keycodes
|
35
|
+
# _output_: Array of send keycodes or Proc
|
36
|
+
def bind input, output=nil
|
37
|
+
input = input.clone
|
38
|
+
new_input = []
|
39
|
+
|
40
|
+
if input.kind_of? Array and input[0].kind_of? Array
|
41
|
+
new_input = input
|
42
|
+
input = new_input.shift
|
43
|
+
end
|
44
|
+
|
45
|
+
tail_code = input.pop
|
46
|
+
input.sort!
|
47
|
+
|
48
|
+
subtree = @tree
|
49
|
+
input.each do |code|
|
50
|
+
if subtree.has_key? code and (not subtree[code].kind_of? Hash)
|
51
|
+
raise DuplicateNodeError, "already register an input:#{input}"
|
52
|
+
end
|
53
|
+
subtree[code] ||= {}
|
54
|
+
subtree = subtree[code]
|
55
|
+
end
|
56
|
+
|
57
|
+
if not new_input.empty?
|
58
|
+
if subtree.has_key?(tail_code) and
|
59
|
+
not (subtree[tail_code].kind_of?(Leaf) and
|
60
|
+
subtree[tail_code].payload.kind_of?(BindTree))
|
61
|
+
raise DuplicateNodeError, "already register an input:#{input}"
|
62
|
+
end
|
63
|
+
|
64
|
+
if new_input.length == 1
|
65
|
+
new_input = new_input.first
|
66
|
+
end
|
67
|
+
|
68
|
+
subtree[tail_code] ||= Leaf.new BindTree.new :ignore
|
69
|
+
subtree[tail_code].payload.bind new_input, output
|
70
|
+
|
71
|
+
elsif subtree.has_key? tail_code
|
72
|
+
raise DuplicateNodeError, "already register an input:#{input}"
|
73
|
+
|
74
|
+
else
|
75
|
+
subtree[tail_code] = Leaf.new KeyBind.new input.push(tail_code), output
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# called when event.value == 0
|
80
|
+
def resolve_for_released_event event, pressed_keys
|
81
|
+
release_binds = []
|
82
|
+
@active_key_binds.reject! do |key_bind|
|
83
|
+
if key_bind.input.include? event.code
|
84
|
+
release_binds << key_bind
|
85
|
+
true
|
86
|
+
else
|
87
|
+
false
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
if release_binds.empty?
|
92
|
+
:through
|
93
|
+
else
|
94
|
+
release_binds
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# called when event.value == 1
|
99
|
+
def resolve_for_pressed_event event, pressed_keys
|
100
|
+
subtree = @tree
|
101
|
+
last_code = -1
|
102
|
+
pressed_keys.each do |code|
|
103
|
+
if last_code >= code
|
104
|
+
raise ArgumentError, "expect a sorted Array for 2nd arg (pressed_keys)"
|
105
|
+
end
|
106
|
+
last_code = code
|
107
|
+
|
108
|
+
if subtree.has_key? code
|
109
|
+
subtree = subtree[code]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
subtree = (subtree.kind_of?(Hash) and subtree[event.code])
|
114
|
+
|
115
|
+
if not subtree or subtree.kind_of? Hash
|
116
|
+
return @default_value
|
117
|
+
elsif subtree.kind_of? Leaf
|
118
|
+
if subtree.payload.kind_of? KeyBind
|
119
|
+
@active_key_binds << subtree.payload
|
120
|
+
return subtree.payload
|
121
|
+
elsif subtree.payload.kind_of? BindTree
|
122
|
+
return subtree.payload
|
123
|
+
end
|
124
|
+
else
|
125
|
+
raise UnexpecedLeafError, "unexpeced Leaf: #{subtree.inspect}"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# called when event.value == 2
|
130
|
+
def resolve_for_pressing_event event, pressed_keys
|
131
|
+
if @active_key_binds.empty?
|
132
|
+
@default_value
|
133
|
+
else
|
134
|
+
@active_key_binds
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
class Leaf
|
139
|
+
attr_reader :payload
|
140
|
+
|
141
|
+
def initialize payload
|
142
|
+
@payload = payload
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
class UnexpecedLeafError < RuntimeError; end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# -*- coding:utf-8; mode:ruby; -*-
|
2
|
+
|
3
|
+
module Rbindkeys
|
4
|
+
|
5
|
+
SUMMARY = 'key remapper for Linux which is configured in ruby'
|
6
|
+
|
7
|
+
# a class is executed by bin/rbindkeys
|
8
|
+
class CLI
|
9
|
+
|
10
|
+
EVDEVS = '/dev/input/event*'
|
11
|
+
|
12
|
+
class << self
|
13
|
+
require 'optparse'
|
14
|
+
|
15
|
+
# if @@cmd == :observe then CLI excecute to observe a given event device
|
16
|
+
# else if @@cmd == :ls then CLI list event devices
|
17
|
+
# (default: :observe)
|
18
|
+
@@cmd = :observe
|
19
|
+
def cmd; @@cmd end
|
20
|
+
|
21
|
+
# a location of a config file (default: "~/.rbindkeys.rb")
|
22
|
+
@@config = "#{ENV['HOME']}/.rbindkeys.rb"
|
23
|
+
def config; @@config end
|
24
|
+
|
25
|
+
@@usage = SUMMARY
|
26
|
+
|
27
|
+
def main
|
28
|
+
begin
|
29
|
+
parse_opt
|
30
|
+
rescue OptionParser::ParseError => e
|
31
|
+
puts "ERROR #{e.to_s}"
|
32
|
+
err
|
33
|
+
end
|
34
|
+
|
35
|
+
method(@@cmd).call
|
36
|
+
end
|
37
|
+
|
38
|
+
def err code=1
|
39
|
+
puts @@usage
|
40
|
+
exit code
|
41
|
+
end
|
42
|
+
|
43
|
+
def parse_opt
|
44
|
+
opt = OptionParser.new <<BANNER
|
45
|
+
#{SUMMARY}
|
46
|
+
Usage: sudo #{$0} [--config file] #{EVDEVS}
|
47
|
+
or: sudo #{$0} --evdev-list
|
48
|
+
BANNER
|
49
|
+
opt.version = VERSION
|
50
|
+
opt.on '-l', '--evdev-list', 'a list of event devices' do
|
51
|
+
@@cmd = :ls
|
52
|
+
end
|
53
|
+
opt.on '-c VAL', '--config VAL', 'specifying your configure file' do |v|
|
54
|
+
@@config = v
|
55
|
+
end
|
56
|
+
opt.on '-e', '--print-example', 'print an example config' do |v|
|
57
|
+
@@cmd = :print_example
|
58
|
+
end
|
59
|
+
|
60
|
+
opt.parse! ARGV
|
61
|
+
|
62
|
+
@@usage = opt.help
|
63
|
+
end
|
64
|
+
|
65
|
+
def observe
|
66
|
+
if ARGV.length != 1
|
67
|
+
puts 'ERROR invalid arguments'
|
68
|
+
err
|
69
|
+
end
|
70
|
+
evdev = ARGV.first
|
71
|
+
Observer.new(@@config, evdev).start
|
72
|
+
end
|
73
|
+
|
74
|
+
def ls
|
75
|
+
require 'revdev'
|
76
|
+
Dir::glob(EVDEVS).sort do |a,b|
|
77
|
+
am = a.match(/[0-9]+$/)
|
78
|
+
bm = b.match(/[0-9]+$/)
|
79
|
+
ai = am[0] ? am[0].to_i : 0
|
80
|
+
bi = bm[0] ? bm[0].to_i : 0
|
81
|
+
ai <=> bi
|
82
|
+
end.each do |f|
|
83
|
+
begin
|
84
|
+
e = Revdev::EventDevice.new f
|
85
|
+
puts "#{f}: #{e.device_name} (#{e.device_id.hr_bustype})"
|
86
|
+
rescue => ex
|
87
|
+
puts ex
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def print_example
|
93
|
+
dir = File.dirname File.expand_path __FILE__
|
94
|
+
dir = File.expand_path File.join dir, '..', '..', 'sample'
|
95
|
+
file = File.join dir, 'emacs.rb'
|
96
|
+
IO.foreach file do |line|
|
97
|
+
puts "# #{line}"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
end # of class Runner
|
103
|
+
end
|