rbindkeys 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|