rum 0.0.1-x86-mswin32-60
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/CHANGELOG +3 -0
- data/README +30 -0
- data/Rakefile +134 -0
- data/bin/rum-client +124 -0
- data/doc/basic.rb +10 -0
- data/doc/doc.html +602 -0
- data/doc/example.rb +59 -0
- data/doc/reference.rb +415 -0
- data/doc/resources/bg.png +0 -0
- data/doc/resources/bottom.png +0 -0
- data/doc/resources/build.rb +235 -0
- data/doc/resources/doc.haml +167 -0
- data/doc/resources/emacs-auto-completion.png +0 -0
- data/doc/resources/flash.png +0 -0
- data/doc/resources/highlight.css +94 -0
- data/doc/resources/intro.rb +17 -0
- data/doc/resources/left.png +0 -0
- data/doc/resources/logo.png +0 -0
- data/doc/resources/screen.css +420 -0
- data/doc/resources/screenshot.png +0 -0
- data/doc/resources/top.png +0 -0
- data/ext/mac/keyboard_hook/English.lproj/InfoPlist.strings +0 -0
- data/ext/mac/keyboard_hook/Event.h +17 -0
- data/ext/mac/keyboard_hook/Event.m +18 -0
- data/ext/mac/keyboard_hook/EventTap.h +11 -0
- data/ext/mac/keyboard_hook/EventTap.m +77 -0
- data/ext/mac/keyboard_hook/Info.plist +26 -0
- data/ext/mac/keyboard_hook/KeyboardHook.xcodeproj/TemplateIcon.icns +0 -0
- data/ext/mac/keyboard_hook/KeyboardHook.xcodeproj/project.pbxproj +323 -0
- data/ext/mac/keyboard_hook/KeyboardHook_Prefix.pch +7 -0
- data/ext/mac/keyboard_hook/version.plist +16 -0
- data/ext/windows/keyboard_hook/extconf.rb +2 -0
- data/ext/windows/keyboard_hook/keyboard_hook.c +126 -0
- data/ext/windows/system/autohotkey_stuff.c +255 -0
- data/ext/windows/system/autohotkey_stuff.h +2 -0
- data/ext/windows/system/clipboard_watcher.c +58 -0
- data/ext/windows/system/clipboard_watcher.h +2 -0
- data/ext/windows/system/extconf.rb +3 -0
- data/ext/windows/system/input_box.c +239 -0
- data/ext/windows/system/input_box.h +4 -0
- data/ext/windows/system/system.c +273 -0
- data/lib/rum.rb +4 -0
- data/lib/rum/apps.rb +4 -0
- data/lib/rum/barrel.rb +157 -0
- data/lib/rum/barrel/emacs.rb +44 -0
- data/lib/rum/barrel/emacs_client.rb +74 -0
- data/lib/rum/core.rb +125 -0
- data/lib/rum/dsl.rb +109 -0
- data/lib/rum/gui.rb +93 -0
- data/lib/rum/help.rb +128 -0
- data/lib/rum/hotkey_core.rb +479 -0
- data/lib/rum/mac.rb +18 -0
- data/lib/rum/mac/app.rb +4 -0
- data/lib/rum/mac/apps.rb +19 -0
- data/lib/rum/mac/gui.rb +26 -0
- data/lib/rum/mac/gui/growl.rb +54 -0
- data/lib/rum/mac/irb/completion.rb +207 -0
- data/lib/rum/mac/keyboard_hook.rb +73 -0
- data/lib/rum/mac/layouts.rb +146 -0
- data/lib/rum/mac/system.rb +45 -0
- data/lib/rum/remote.rb +48 -0
- data/lib/rum/server.rb +92 -0
- data/lib/rum/windows.rb +23 -0
- data/lib/rum/windows/app.rb +72 -0
- data/lib/rum/windows/apps.rb +25 -0
- data/lib/rum/windows/gui.rb +116 -0
- data/lib/rum/windows/keyboard.rb +80 -0
- data/lib/rum/windows/keyboard_hook.rb +20 -0
- data/lib/rum/windows/keyboard_hook.so +0 -0
- data/lib/rum/windows/layouts.rb +232 -0
- data/lib/rum/windows/system.rb +310 -0
- data/lib/rum/windows/system.so +0 -0
- data/lib/rum/windows/system_foreign_functions.rb +129 -0
- data/rum.gemspec +14 -0
- metadata +156 -0
data/lib/rum/gui.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
module Rum
|
2
|
+
module Gui
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def self.use gui_module, *methods
|
6
|
+
if methods.empty?
|
7
|
+
include gui_module
|
8
|
+
extend self
|
9
|
+
gui_module.auto_setup if gui_module.respond_to? :auto_setup
|
10
|
+
else
|
11
|
+
methods.each do |method|
|
12
|
+
method = gui_module.method(method)
|
13
|
+
define_method(method.name) do |*args, &block|
|
14
|
+
method.call(*args, &block)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def message text, *args, &callback
|
21
|
+
sticky = !!args.delete(:sticky)
|
22
|
+
title = args.first
|
23
|
+
message_backend(text, title, sticky, callback)
|
24
|
+
end
|
25
|
+
|
26
|
+
def alert text, title=nil
|
27
|
+
Rum.switch_worker_thread
|
28
|
+
title ||= 'Rum'
|
29
|
+
alert_backend(text, title)
|
30
|
+
end
|
31
|
+
|
32
|
+
def read(text='', args=nil)
|
33
|
+
Rum.switch_worker_thread
|
34
|
+
if text.is_a? Hash
|
35
|
+
args = text
|
36
|
+
text = nil
|
37
|
+
end
|
38
|
+
if args
|
39
|
+
default = args[:default]
|
40
|
+
title = args[:title]
|
41
|
+
end
|
42
|
+
read_backend(text, (title or 'Rum'), default)
|
43
|
+
end
|
44
|
+
|
45
|
+
def choose prompt=nil, choices
|
46
|
+
Rum.switch_worker_thread
|
47
|
+
choose_backend(prompt, choices)
|
48
|
+
end
|
49
|
+
|
50
|
+
def browse url
|
51
|
+
url = 'http://' + url unless url =~ /^\w+:\/\//
|
52
|
+
browse_backend url
|
53
|
+
end
|
54
|
+
|
55
|
+
module CocoaDialog
|
56
|
+
private
|
57
|
+
|
58
|
+
def alert_backend prompt, title
|
59
|
+
result = dialog('ok-msgbox', '--text', prompt.to_s, '--title', title.to_s)
|
60
|
+
result.first == '1'
|
61
|
+
end
|
62
|
+
|
63
|
+
def message_backend text, title, sticky, callback
|
64
|
+
alert text, title
|
65
|
+
end
|
66
|
+
|
67
|
+
def read_backend text, title, default
|
68
|
+
text = ['--informative-text', text.to_s] if text
|
69
|
+
title = ['--title', title.to_s] if title
|
70
|
+
default = ['--text', default.to_s] if default
|
71
|
+
result = dialog('standard-inputbox', *text, *default, *title)
|
72
|
+
result.shift == '1' ? result.join(' ') : ''
|
73
|
+
end
|
74
|
+
|
75
|
+
def choose_backend prompt, choices
|
76
|
+
prompt = ['--text', prompt.to_s] if prompt
|
77
|
+
result = dialog('standard-dropdown', '--items', *choices.map(&:to_s), *prompt)
|
78
|
+
choices[result[1].to_i] if result.first == '1'
|
79
|
+
end
|
80
|
+
|
81
|
+
def dialog *args
|
82
|
+
IO.popen([@@binary, *args]) { |p| p.read }.split
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.setup binary=nil
|
86
|
+
unless binary
|
87
|
+
raise ArgumentError, 'Please provide a path to the CocoaDialog binary.'
|
88
|
+
end
|
89
|
+
@@binary = binary
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
data/lib/rum/help.rb
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
module Rum
|
2
|
+
def self.reference
|
3
|
+
reference = File.join(File.dirname(__FILE__), '..', '..',
|
4
|
+
'doc', 'reference.rb')
|
5
|
+
Gui.open_file reference
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.read_key &proc
|
9
|
+
KeyReader.start(hotkey_processor, proc)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.show_hotkey
|
13
|
+
read_key { |hotkey| Gui.message "'#{hotkey}' not active" }
|
14
|
+
Action.hook = lambda do |action|
|
15
|
+
KeyReader.remove_hooks
|
16
|
+
action.show_definition or Gui.message "Definition location unkown."
|
17
|
+
# Must return true for the key that triggered the action to be
|
18
|
+
# retained by the processor.
|
19
|
+
true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.print_next_hotkey
|
24
|
+
read_key { |hotkey| Gui.message hotkey }
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.snippet
|
28
|
+
Gui.message 'Enter hotkey!'
|
29
|
+
read_key do |hotkey|
|
30
|
+
snippet = "'#{hotkey}'.do { }"
|
31
|
+
Keyboard.type! snippet
|
32
|
+
Keyboard.type '(left)(left)'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
module KeyReader
|
37
|
+
extend self
|
38
|
+
attr_accessor :pressed_modifiers
|
39
|
+
|
40
|
+
Hook = proc do
|
41
|
+
@pass_key = false
|
42
|
+
if @key.modifier?
|
43
|
+
if @down
|
44
|
+
KeyReader.pressed_modifiers << @key
|
45
|
+
elsif seen_key = KeyReader.pressed_modifiers.delete(@key)
|
46
|
+
if @key == @last_key
|
47
|
+
KeyReader.stop(@key, @pressed_modifiers)
|
48
|
+
elsif @pressed_modifiers.values.compact.empty?
|
49
|
+
KeyReader.stop
|
50
|
+
end
|
51
|
+
else
|
52
|
+
@pass_key = true
|
53
|
+
end
|
54
|
+
elsif @down and not @was_executed[@key]
|
55
|
+
KeyReader.stop(@key, @pressed_modifiers)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def start(hotkey_processor, proc)
|
60
|
+
@hotkey_processor = hotkey_processor
|
61
|
+
@proc = proc
|
62
|
+
@pressed_modifiers = []
|
63
|
+
add_hooks
|
64
|
+
end
|
65
|
+
|
66
|
+
def stop key=nil, modifiers=nil
|
67
|
+
if key
|
68
|
+
modifiers = modifiers.keys.select { |key| modifiers[key] }
|
69
|
+
key = Hotkey.new(key, modifiers)
|
70
|
+
end
|
71
|
+
remove_hooks
|
72
|
+
@proc.call(key)
|
73
|
+
end
|
74
|
+
|
75
|
+
def add_hooks
|
76
|
+
@hotkey_processor.add_hook Hook
|
77
|
+
Action.hook = lambda { |action| } # Ignore Actions.
|
78
|
+
end
|
79
|
+
|
80
|
+
def remove_hooks
|
81
|
+
@hotkey_processor.remove_hook Hook
|
82
|
+
Action.hook = nil
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
module WindowInfo
|
87
|
+
module_function
|
88
|
+
|
89
|
+
def start
|
90
|
+
return if @thread
|
91
|
+
register_stop_key
|
92
|
+
@stop = false
|
93
|
+
@thread = Thread.new do
|
94
|
+
new = old = nil
|
95
|
+
loop do
|
96
|
+
break if @stop
|
97
|
+
new = active_window
|
98
|
+
if new != old
|
99
|
+
Gui.message new.report
|
100
|
+
old = new
|
101
|
+
end
|
102
|
+
sleep 0.1
|
103
|
+
end
|
104
|
+
Clipboard.set new.report
|
105
|
+
Gui.message 'Window Info stopped.'
|
106
|
+
@thread = nil
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
StopKey = 'escape'
|
111
|
+
|
112
|
+
def register_stop_key
|
113
|
+
@old_action = StopKey.unregister
|
114
|
+
StopKey.do { WindowInfo.stop }
|
115
|
+
Gui.message "Press '#{StopKey}' to stop WindowInfo."
|
116
|
+
end
|
117
|
+
|
118
|
+
def stop
|
119
|
+
return unless @thread
|
120
|
+
@stop = true
|
121
|
+
if @old_action
|
122
|
+
@old_action.register
|
123
|
+
else
|
124
|
+
StopKey.unregister
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,479 @@
|
|
1
|
+
module Rum
|
2
|
+
class Key
|
3
|
+
attr_accessor :name, :id, :aliases, :modifier
|
4
|
+
|
5
|
+
def initialize name, aliases, id
|
6
|
+
@name = name
|
7
|
+
@id = id
|
8
|
+
@aliases = aliases
|
9
|
+
@modifier = false
|
10
|
+
end
|
11
|
+
|
12
|
+
def modifier?
|
13
|
+
@modifier
|
14
|
+
end
|
15
|
+
|
16
|
+
def inspect
|
17
|
+
"#<Key:#{name}>"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Layout
|
22
|
+
attr_accessor :ids, :names, :aliases, :modifiers, :core_modifiers, \
|
23
|
+
:action_modifiers, :translations
|
24
|
+
|
25
|
+
def initialize
|
26
|
+
@ids = {}
|
27
|
+
@names = {}
|
28
|
+
@aliases = {}
|
29
|
+
@modifiers = []
|
30
|
+
@core_modifiers = {}
|
31
|
+
# Windows-specific: Modifiers that trigger actions when pressed and released.
|
32
|
+
@action_modifiers = {}
|
33
|
+
@translations = {}
|
34
|
+
end
|
35
|
+
|
36
|
+
def keys
|
37
|
+
@ids.values.uniq
|
38
|
+
end
|
39
|
+
|
40
|
+
def lookup_id id
|
41
|
+
@ids[id]
|
42
|
+
end
|
43
|
+
|
44
|
+
def [] attribute
|
45
|
+
@names[attribute] or @aliases[attribute] or @ids[attribute]
|
46
|
+
end
|
47
|
+
|
48
|
+
# Modifiers can have the names of other keys as aliases.
|
49
|
+
# This allows for abbreviating 'ctrl shift enter'.do to 'c s enter'.do
|
50
|
+
# Lookup the alias first to avoid matching normal keys.
|
51
|
+
def modifier_lookup attribute
|
52
|
+
if (key = @aliases[attribute] || @names[attribute]) and key.modifier?
|
53
|
+
key
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def add *args, id
|
58
|
+
id += 2**9 if args.delete :extended # Windows-specific magic value.
|
59
|
+
# An explicit implementation might follow
|
60
|
+
name = args.shift
|
61
|
+
aliases = args
|
62
|
+
key = Key.new(name, aliases, id)
|
63
|
+
|
64
|
+
@ids[id] = key
|
65
|
+
@names[name] = key
|
66
|
+
aliases.each { |key_alias| @aliases[key_alias] = key }
|
67
|
+
end
|
68
|
+
|
69
|
+
def alias key, key_alias
|
70
|
+
key = self[key]
|
71
|
+
@aliases[key_alias] = key
|
72
|
+
key.aliases << key_alias
|
73
|
+
end
|
74
|
+
|
75
|
+
def rename from, to
|
76
|
+
key = self[from]
|
77
|
+
@names.delete from
|
78
|
+
key.name = to
|
79
|
+
@names[to] = key
|
80
|
+
end
|
81
|
+
|
82
|
+
def remap *from, to
|
83
|
+
to = self[to]
|
84
|
+
from.each { |key| @ids[self[key].id] = to }
|
85
|
+
end
|
86
|
+
|
87
|
+
def modifier key
|
88
|
+
key = self[key]
|
89
|
+
key.modifier = true
|
90
|
+
@modifiers << key unless @modifiers.include? key
|
91
|
+
key
|
92
|
+
end
|
93
|
+
|
94
|
+
def core_modifier key
|
95
|
+
@core_modifiers[modifier(key)] = true
|
96
|
+
end
|
97
|
+
|
98
|
+
def action_modifier key
|
99
|
+
@action_modifiers[modifier(key)] = true
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
module Layouts
|
104
|
+
def self.list
|
105
|
+
layouts = methods(nil) - [:default_layout, :core, :basic, :list]
|
106
|
+
layouts - Object.methods # Needed for MacRuby, methods(nil) doesn't work here.
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
class Hotkey
|
111
|
+
attr_accessor :key, :modifiers, :actions, :direction # todo: unveränderlich machen
|
112
|
+
|
113
|
+
def initialize key, modifiers=[], fuzzy=false, actions=[]
|
114
|
+
@key = key
|
115
|
+
@modifiers = modifiers
|
116
|
+
@fuzzy = fuzzy
|
117
|
+
@actions = actions
|
118
|
+
@direction = nil
|
119
|
+
end
|
120
|
+
|
121
|
+
def fuzzy?
|
122
|
+
@fuzzy
|
123
|
+
end
|
124
|
+
|
125
|
+
def execute repeated
|
126
|
+
@actions.each { |action| return true if action.execute(repeated) }
|
127
|
+
false
|
128
|
+
end
|
129
|
+
|
130
|
+
def to_s
|
131
|
+
(modifiers.dup << key).map(&:name).join ' '
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
class Action
|
136
|
+
attr_accessor :action, :condition, :hotkey, :location
|
137
|
+
|
138
|
+
@@hook = nil
|
139
|
+
|
140
|
+
def self.work_queue= queue
|
141
|
+
@@work_queue = queue
|
142
|
+
end
|
143
|
+
|
144
|
+
def self.hook= proc
|
145
|
+
@@hook = proc
|
146
|
+
end
|
147
|
+
|
148
|
+
def self.hook
|
149
|
+
@@hook
|
150
|
+
end
|
151
|
+
|
152
|
+
def initialize(action, condition, repeated, location)
|
153
|
+
@action = action
|
154
|
+
@condition = condition
|
155
|
+
@repeated = repeated
|
156
|
+
@location = location
|
157
|
+
end
|
158
|
+
|
159
|
+
def execute repeated
|
160
|
+
return false if not @action or repeated and not @repeated
|
161
|
+
if not @condition or @condition.call
|
162
|
+
if @@hook
|
163
|
+
@@hook.call(self)
|
164
|
+
else
|
165
|
+
@@work_queue.enq @action
|
166
|
+
true
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def show_definition
|
172
|
+
@location.show if @location
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# TODO: Needs refactoring.
|
177
|
+
class HotkeySet
|
178
|
+
attr_accessor :layout, :hotkeys, :modifiers, :down, :up, \
|
179
|
+
:up_fuzzy, :down_fuzzy, :modifier_hotkeys
|
180
|
+
|
181
|
+
def initialize layout
|
182
|
+
@layout = layout
|
183
|
+
# All hotkeys that consist entirely of modifiers
|
184
|
+
@modifier_hotkeys = {}
|
185
|
+
@modifiers = layout.modifiers.sort_by &:id
|
186
|
+
# All modifier combinations of all registered hotkeys
|
187
|
+
@modifier_combinations = Hash.new(0)
|
188
|
+
@up = {}
|
189
|
+
@down = {}
|
190
|
+
@up_fuzzy = {}
|
191
|
+
@down_fuzzy = {}
|
192
|
+
end
|
193
|
+
|
194
|
+
def add_hotkey(string, action, condition, repeated, location)
|
195
|
+
action = Action.new(action, condition, repeated, location)
|
196
|
+
action.hotkey = hotkey_from_string(string)
|
197
|
+
register(action)
|
198
|
+
end
|
199
|
+
|
200
|
+
def remove_hotkey string
|
201
|
+
hotkey = hotkey_from_string(string)
|
202
|
+
Rum.hotkey_set.unregister_conditionless_action(hotkey)
|
203
|
+
end
|
204
|
+
|
205
|
+
# Implement translations in terms of normal hotkeys.
|
206
|
+
# TODO: Extending HotkeyProcessor to natively support translations might be a
|
207
|
+
# simpler and more solid approach.
|
208
|
+
def add_translation(string, to, condition, location)
|
209
|
+
down_hotkey = hotkey_from_string(string)
|
210
|
+
up_hotkey = hotkey_from_string(string)
|
211
|
+
down_hotkey.direction = :down
|
212
|
+
up_hotkey.direction = :up
|
213
|
+
to_key = @layout[to]
|
214
|
+
|
215
|
+
catch_modifier_up = proc do
|
216
|
+
if down_hotkey.modifiers.include? @key
|
217
|
+
send_key_event(to_key, false)
|
218
|
+
remove_hook(catch_modifier_up)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
down_action = Action.new( lambda do
|
222
|
+
send_key_event(to_key, true)
|
223
|
+
Rum.hotkey_processor.add_hook(catch_modifier_up)
|
224
|
+
end,
|
225
|
+
condition, true, location)
|
226
|
+
up_action = Action.new( lambda do
|
227
|
+
send_key_event(to_key, false)
|
228
|
+
Rum.hotkey_processor.remove_hook(catch_modifier_up)
|
229
|
+
end,
|
230
|
+
condition, true, location)
|
231
|
+
down_action.hotkey = down_hotkey
|
232
|
+
up_action.hotkey = up_hotkey
|
233
|
+
register(down_action)
|
234
|
+
register(up_action)
|
235
|
+
end
|
236
|
+
|
237
|
+
def hotkey_from_string str
|
238
|
+
*modifier_aliases, key_alias = str.split(' ')
|
239
|
+
fuzzy = !!modifier_aliases.delete('*')
|
240
|
+
|
241
|
+
# Lookup modifiers
|
242
|
+
modifiers = []
|
243
|
+
modifier_aliases.each do |modifier_alias|
|
244
|
+
if key = @layout.modifier_lookup(modifier_alias)
|
245
|
+
modifiers << key
|
246
|
+
else
|
247
|
+
raise "Invalid modifier: #{modifier_alias}"
|
248
|
+
end
|
249
|
+
end
|
250
|
+
modifiers = modifiers.sort_by &:id
|
251
|
+
|
252
|
+
# Lookup key
|
253
|
+
key = @layout[key_alias]
|
254
|
+
raise "#{key_alias} is no valid key." unless key
|
255
|
+
|
256
|
+
Hotkey.new(key, modifiers, fuzzy)
|
257
|
+
end
|
258
|
+
|
259
|
+
def register action
|
260
|
+
hotkey = register_hotkey(action.hotkey)
|
261
|
+
if action.condition
|
262
|
+
hotkey.actions.unshift action # put actions with conditions first
|
263
|
+
else
|
264
|
+
# Only one conditionless action per hotkey
|
265
|
+
unless last_action = hotkey.actions.last and last_action.condition
|
266
|
+
hotkey.actions.pop
|
267
|
+
end
|
268
|
+
hotkey.actions << action
|
269
|
+
end
|
270
|
+
action
|
271
|
+
end
|
272
|
+
|
273
|
+
def unregister action
|
274
|
+
if hotkey = action.hotkey
|
275
|
+
hotkey.actions.delete(action)
|
276
|
+
unregister_hotkey(hotkey) if hotkey.actions.empty?
|
277
|
+
action
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
def unregister_conditionless_action hotkey
|
282
|
+
if hotkey = lookup_hotkey(hotkey) and action = hotkey.actions.last
|
283
|
+
unregister action unless action.condition
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
def get_dict(hotkey)
|
288
|
+
if hotkey.fuzzy?
|
289
|
+
dict = if hotkey.direction == :up
|
290
|
+
@up_fuzzy
|
291
|
+
else
|
292
|
+
@down_fuzzy
|
293
|
+
end
|
294
|
+
dict = (dict[hotkey.key] ||= {})
|
295
|
+
dict_key = hotkey.modifiers
|
296
|
+
else
|
297
|
+
dict_key = key_signature(hotkey)
|
298
|
+
dict = if (dir = hotkey.direction and dir == :up) or \
|
299
|
+
(hotkey.key.modifier? and @modifier_combinations[dict_key] > 0)
|
300
|
+
@up
|
301
|
+
else
|
302
|
+
@down
|
303
|
+
end
|
304
|
+
end
|
305
|
+
[dict, dict_key]
|
306
|
+
end
|
307
|
+
|
308
|
+
def key_signature hotkey
|
309
|
+
hotkey.modifiers.dup << hotkey.key
|
310
|
+
end
|
311
|
+
|
312
|
+
def lookup_hotkey hotkey
|
313
|
+
dict, dict_key = get_dict(hotkey)
|
314
|
+
dict[dict_key]
|
315
|
+
end
|
316
|
+
|
317
|
+
def register_hotkey hotkey
|
318
|
+
dict, dict_key = get_dict(hotkey)
|
319
|
+
existing_hotkey = dict[dict_key] and return existing_hotkey
|
320
|
+
|
321
|
+
dict[dict_key] = hotkey
|
322
|
+
if hotkey.key.modifier? # Modifier hotkeys aren't allowed to be fuzzy.
|
323
|
+
@modifier_hotkeys[dict_key] = hotkey
|
324
|
+
else
|
325
|
+
register_modifier_combination(hotkey.modifiers)
|
326
|
+
end
|
327
|
+
hotkey
|
328
|
+
end
|
329
|
+
|
330
|
+
def unregister_hotkey hotkey
|
331
|
+
dict, dict_key = get_dict(hotkey)
|
332
|
+
if dict.delete(dict_key)
|
333
|
+
if hotkey.key.modifier?
|
334
|
+
@modifier_hotkeys.delete(hotkey)
|
335
|
+
else
|
336
|
+
unregister_modifier_combination(hotkey.modifiers)
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
def register_modifier_combination(modifiers)
|
342
|
+
maybe_reregister_mod_hotkey(modifiers, 1, 0)
|
343
|
+
end
|
344
|
+
|
345
|
+
def unregister_modifier_combination(modifiers)
|
346
|
+
maybe_reregister_mod_hotkey(modifiers, -1, 1)
|
347
|
+
end
|
348
|
+
|
349
|
+
# Example:
|
350
|
+
# 1. The modifier hotkey 'ctrl shift' is the only registered hotkey.
|
351
|
+
# 2. The hotkey 'ctrl shift a' gets registered.
|
352
|
+
# Now the 'ctrl shift' hotkey needs to be re-registered to avoid
|
353
|
+
# getting triggered while 'ctrl shift a' is pressed.
|
354
|
+
#
|
355
|
+
# Vice versa:
|
356
|
+
# When all conflicting modifier combinations have been
|
357
|
+
# unregistered, a modifier hotkey can be re-registered to trigger
|
358
|
+
# instantly.
|
359
|
+
#
|
360
|
+
# This function keeps track of active modifier_combinations when
|
361
|
+
# hotkeys ar added or removed. It re-registers corresponding
|
362
|
+
# modifier hotkeys accordingly.
|
363
|
+
def maybe_reregister_mod_hotkey(modifiers, change_count, threshold)
|
364
|
+
count = @modifier_combinations[modifiers]
|
365
|
+
if count == threshold and (mod_hotkey = @modifier_hotkeys[modifiers])
|
366
|
+
unregister_hotkey(mod_hotkey)
|
367
|
+
@modifier_combinations[modifiers] = count + change_count
|
368
|
+
register_hotkey(mod_hotkey)
|
369
|
+
else
|
370
|
+
@modifier_combinations[modifiers] = count + change_count
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
def lookup(down, signature)
|
375
|
+
(down ? @down : @up)[signature]
|
376
|
+
end
|
377
|
+
|
378
|
+
def fuzzy_lookup(down, key, pressed_modifiers)
|
379
|
+
if (hotkeys = (down ? @down_fuzzy : @up_fuzzy)[key])
|
380
|
+
hotkeys.each do |modifiers, hotkey|
|
381
|
+
return hotkey if modifiers.all? { |mod| pressed_modifiers[mod] }
|
382
|
+
end
|
383
|
+
nil
|
384
|
+
end
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
# TODO: Needs refactoring.
|
389
|
+
class HotkeyProcessor
|
390
|
+
attr_reader :layout, :pressed_modifiers, :hooks
|
391
|
+
def initialize hotkey_set
|
392
|
+
@hotkey_set = hotkey_set
|
393
|
+
@modifiers = @hotkey_set.modifiers
|
394
|
+
@layout = @hotkey_set.layout
|
395
|
+
@pressed_modifiers = {}
|
396
|
+
@was_executed = {}
|
397
|
+
@key_signature = nil
|
398
|
+
@hooks = []
|
399
|
+
@pass_key = true
|
400
|
+
|
401
|
+
@key = nil
|
402
|
+
@last_key = nil
|
403
|
+
@down = nil
|
404
|
+
@last_event_up = nil
|
405
|
+
end
|
406
|
+
|
407
|
+
def add_hook(hook=nil, &block)
|
408
|
+
hook ||= block
|
409
|
+
@hooks.unshift hook
|
410
|
+
hook
|
411
|
+
end
|
412
|
+
|
413
|
+
# Return removed hook.
|
414
|
+
def remove_hook(hook)
|
415
|
+
@hooks.delete hook
|
416
|
+
end
|
417
|
+
|
418
|
+
def key_signature key
|
419
|
+
@modifiers.select { |modifier| @pressed_modifiers[modifier] } << key
|
420
|
+
end
|
421
|
+
|
422
|
+
def execute(down, repeated)
|
423
|
+
(hotkey = @hotkey_set.lookup(down, @key_signature) \
|
424
|
+
and hotkey.execute(repeated)) or
|
425
|
+
(hotkey = @hotkey_set.fuzzy_lookup(down, @key, @pressed_modifiers) \
|
426
|
+
and hotkey.execute(repeated))
|
427
|
+
end
|
428
|
+
|
429
|
+
def process_event event
|
430
|
+
puts event
|
431
|
+
@key = @layout.lookup_id(event.id)
|
432
|
+
@down = event.down?
|
433
|
+
unless @key
|
434
|
+
puts "Unknown Key\n"
|
435
|
+
return true
|
436
|
+
end
|
437
|
+
modifier = @key.modifier?
|
438
|
+
|
439
|
+
# Skip generating unnecessary key_signatures
|
440
|
+
if modifier and @last_event_up or @key != @last_key
|
441
|
+
@key_signature = key_signature(@key)
|
442
|
+
end
|
443
|
+
@last_event_up = !@down
|
444
|
+
|
445
|
+
print "[#{@key_signature.map { |key| key.name.capitalize }.join(', ')}]"
|
446
|
+
print ' (again)' if @key == @last_key; puts
|
447
|
+
|
448
|
+
if @down
|
449
|
+
repeated = @was_executed[@key]
|
450
|
+
eat = @was_executed[@key] = true if execute(true, repeated)
|
451
|
+
if repeated
|
452
|
+
eat = true
|
453
|
+
elsif modifier # if repeated, the modifier has already been added to pressed_modifiers
|
454
|
+
@pressed_modifiers[@key] = true
|
455
|
+
end
|
456
|
+
else #up
|
457
|
+
@was_executed[@key] = true if execute(false, false)
|
458
|
+
if modifier
|
459
|
+
@pressed_modifiers[@key] = nil
|
460
|
+
if @layout.action_modifiers[@key] and \
|
461
|
+
(!@pass_key or @was_executed[@key])
|
462
|
+
inhibit_modifier_action
|
463
|
+
end
|
464
|
+
end
|
465
|
+
eat = @was_executed.delete(@key)
|
466
|
+
end
|
467
|
+
|
468
|
+
@pass_key = if modifier
|
469
|
+
@layout.core_modifiers[@key]
|
470
|
+
else
|
471
|
+
!eat
|
472
|
+
end
|
473
|
+
@hooks.dup.each { |hook| instance_eval &hook }
|
474
|
+
@last_key = @key
|
475
|
+
puts
|
476
|
+
@pass_key
|
477
|
+
end
|
478
|
+
end
|
479
|
+
end
|