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
@@ -0,0 +1,51 @@
|
|
1
|
+
# -*- coding:utf-8; mode:ruby; -*-
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
module Rbindkeys
|
6
|
+
|
7
|
+
class LogUtils
|
8
|
+
|
9
|
+
DEFAULT_LOG_OUTPUT = STDOUT
|
10
|
+
DEFAULT_FORMAT = :simple
|
11
|
+
DEFAULT_LEVEL = Logger::INFO
|
12
|
+
@@output = DEFAULT_LOG_OUTPUT
|
13
|
+
@@format = DEFAULT_FORMAT
|
14
|
+
@@level = DEFAULT_LEVEL
|
15
|
+
|
16
|
+
class << self
|
17
|
+
|
18
|
+
def get_logger progname
|
19
|
+
logger = Logger.new @@output
|
20
|
+
|
21
|
+
logger.progname = progname
|
22
|
+
logger.level = @@level
|
23
|
+
set_formatter logger
|
24
|
+
|
25
|
+
logger
|
26
|
+
end
|
27
|
+
|
28
|
+
def set_formatter logger
|
29
|
+
case @@format
|
30
|
+
when :simple
|
31
|
+
logger.formatter = proc do |sev, date, prog, msg|
|
32
|
+
"* #{msg}\n"
|
33
|
+
end
|
34
|
+
when :default
|
35
|
+
else
|
36
|
+
l = Logger.new STDERR
|
37
|
+
l.fatal "unknown logger format"
|
38
|
+
exit false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# setter and reader methods for the class variables
|
43
|
+
[:output, :format, :level].each do |name|
|
44
|
+
eval "def #{name}; @@#{name} end"
|
45
|
+
eval "def #{name}= o; @@#{name} = o end"
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# -*- coding:utf-8; mode:ruby; -*-
|
2
|
+
|
3
|
+
require "revdev"
|
4
|
+
|
5
|
+
module Rbindkeys
|
6
|
+
|
7
|
+
# main event loop class
|
8
|
+
class Observer
|
9
|
+
include Revdev
|
10
|
+
|
11
|
+
LOG = LogUtils.get_logger name
|
12
|
+
VIRTUAL_DEVICE_NAME = "rbindkyes"
|
13
|
+
DEFAULT_TIMEOUT = 0.5
|
14
|
+
|
15
|
+
attr_reader :device
|
16
|
+
attr_reader :virtual
|
17
|
+
attr_reader :event_handler
|
18
|
+
attr_reader :config_file
|
19
|
+
|
20
|
+
def initialize config_name, device_location
|
21
|
+
@device = Device.new device_location
|
22
|
+
@virtual = VirtualDevice.new
|
23
|
+
operator = DeviceOperator.new @device, @virtual
|
24
|
+
@event_handler = KeyEventHandler.new operator
|
25
|
+
@config_file = config_name
|
26
|
+
@started = false
|
27
|
+
@window_observer = ActiveWindowX::EventListener.new
|
28
|
+
@timeout = DEFAULT_TIMEOUT
|
29
|
+
end
|
30
|
+
|
31
|
+
# main loop
|
32
|
+
def start
|
33
|
+
@device.grab
|
34
|
+
@device.release_all_key
|
35
|
+
|
36
|
+
@virtual.create VIRTUAL_DEVICE_NAME #, @device.device_id
|
37
|
+
|
38
|
+
@event_handler.load_config @config_file
|
39
|
+
|
40
|
+
@event_handler.bind_resolver.tree.each do |k,v|
|
41
|
+
puts "#{k} => #{v.inspect}"
|
42
|
+
end
|
43
|
+
|
44
|
+
trap :INT, method(:destroy)
|
45
|
+
trap :TERM, method(:destroy)
|
46
|
+
|
47
|
+
active_window = @window_observer.active_window
|
48
|
+
@event_handler.active_window_changed active_window if active_window
|
49
|
+
|
50
|
+
# start main loop
|
51
|
+
@started = true
|
52
|
+
while true
|
53
|
+
ios = select_ios
|
54
|
+
if ios.nil?
|
55
|
+
# select timeout
|
56
|
+
# no op
|
57
|
+
next
|
58
|
+
end
|
59
|
+
|
60
|
+
if LOG.debug?
|
61
|
+
LOG.debug ""
|
62
|
+
LOG.debug "select => #{ios.inspect}"
|
63
|
+
end
|
64
|
+
|
65
|
+
ios.each do |io|
|
66
|
+
case io
|
67
|
+
when @window_observer.connection then handle_x_event
|
68
|
+
when @device.file then handle_device_event
|
69
|
+
else LOG.error "unknown IO #{io.inspect}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def select_ios
|
76
|
+
if @window_observer.pending_events_num != 0
|
77
|
+
[@window_observer.connection]
|
78
|
+
else
|
79
|
+
ios = select [@window_observer.connection, @device.file], nil, nil, @timeout
|
80
|
+
if ios.nil?
|
81
|
+
nil
|
82
|
+
else
|
83
|
+
ios.first
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def handle_x_event
|
89
|
+
event = @window_observer.listen_with_no_select
|
90
|
+
return if event.nil? or event.window.nil?
|
91
|
+
|
92
|
+
@event_handler.active_window_changed event.window
|
93
|
+
end
|
94
|
+
|
95
|
+
def handle_device_event
|
96
|
+
event = @device.read_input_event
|
97
|
+
|
98
|
+
if event.type != Revdev::EV_KEY
|
99
|
+
@virtual.write_input_event event
|
100
|
+
else
|
101
|
+
@event_handler.handle event
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def destroy *args
|
106
|
+
if not @started
|
107
|
+
LOG.error 'did not start to observe'
|
108
|
+
return
|
109
|
+
end
|
110
|
+
|
111
|
+
begin
|
112
|
+
LOG.info "try @device.ungrab"
|
113
|
+
@device.ungrab
|
114
|
+
LOG.info "=> success"
|
115
|
+
rescue => e
|
116
|
+
LOG.error e
|
117
|
+
end
|
118
|
+
|
119
|
+
begin
|
120
|
+
LOG.info "try @virtural.destroy"
|
121
|
+
@virtual.destroy
|
122
|
+
LOG.info "=> success"
|
123
|
+
rescue => e
|
124
|
+
LOG.error e
|
125
|
+
end
|
126
|
+
|
127
|
+
begin
|
128
|
+
LOG.info "try @window_observer.destory"
|
129
|
+
@window_observer.destroy
|
130
|
+
LOG.info "=> success"
|
131
|
+
rescue => e
|
132
|
+
LOG.error e
|
133
|
+
end
|
134
|
+
|
135
|
+
exit true
|
136
|
+
end
|
137
|
+
|
138
|
+
end # of class
|
139
|
+
|
140
|
+
end # of module Rbindkeys
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# -*- coding:utf-8; mode:ruby; -*-
|
2
|
+
#
|
3
|
+
# matcher for windows to use the app_class(app_name), title of the windows
|
4
|
+
#
|
5
|
+
|
6
|
+
module Rbindkeys
|
7
|
+
class WindowMatcher
|
8
|
+
|
9
|
+
attr_reader :app_name, :title
|
10
|
+
|
11
|
+
def initialize h
|
12
|
+
@app_name = (h[:class] or h[:app_name] or h[:app_class])
|
13
|
+
@title = (h[:title] or h[:name])
|
14
|
+
|
15
|
+
if not @app_name.nil? and not @title.nil?
|
16
|
+
raise ArgumentError, 'expect to be given :class, :app_name,'+
|
17
|
+
' :app_class, :title or :name '
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def match? app_name, title
|
22
|
+
(@app_name.nil? or match_app?(app_name)) and
|
23
|
+
(@title.nil? or match_title?(title))
|
24
|
+
end
|
25
|
+
|
26
|
+
def match_app? app_name
|
27
|
+
app_name and app_name.match @app_name
|
28
|
+
end
|
29
|
+
|
30
|
+
def match_title? title
|
31
|
+
title and title.match @title
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
data/rbindkeys.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- coding:utf-8-unix; mode:ruby; -*-
|
2
|
+
|
3
|
+
require File.expand_path('../lib/rbindkeys/version', __FILE__)
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.authors = ["Keiichiro Ui"]
|
7
|
+
gem.email = ["keiichiro.ui@gmail.com"]
|
8
|
+
gem.description = "key remap"
|
9
|
+
gem.summary = "key remap"
|
10
|
+
gem.homepage = ""
|
11
|
+
|
12
|
+
gem.files = `git ls-files`.split($\)
|
13
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
14
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
15
|
+
gem.name = "rbindkeys"
|
16
|
+
gem.require_paths = ["lib"]
|
17
|
+
gem.version = Rbindkeys::VERSION
|
18
|
+
|
19
|
+
gem.add_dependency 'revdev'
|
20
|
+
gem.add_dependency 'ruinput'
|
21
|
+
gem.add_dependency 'active_window_x'
|
22
|
+
gem.add_development_dependency 'bundler'
|
23
|
+
gem.add_development_dependency 'rake'
|
24
|
+
gem.add_development_dependency 'rspec'
|
25
|
+
|
26
|
+
end
|
data/sample/emacs.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
# -*- coding:utf-8; mode:ruby; -*-
|
2
|
+
|
3
|
+
## user settings
|
4
|
+
|
5
|
+
# if you use a keyboard which have a left ctrl key at the left of "A" key,
|
6
|
+
# then you must set false
|
7
|
+
@swap_left_ctrl_with_caps = true
|
8
|
+
|
9
|
+
# for apple keyboard
|
10
|
+
@swap_left_opt_with_left_cmd = true
|
11
|
+
|
12
|
+
##
|
13
|
+
|
14
|
+
# pre_bind_key: pre-proccessed keybinding
|
15
|
+
if @swap_left_ctrl_with_caps
|
16
|
+
pre_bind_key KEY_CAPSLOCK, KEY_LEFTCTRL
|
17
|
+
pre_bind_key KEY_LEFTCTRL, KEY_CAPSLOCK
|
18
|
+
end
|
19
|
+
|
20
|
+
if @swap_left_opt_with_left_cmd
|
21
|
+
pre_bind_key KEY_LEFTMETA, KEY_LEFTALT
|
22
|
+
pre_bind_key KEY_LEFTALT, KEY_LEFTMETA
|
23
|
+
end
|
24
|
+
|
25
|
+
# cursor move
|
26
|
+
bind_key [KEY_LEFTCTRL, KEY_F], KEY_RIGHT
|
27
|
+
bind_key [KEY_LEFTCTRL, KEY_B], KEY_LEFT
|
28
|
+
bind_key [KEY_LEFTCTRL, KEY_P], KEY_UP
|
29
|
+
bind_key [KEY_LEFTCTRL, KEY_N], KEY_DOWN
|
30
|
+
bind_key [KEY_LEFTCTRL, KEY_A], KEY_HOME
|
31
|
+
bind_key [KEY_LEFTCTRL, KEY_E], KEY_END
|
32
|
+
|
33
|
+
# page scroll
|
34
|
+
bind_key [KEY_LEFTCTRL, KEY_V], KEY_PAGEDOWN
|
35
|
+
bind_key [KEY_LEFTALT, KEY_V], KEY_PAGEUP
|
36
|
+
|
37
|
+
# edit
|
38
|
+
bind_key [KEY_LEFTCTRL, KEY_D], KEY_DELETE
|
39
|
+
bind_key [KEY_LEFTCTRL, KEY_H], KEY_BACKSPACE
|
40
|
+
bind_key [KEY_LEFTCTRL, KEY_M], KEY_ENTER
|
41
|
+
bind_key [KEY_LEFTCTRL, KEY_I], KEY_TAB
|
42
|
+
bind_key [KEY_LEFTCTRL, KEY_LEFTBRACE], KEY_ESC
|
43
|
+
|
44
|
+
# give a block sample
|
45
|
+
@caps_led_state = 0
|
46
|
+
bind_key KEY_CAPSLOCK do |event, operator|
|
47
|
+
@caps_led_state = @caps_led_state ^ 1
|
48
|
+
puts "########## CAPSLOCK LED #{@caps_led_state.zero? ? 'off' : 'on'} ##########"
|
49
|
+
operator.send_event EV_LED, LED_CAPSL, @caps_led_state
|
50
|
+
end
|
51
|
+
|
52
|
+
# cut, copy and paste
|
53
|
+
bind_key [KEY_LEFTCTRL, KEY_W], [KEY_LEFTCTRL,KEY_X]
|
54
|
+
bind_key [KEY_LEFTALT, KEY_W], [KEY_LEFTCTRL,KEY_C]
|
55
|
+
bind_key [KEY_LEFTCTRL, KEY_Y], [KEY_LEFTCTRL,KEY_V]
|
56
|
+
|
57
|
+
# kill line
|
58
|
+
bind_key [KEY_LEFTCTRL, KEY_K] do |event, operator|
|
59
|
+
# Shift+End : select text to end of line
|
60
|
+
operator.press_key KEY_LEFTSHIFT
|
61
|
+
operator.press_key KEY_END
|
62
|
+
operator.release_key KEY_END
|
63
|
+
operator.release_key KEY_LEFTSHIFT
|
64
|
+
|
65
|
+
# Ctrl+x : cut
|
66
|
+
operator.press_key KEY_LEFTCTRL
|
67
|
+
operator.press_key KEY_X
|
68
|
+
operator.release_key KEY_X
|
69
|
+
operator.release_key KEY_LEFTCTRL
|
70
|
+
end
|
71
|
+
|
72
|
+
# 2 stroke key binds
|
73
|
+
# if your input was not hit any bind_key, the input will be ignored
|
74
|
+
bind_prefix_key [KEY_LEFTCTRL, KEY_X] do
|
75
|
+
|
76
|
+
# C-xk: close tab, etc.
|
77
|
+
bind_key KEY_K, [KEY_LEFTCTRL, KEY_W]
|
78
|
+
|
79
|
+
# C-xC-s: save
|
80
|
+
bind_key [KEY_LEFTCTRL, KEY_S], [KEY_LEFTCTRL, KEY_S]
|
81
|
+
|
82
|
+
# C-xb: next tab, etc.
|
83
|
+
bind_key KEY_B, [KEY_LEFTCTRL, KEY_TAB]
|
84
|
+
|
85
|
+
# C-xC-g: ignore C-x prefix bind
|
86
|
+
bind_key [KEY_LEFTCTRL, KEY_G], :ignore
|
87
|
+
|
88
|
+
# C-xC-c: close window
|
89
|
+
bind_key [KEY_LEFTCTRL, KEY_C], [KEY_LEFTALT, KEY_F4]
|
90
|
+
end
|
91
|
+
|
92
|
+
# settings per window class (or title)
|
93
|
+
|
94
|
+
# through all key inputs if active
|
95
|
+
window(:through, :class => /gnome-terminal/)
|
96
|
+
|
97
|
+
# add new bind_key to default binds
|
98
|
+
window(@default_bind_resolver, :class => /google-chrome/) do
|
99
|
+
bind_key [KEY_LEFTCTRL, KEY_S], [KEY_LEFTCTRL, KEY_F]
|
100
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# -*- coding:utf-8; mode:ruby; -*-
|
2
|
+
|
3
|
+
require 'rbindkeys'
|
4
|
+
require 'revdev'
|
5
|
+
|
6
|
+
include Rbindkeys
|
7
|
+
|
8
|
+
describe BindResolver do
|
9
|
+
before do
|
10
|
+
@resolver = BindResolver.new :foo
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "#bind" do
|
14
|
+
context "with two Fixnum Array" do
|
15
|
+
before do
|
16
|
+
@input = [0, 1]
|
17
|
+
@output = [2, 3]
|
18
|
+
end
|
19
|
+
it "should update @tree" do
|
20
|
+
@resolver.bind @input, @output
|
21
|
+
@resolver.bind [0,2], [2,3]
|
22
|
+
@resolver.tree[@input.last].first.input.should == @input
|
23
|
+
@resolver.tree[@input.last].first.output.should == @output
|
24
|
+
end
|
25
|
+
end
|
26
|
+
context "with a Fixnum Array and a BindResolver" do
|
27
|
+
before do
|
28
|
+
@input = [0, 1]
|
29
|
+
@output = BindResolver.new :bar
|
30
|
+
end
|
31
|
+
it "should update @tree" do
|
32
|
+
@resolver.bind @input, @output
|
33
|
+
@resolver.bind [0,2], [2,3]
|
34
|
+
@resolver.tree[@input.last].first.input.should == @input
|
35
|
+
@resolver.tree[@input.last].first.output.should == @output
|
36
|
+
end
|
37
|
+
end
|
38
|
+
context "with 2 BindResolver which is same as other one" do
|
39
|
+
before do
|
40
|
+
@input = [0, 1]
|
41
|
+
@output = BindResolver.new :bar
|
42
|
+
end
|
43
|
+
it "should update @tree" do
|
44
|
+
@resolver.bind @input, @output
|
45
|
+
lambda{@resolver.bind @input, [2,4]}.should raise_error(DuplicateNodeError)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
context "with Fixnum Arrays" do
|
49
|
+
before do
|
50
|
+
end
|
51
|
+
it "should update @tree which store binds sort by modkey numbers" do
|
52
|
+
@resolver.bind [0,2], [0,3]
|
53
|
+
@resolver.bind [0,1,2], [1,3]
|
54
|
+
@resolver.bind [3,2], [2,3]
|
55
|
+
|
56
|
+
@resolver.tree[2][0].output.should == [1,3]
|
57
|
+
@resolver.tree[2][1].output.should == [0,3]
|
58
|
+
@resolver.tree[2][2].output.should == [2,3]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "#resolve" do
|
64
|
+
before do
|
65
|
+
@resolver2 = BindResolver.new(:ignore)
|
66
|
+
@resolver.bind [0, 1], [2, 3]
|
67
|
+
@resolver.bind [3, 1], [2, 4]
|
68
|
+
@resolver.bind [0, 2], [2, 5]
|
69
|
+
@resolver.bind [0, 1, 2], @resolver2
|
70
|
+
end
|
71
|
+
context "with an input which hit a bind" do
|
72
|
+
before do
|
73
|
+
@input = 1
|
74
|
+
@pressed_key_set = [0]
|
75
|
+
end
|
76
|
+
it "should return the bind" do
|
77
|
+
@resolver.resolve(@input, @pressed_key_set).kind_of?(KeyBind).should be_true
|
78
|
+
@resolver.resolve(@input, @pressed_key_set).output.should == [2, 3]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
context "with an input which hit a BindResolver" do
|
82
|
+
before do
|
83
|
+
@input = 2
|
84
|
+
@pressed_key_set = [0,1]
|
85
|
+
end
|
86
|
+
it "should return the bind" do
|
87
|
+
@resolver.resolve(@input, @pressed_key_set).kind_of?(KeyBind).should be_true
|
88
|
+
@resolver.resolve(@input, @pressed_key_set).output.should == @resolver2
|
89
|
+
end
|
90
|
+
end
|
91
|
+
context "with an input which hit no binds" do
|
92
|
+
before do
|
93
|
+
@input = 2
|
94
|
+
@pressed_key_set = [1]
|
95
|
+
end
|
96
|
+
it "should return default value" do
|
97
|
+
@resolver.resolve(@input, @pressed_key_set).should == :foo
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|