rum 0.0.1-universal-darwin-10
Sign up to get free protection for your applications and to get access to all the features.
- 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/CocoaDialog.app/Contents/Info.plist +28 -0
- data/lib/rum/mac/gui/CocoaDialog.app/Contents/MacOS/CocoaDialog +0 -0
- data/lib/rum/mac/gui/CocoaDialog.app/Contents/Resources/Info.plist +28 -0
- data/lib/rum/mac/gui/CocoaDialog.app/Contents/Resources/InfoPlist.strings +0 -0
- data/lib/rum/mac/gui/CocoaDialog.app/Contents/Resources/Inputbox.nib/classes.nib +51 -0
- data/lib/rum/mac/gui/CocoaDialog.app/Contents/Resources/Inputbox.nib/info.nib +16 -0
- data/lib/rum/mac/gui/CocoaDialog.app/Contents/Resources/Inputbox.nib/keyedobjects.nib +0 -0
- data/lib/rum/mac/gui/CocoaDialog.app/Contents/Resources/MainMenu.nib/classes.nib +7 -0
- data/lib/rum/mac/gui/CocoaDialog.app/Contents/Resources/MainMenu.nib/info.nib +21 -0
- data/lib/rum/mac/gui/CocoaDialog.app/Contents/Resources/MainMenu.nib/info.nib.orig +21 -0
- data/lib/rum/mac/gui/CocoaDialog.app/Contents/Resources/MainMenu.nib/objects.nib +0 -0
- data/lib/rum/mac/gui/CocoaDialog.app/Contents/Resources/MainMenu.nib/objects.nib.orig +0 -0
- data/lib/rum/mac/gui/CocoaDialog.app/Contents/Resources/Msgbox.nib/classes.nib +27 -0
- data/lib/rum/mac/gui/CocoaDialog.app/Contents/Resources/Msgbox.nib/info.nib +16 -0
- data/lib/rum/mac/gui/CocoaDialog.app/Contents/Resources/Msgbox.nib/keyedobjects.nib +0 -0
- data/lib/rum/mac/gui/Growl.framework/Growl +0 -0
- data/lib/rum/mac/gui/Growl.framework/Versions/A/Growl +0 -0
- data/lib/rum/mac/gui/Growl.framework/Versions/A/Headers/Growl.h +6 -0
- data/lib/rum/mac/gui/Growl.framework/Versions/A/Headers/GrowlApplicationBridge-Carbon.h +780 -0
- data/lib/rum/mac/gui/Growl.framework/Versions/A/Headers/GrowlApplicationBridge.h +575 -0
- data/lib/rum/mac/gui/Growl.framework/Versions/A/Headers/GrowlDefines.h +348 -0
- data/lib/rum/mac/gui/Growl.framework/Versions/A/Resources/Info.plist +24 -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/keyboard_hook/KeyboardHook.framework/KeyboardHook +0 -0
- data/lib/rum/mac/keyboard_hook/KeyboardHook.framework/Versions/A/KeyboardHook +0 -0
- data/lib/rum/mac/keyboard_hook/KeyboardHook.framework/Versions/A/Resources/English.lproj/InfoPlist.strings +0 -0
- data/lib/rum/mac/keyboard_hook/KeyboardHook.framework/Versions/A/Resources/Info.plist +22 -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/layouts.rb +232 -0
- data/lib/rum/windows/system.rb +310 -0
- data/lib/rum/windows/system_foreign_functions.rb +129 -0
- data/rum.gemspec +14 -0
- metadata +166 -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
|