rum 0.0.1-x86-mswin32-60

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. data/CHANGELOG +3 -0
  2. data/README +30 -0
  3. data/Rakefile +134 -0
  4. data/bin/rum-client +124 -0
  5. data/doc/basic.rb +10 -0
  6. data/doc/doc.html +602 -0
  7. data/doc/example.rb +59 -0
  8. data/doc/reference.rb +415 -0
  9. data/doc/resources/bg.png +0 -0
  10. data/doc/resources/bottom.png +0 -0
  11. data/doc/resources/build.rb +235 -0
  12. data/doc/resources/doc.haml +167 -0
  13. data/doc/resources/emacs-auto-completion.png +0 -0
  14. data/doc/resources/flash.png +0 -0
  15. data/doc/resources/highlight.css +94 -0
  16. data/doc/resources/intro.rb +17 -0
  17. data/doc/resources/left.png +0 -0
  18. data/doc/resources/logo.png +0 -0
  19. data/doc/resources/screen.css +420 -0
  20. data/doc/resources/screenshot.png +0 -0
  21. data/doc/resources/top.png +0 -0
  22. data/ext/mac/keyboard_hook/English.lproj/InfoPlist.strings +0 -0
  23. data/ext/mac/keyboard_hook/Event.h +17 -0
  24. data/ext/mac/keyboard_hook/Event.m +18 -0
  25. data/ext/mac/keyboard_hook/EventTap.h +11 -0
  26. data/ext/mac/keyboard_hook/EventTap.m +77 -0
  27. data/ext/mac/keyboard_hook/Info.plist +26 -0
  28. data/ext/mac/keyboard_hook/KeyboardHook.xcodeproj/TemplateIcon.icns +0 -0
  29. data/ext/mac/keyboard_hook/KeyboardHook.xcodeproj/project.pbxproj +323 -0
  30. data/ext/mac/keyboard_hook/KeyboardHook_Prefix.pch +7 -0
  31. data/ext/mac/keyboard_hook/version.plist +16 -0
  32. data/ext/windows/keyboard_hook/extconf.rb +2 -0
  33. data/ext/windows/keyboard_hook/keyboard_hook.c +126 -0
  34. data/ext/windows/system/autohotkey_stuff.c +255 -0
  35. data/ext/windows/system/autohotkey_stuff.h +2 -0
  36. data/ext/windows/system/clipboard_watcher.c +58 -0
  37. data/ext/windows/system/clipboard_watcher.h +2 -0
  38. data/ext/windows/system/extconf.rb +3 -0
  39. data/ext/windows/system/input_box.c +239 -0
  40. data/ext/windows/system/input_box.h +4 -0
  41. data/ext/windows/system/system.c +273 -0
  42. data/lib/rum.rb +4 -0
  43. data/lib/rum/apps.rb +4 -0
  44. data/lib/rum/barrel.rb +157 -0
  45. data/lib/rum/barrel/emacs.rb +44 -0
  46. data/lib/rum/barrel/emacs_client.rb +74 -0
  47. data/lib/rum/core.rb +125 -0
  48. data/lib/rum/dsl.rb +109 -0
  49. data/lib/rum/gui.rb +93 -0
  50. data/lib/rum/help.rb +128 -0
  51. data/lib/rum/hotkey_core.rb +479 -0
  52. data/lib/rum/mac.rb +18 -0
  53. data/lib/rum/mac/app.rb +4 -0
  54. data/lib/rum/mac/apps.rb +19 -0
  55. data/lib/rum/mac/gui.rb +26 -0
  56. data/lib/rum/mac/gui/growl.rb +54 -0
  57. data/lib/rum/mac/irb/completion.rb +207 -0
  58. data/lib/rum/mac/keyboard_hook.rb +73 -0
  59. data/lib/rum/mac/layouts.rb +146 -0
  60. data/lib/rum/mac/system.rb +45 -0
  61. data/lib/rum/remote.rb +48 -0
  62. data/lib/rum/server.rb +92 -0
  63. data/lib/rum/windows.rb +23 -0
  64. data/lib/rum/windows/app.rb +72 -0
  65. data/lib/rum/windows/apps.rb +25 -0
  66. data/lib/rum/windows/gui.rb +116 -0
  67. data/lib/rum/windows/keyboard.rb +80 -0
  68. data/lib/rum/windows/keyboard_hook.rb +20 -0
  69. data/lib/rum/windows/keyboard_hook.so +0 -0
  70. data/lib/rum/windows/layouts.rb +232 -0
  71. data/lib/rum/windows/system.rb +310 -0
  72. data/lib/rum/windows/system.so +0 -0
  73. data/lib/rum/windows/system_foreign_functions.rb +129 -0
  74. data/rum.gemspec +14 -0
  75. metadata +156 -0
@@ -0,0 +1,45 @@
1
+ module Rum
2
+ module System
3
+ extend self
4
+
5
+ def applescript src
6
+ pointer = Pointer.new_with_type("@")
7
+ as = NSAppleScript.alloc.initWithSource(src)
8
+ as.executeAndReturnError(pointer)
9
+ end
10
+
11
+ def start *args
12
+ system 'open', *args
13
+ end
14
+
15
+ def escape_shell_word str
16
+ if str.empty? or %r{\A[0-9A-Za-z+,./:=@_-]+\z} =~ str
17
+ str
18
+ else
19
+ result = ''
20
+ str.scan(/('+)|[^']+/) {
21
+ if $1
22
+ result << %q{\'} * $1.length
23
+ else
24
+ result << "'#{$&}'"
25
+ end
26
+ }
27
+ result
28
+ end
29
+ end
30
+
31
+ def applescript_quote_string str
32
+ str.gsub!('\\', '\\\\\\\\')
33
+ str.gsub!('"', '\\"')
34
+ '"' << str << '"'
35
+ end
36
+
37
+ def spawn_in_terminal(*args)
38
+ close = args.delete :close_if_successful
39
+ command = args.map { |arg| escape_shell_word(arg) }.join(' ')
40
+ command << ';[ $? -eq 0 ] && exit' if close
41
+ command = applescript_quote_string(command)
42
+ applescript 'tell application "Terminal" to do script ' + command
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,48 @@
1
+ require 'socket'
2
+
3
+ module Rum
4
+ class Remote
5
+ def initialize(port=Remote.default_port)
6
+ @connection = Connection.new(TCPSocket.open('127.0.0.1', port))
7
+ end
8
+
9
+ def eval code
10
+ @connection.dispatch code
11
+ @connection.receive
12
+ end
13
+
14
+ def disconnect
15
+ @connection.close
16
+ end
17
+
18
+ def self.default_port
19
+ if (port = ENV['RUM_PORT'].to_i) and port.nonzero?
20
+ port
21
+ else
22
+ 1994
23
+ end
24
+ end
25
+
26
+ module Connection
27
+ module Messaging
28
+ # Assumes message is not larger than 4,3 GB ((2**(4*8) - 1) bytes)
29
+ def dispatch(msg)
30
+ msg.encode! Encoding::UTF_8
31
+ msg.force_encoding Encoding::BINARY
32
+ write([msg.length].pack('N') + msg)
33
+ end
34
+
35
+ def receive
36
+ if message_size = read(4) # sizeof (N)
37
+ message_size = message_size.unpack('N')[0]
38
+ read(message_size).force_encoding(Encoding::UTF_8)
39
+ end
40
+ end
41
+ end
42
+
43
+ def Connection.new(stream)
44
+ stream.extend Messaging
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,92 @@
1
+ require 'rum/remote'
2
+
3
+ module Rum
4
+ module Server
5
+ module_function
6
+
7
+ # This makes it easier for code to check if it runs inside the server thread.
8
+ def thread
9
+ @thread
10
+ end
11
+
12
+ def start
13
+ return if @thread
14
+ @thread = Thread.new do
15
+ @server = TCPServer.new('127.0.0.1', Remote.default_port)
16
+ puts "Server started."
17
+ begin
18
+ @connection = nil
19
+ loop do
20
+ @connection = Remote::Connection.new(@server.accept)
21
+ handle(@connection)
22
+ @connection.close
23
+ end
24
+ ensure # clean up when thread gets killed
25
+ close_connections
26
+ end
27
+ end
28
+ end
29
+
30
+ # Temporary hack.
31
+ # MacRuby crashes on the ensure clause above when
32
+ # the server thread is killed.
33
+ # This function allows for manually closing the connections.
34
+ def close_connections
35
+ [@server, @connection].compact.each { |e| e.close unless e.closed? }
36
+ end
37
+
38
+ def stop
39
+ if @thread
40
+ @thread.kill
41
+ # Kill can return before the thread has finished execution. A Ruby bug?
42
+ @thread.join
43
+ @thread = nil
44
+ end
45
+ end
46
+
47
+ EvalBinding = TOPLEVEL_BINDING
48
+
49
+ def handle(connection)
50
+ return nil unless message = connection.receive
51
+ result = begin
52
+ eval(message, EvalBinding).inspect
53
+ rescue Exception => exception
54
+ error_message(exception)
55
+ end
56
+ connection.dispatch(result)
57
+ rescue SystemCallError # connection errors
58
+ end
59
+
60
+ def error_message(exception)
61
+ # Hide the internals from the backtrace
62
+ backtrace = exception.backtrace.reject { |frame| frame =~ /^#{__FILE__}/ }
63
+ msg = ["Rum-Server: Evaluation Error."]
64
+ msg << "#{exception.class}: #{exception}"
65
+ msg += backtrace
66
+ msg.join "\n"
67
+ end
68
+
69
+ module IRBCompletion
70
+ Glue = 'binding = Rum::Server::EvalBinding
71
+ workspace = Struct.new(:binding).new(binding)
72
+ context = Struct.new(:workspace).new(workspace)
73
+ @CONF = { MAIN_CONTEXT: context }
74
+
75
+ def IRB.conf
76
+ @CONF
77
+ end'
78
+
79
+ def self.setup
80
+ unless defined? IRB
81
+ # Since version 0.7, MacRuby ships with an incompatible IRB
82
+ # distribution called dietirb. Mac Rum includes a partial
83
+ # copy of the original IRB.
84
+ require Rum::Platform == :mac ? 'rum/mac/irb/completion' : 'irb/completion'
85
+ IRB.module_eval(Glue)
86
+ end
87
+ end
88
+ end
89
+
90
+ IRBCompletion.setup
91
+ end
92
+ end
@@ -0,0 +1,23 @@
1
+ require 'rum/windows/system'
2
+ require 'rum/windows/keyboard_hook'
3
+ require 'rum/windows/keyboard'
4
+ require 'rum/windows/gui'
5
+ require 'rum/windows/app'
6
+ require 'rum/windows/layouts'
7
+
8
+ module Rum
9
+ Platform = :windows
10
+
11
+ def self.restart_platform_specific
12
+ Rum::Server.stop
13
+ System.spawn_in_terminal('ruby', $PROGRAM_NAME)
14
+ sleep 0.01 # This prevents a strange lag. Wow.
15
+ System.terminal_window.close
16
+ end
17
+
18
+ class HotkeyProcessor
19
+ def inhibit_modifier_action
20
+ System.send_keypress @layout['control']
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,72 @@
1
+ module Rum
2
+ class App
3
+ include System
4
+
5
+ @@apps = {}
6
+
7
+ def self.add(app)
8
+ @@apps[app.binary] = app
9
+ end
10
+
11
+ def self.remove(app)
12
+ @@apps.delete(app.binary)
13
+ end
14
+
15
+ def self.apps
16
+ @@apps
17
+ end
18
+
19
+ def self.for_exe(exe)
20
+ @@apps[exe.downcase]
21
+ end
22
+
23
+ attr_accessor :path, :binary, :matcher
24
+
25
+ def initialize(path, *matcher)
26
+ @path = append_path_to_programfiles_if_relative(path)
27
+ @binary = File.basename(@path).sub(/\.exe$/, '').downcase
28
+ @matcher = WindowMatcher.new(*matcher)
29
+ App.add(self)
30
+ end
31
+
32
+ def append_path_to_programfiles_if_relative(path)
33
+ if path !~ /^\w:|^%/
34
+ path = File.join(ENV['PROGRAMFILES'].gsub("\\", '/'), path)
35
+ end
36
+ path
37
+ end
38
+
39
+ def binary=(bin)
40
+ App.remove(self)
41
+ @binary = bin.downcase
42
+ App.add(self)
43
+ end
44
+
45
+ # Returns 'true' if the application window could be activated instantly.
46
+ def activate
47
+ window = @matcher.find
48
+ if window
49
+ window.show
50
+ else
51
+ run
52
+ false
53
+ end
54
+ end
55
+
56
+ def run
57
+ start @path
58
+ end
59
+
60
+ def to_matcher
61
+ @matcher
62
+ end
63
+
64
+ def active?
65
+ @matcher.active?
66
+ end
67
+
68
+ def window
69
+ @matcher.find
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+ Emacs = App.new('', nil, 'Emacs')
3
+ Emacs.binary = 'emacs'
4
+ require 'rum/barrel/emacs'
5
+
6
+ Firefox = App.new('mozilla firefox/firefox', /firefox/, 'MozillaWindowClass')
7
+ def Firefox.activate_and_focus_address_bar
8
+ Keyboard.type '(ctrl l)' if activate
9
+ end
10
+
11
+ Photoshop = App.new('Adobe/Adobe Photoshop CS5/Photoshop', nil, 'Photoshop')
12
+ class << Photoshop
13
+ def next_blend_mode
14
+ if Rum.layout['ö'] # german layout
15
+ type '(shift 0)'
16
+ else
17
+ type '(shift +)'
18
+ end
19
+ end
20
+
21
+ def previous_blend_mode
22
+ type '(shift -)'
23
+ end
24
+ end
25
+
@@ -0,0 +1,116 @@
1
+ module Rum
2
+ module Gui
3
+ def goto path
4
+ if File.directory? path
5
+ System.start path
6
+ else
7
+ Gui.message "Can't visit a file in Windows Explorer."
8
+ end
9
+ end
10
+
11
+ def open_file file, line=nil
12
+ System.start file
13
+ end
14
+
15
+ private
16
+
17
+ def browse_backend url
18
+ System.start url
19
+ end
20
+
21
+ module WindowsDialogs
22
+ private
23
+ def alert_backend text, title
24
+ System.message_box text, title
25
+ end
26
+
27
+ def message_backend text, title, sticky, callback
28
+ alert text, title
29
+ end
30
+
31
+ def read_backend text, title, initial_value
32
+ System.input_box text, title, initial_value
33
+ end
34
+ end
35
+
36
+ use WindowsDialogs
37
+
38
+ module Growl
39
+ Message = 'message'
40
+
41
+ private
42
+
43
+ def message_backend(text, title, sticky, callback)
44
+ unless title
45
+ title = text
46
+ text = nil
47
+ end
48
+ # ruby_gntp raises an error when 'title' is nil or pure whitespace.
49
+ title = 'empty text' if title.to_s.strip.empty?
50
+ if callback
51
+ on_click = lambda { |event| callback.call if event[:callback_result] == "CLICK" }
52
+ end
53
+
54
+ Growl.client.notify(name: Message, title: title,
55
+ text: text, sticky: sticky, &on_click)
56
+ end
57
+
58
+ module_function
59
+
60
+ def auto_setup
61
+ require 'ruby_gntp'
62
+ @growl = GNTP.new("Rum")
63
+ @growl.register(notifications: [{name: Message, enabled: true}])
64
+ true
65
+ rescue Errno::ECONNREFUSED # Growl not running
66
+ @growl = nil
67
+ end
68
+
69
+ def client
70
+ @growl or auto_setup and @growl
71
+ end
72
+ end
73
+
74
+ module EmacsInteraction
75
+ private
76
+
77
+ def interaction(*args)
78
+ emacs = Emacs.window
79
+ return '' unless emacs
80
+ unless (emacs_already_active = emacs.active?)
81
+ old = System.active_window
82
+ emacs.move(400, 400, 800, 300)
83
+ emacs.show
84
+ end
85
+ result = unpack(Emacs.funcall(*args))
86
+ unless emacs_already_active
87
+ wait { old.active? } unless old.show
88
+ emacs.maximize
89
+ end
90
+ result
91
+ end
92
+
93
+ def unpack(output)
94
+ # If we don't get a string back something has gone wrong.
95
+ raise 'emacs returned no string: ' << output unless output[0] == ?"
96
+ # The first line contains the status (1 or 0),
97
+ # the second line contains additional output.
98
+ # See telemacs-format-output.
99
+ output = Emacs.unquote(output)
100
+ if output =~ /\n$/
101
+ ''
102
+ else
103
+ output = output.split(/\n/)
104
+ output[1] or (output[0] == '1' ? true : nil)
105
+ end
106
+ end
107
+
108
+ def choose_backend prompt, choices
109
+ prompt = prompt ? Emacs.quote(prompt) : 'nil'
110
+ fn = 'selekt-external-fast'
111
+ result = interaction(fn, prompt, *choices.map { |item| Emacs.quote(item.to_s) })
112
+ choices[result.to_i] if result
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,80 @@
1
+ require 'strscan'
2
+
3
+ module Rum
4
+ module Keyboard
5
+ extend self
6
+
7
+ def type(key_sequence, *args)
8
+ release_core_modifiers unless args.include? :blind
9
+ type_sequence(key_sequence, args.include?(:slow))
10
+ end
11
+
12
+ def type!(key_sequence, *args)
13
+ release_core_modifiers unless args.include? :blind
14
+ type_sequence_literally(key_sequence, args.include?(:slow))
15
+ end
16
+
17
+ def release_core_modifiers
18
+ pressed = Rum.hotkey_processor.pressed_modifiers
19
+ to_release = Rum.layout.core_modifiers.keys.select { |mod| pressed[mod] }
20
+ to_release.each { |key| System.send_key_event key, false }
21
+ end
22
+
23
+ private
24
+
25
+ def type_sequence_literally(key_sequence, slow)
26
+ key_sequence.chars.each do |char|
27
+ if (key = Rum.layout[char])
28
+ System.send_keypress key
29
+ sleep 0.01 if slow
30
+ elsif (translation = Rum.layout.translations[char])
31
+ type_sequence(translation, slow)
32
+ end
33
+ end
34
+ end
35
+
36
+ def type_sequence(key_sequence, slow)
37
+ s = StringScanner.new(key_sequence)
38
+ pressed_keys = []
39
+ while (char = s.getch)
40
+ case char
41
+ when '\\'
42
+ down_and_up(s.getch, slow)
43
+ when '('
44
+ key = s.scan(/[^() ]+/)
45
+ s.skip /\ /
46
+ down key
47
+ pressed_keys << key
48
+ when ')'
49
+ up pressed_keys.pop
50
+ else
51
+ down_and_up(char, slow)
52
+ end
53
+ sleep 0.01 if slow
54
+ end
55
+ pressed_keys.reverse_each { |key| up key }
56
+ end
57
+
58
+ def send_key(key, down)
59
+ if id = Rum.layout[key]
60
+ System.send_key_event id, down
61
+ end
62
+ end
63
+
64
+ def down(key)
65
+ send_key key, true
66
+ end
67
+
68
+ def up(key)
69
+ send_key key, false
70
+ end
71
+
72
+ def down_and_up(char, slow)
73
+ if (key = Rum.layout[char])
74
+ System.send_keypress key
75
+ elsif (key_sequence = Rum.layout.translations[char])
76
+ type_sequence(key_sequence, slow)
77
+ end
78
+ end
79
+ end
80
+ end