castanaut 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +12 -1
- data/README.txt +41 -18
- data/bin/castanaut +0 -0
- data/cbin/osxautomation +0 -0
- data/lib/castanaut/exceptions.rb +14 -3
- data/lib/castanaut/keys.rb +3 -2
- data/lib/castanaut/main.rb +3 -3
- data/lib/castanaut/movie.rb +305 -175
- data/lib/castanaut/os/mac_os_x.rb +336 -0
- data/lib/castanaut/os/mac_os_x_legacy.rb +223 -0
- data/lib/castanaut/plugin.rb +2 -2
- data/lib/castanaut.rb +53 -57
- data/lib/plugins/ishowuhd.rb +98 -0
- data/lib/plugins/keystack.rb +111 -0
- data/lib/plugins/mousepose.rb +1 -1
- data/lib/plugins/safari.rb +67 -20
- data/lib/plugins/snapz_pro.rb +55 -0
- data/lib/plugins/terminal.rb +55 -0
- data/lib/plugins/textmate.rb +65 -0
- data/test/helper.rb +12 -0
- data/test/main_test.rb +13 -0
- data/test/movie_test.rb +67 -0
- metadata +32 -27
- data/Manifest.txt +0 -31
- data/Rakefile +0 -25
- data/tasks/ann.rake +0 -77
- data/tasks/annotations.rake +0 -22
- data/tasks/doc.rake +0 -49
- data/tasks/gem.rake +0 -110
- data/tasks/manifest.rake +0 -50
- data/tasks/post_load.rake +0 -18
- data/tasks/rubyforge.rake +0 -57
- data/tasks/setup.rb +0 -221
- data/tasks/spec.rake +0 -43
- data/tasks/svn.rake +0 -44
- data/tasks/test.rake +0 -40
- data/test/example_script.screenplay +0 -91
- data/test/googling.screenplay +0 -34
@@ -0,0 +1,336 @@
|
|
1
|
+
module Castanaut; module OS; module MacOSX
|
2
|
+
|
3
|
+
# This class is intended to work on machines running Mac OS X 10.5.x or
|
4
|
+
# greater.
|
5
|
+
#
|
6
|
+
# KNOWN LIMITATIONS
|
7
|
+
#
|
8
|
+
# Partially working:
|
9
|
+
# * type - does not support the :speed option
|
10
|
+
# * hit - only works with special keys (those in keys.rb) not
|
11
|
+
# other characters (like 'a'), and does not support modifier keys
|
12
|
+
# (you can use keystroke instead, perhaps)
|
13
|
+
#
|
14
|
+
class Movie < Castanaut::Movie
|
15
|
+
|
16
|
+
register("Mac OS X 10.5 or greater")
|
17
|
+
|
18
|
+
|
19
|
+
# Returns true if the current platform is Mac OS X 10.5 or greater.
|
20
|
+
#
|
21
|
+
def self.platform_supported?
|
22
|
+
vers = `/usr/bin/sw_vers -productVersion`.match(/10\.(\d)\.\d+/)
|
23
|
+
vers[1].to_i >= 5
|
24
|
+
rescue
|
25
|
+
false
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
#--------------------------------------------------------------------------
|
30
|
+
# KEYBOARD INPUT DIRECTIONS
|
31
|
+
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
32
|
+
|
33
|
+
# Does not support modifiers (shift, ctrl, etc)
|
34
|
+
def hit(key, *modifiers)
|
35
|
+
not_supported "modifier keys for 'hit'" unless modifiers.empty?
|
36
|
+
automatically "hit #{key}"
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
# Hit a command key combo toward the currently active application.
|
41
|
+
#
|
42
|
+
# Use any combination of "command", "option", "control", "shift".
|
43
|
+
# ("command" is the default).
|
44
|
+
#
|
45
|
+
# Case matters! It's easiest to use lowercase, then "shift" if needed.
|
46
|
+
#
|
47
|
+
# keystroke "t" # COMMAND-t
|
48
|
+
# keystroke "k", "control", "shift" # A combo
|
49
|
+
#
|
50
|
+
def keystroke(character, *special_keys)
|
51
|
+
special_keys = ["command"] if special_keys.length == 0
|
52
|
+
special_keys_as_applescript_array = special_keys.map { |k|
|
53
|
+
"#{k} down"
|
54
|
+
}.join(", ")
|
55
|
+
execute_applescript(%Q'
|
56
|
+
tell application "System Events"
|
57
|
+
set frontApp to name of first item of (processes whose frontmost is true)
|
58
|
+
tell application frontApp
|
59
|
+
keystroke "#{character}" using {#{special_keys_as_applescript_array}}
|
60
|
+
end
|
61
|
+
end tell
|
62
|
+
')
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
# If you pass :applescript => true, the AppleScript technique for typing
|
67
|
+
# will be used. In this way you can use the :speed option —
|
68
|
+
# it's not supported by the main (osxautomation) technique.
|
69
|
+
#
|
70
|
+
def type(str, opts = {})
|
71
|
+
if opts.delete(:applescript)
|
72
|
+
type_via_applescript(str, opts)
|
73
|
+
else
|
74
|
+
automatically "type #{str}"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
# The alternative typing method for Mac OS X - lets you set the
|
80
|
+
# typomatic rate with the :speed option.
|
81
|
+
#
|
82
|
+
def type_via_applescript(str, opts = {})
|
83
|
+
opts[:speed] = 50 unless !opts[:speed].nil?
|
84
|
+
opts[:speed] = opts[:speed] / 1000.0
|
85
|
+
|
86
|
+
full_str = ""
|
87
|
+
str.split("").each do |a|
|
88
|
+
a.gsub!(/"/, '\"')
|
89
|
+
full_str += "delay #{opts[:speed]}\n" if !full_str.empty?
|
90
|
+
full_str += "keystroke \"#{a}\"\n"
|
91
|
+
end
|
92
|
+
cmd = %Q'
|
93
|
+
tell application "System Events"
|
94
|
+
set frontApp to name of first item of (processes whose frontmost is true)
|
95
|
+
tell application frontApp
|
96
|
+
#{full_str}
|
97
|
+
end
|
98
|
+
end tell
|
99
|
+
'
|
100
|
+
execute_applescript cmd
|
101
|
+
str
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
#---------------------------------------------------------------------------
|
106
|
+
# MOUSE INPUT DIRECTIONS
|
107
|
+
#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
108
|
+
|
109
|
+
def cursor(*options)
|
110
|
+
options = combine_options(*options)
|
111
|
+
|
112
|
+
apply_offset(options)
|
113
|
+
@cursor_loc ||= {}
|
114
|
+
@cursor_loc[:x] = options[:to][:left]
|
115
|
+
@cursor_loc[:y] = options[:to][:top]
|
116
|
+
|
117
|
+
automatically "mousemove #{@cursor_loc[:x]} #{@cursor_loc[:y]}"
|
118
|
+
end
|
119
|
+
|
120
|
+
alias :move :cursor
|
121
|
+
|
122
|
+
|
123
|
+
def cursor_location
|
124
|
+
loc = automatically("mouselocation").strip.split(' ')
|
125
|
+
{:x => loc[0].to_i, :y => loc[1].to_i}
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
def click(btn = "left")
|
130
|
+
automatically "mouseclick #{mouse_button_translate(btn)}"
|
131
|
+
end
|
132
|
+
|
133
|
+
|
134
|
+
def doubleclick(btn = "left")
|
135
|
+
automatically "mousedoubleclick #{mouse_button_translate(btn)}"
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
def tripleclick(btn = "left")
|
140
|
+
automatically "mousetripleclick #{mouse_button_translate(btn)}"
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
def mousedown(btn = "left")
|
145
|
+
automatically "mousedown #{mouse_button_translate(btn)}"
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
def mouseup(btn = "left")
|
150
|
+
automatically "mouseup #{mouse_button_translate(btn)}"
|
151
|
+
end
|
152
|
+
|
153
|
+
|
154
|
+
def drag(*options)
|
155
|
+
options = combine_options(*options)
|
156
|
+
apply_offset(options)
|
157
|
+
automatically "mousedrag #{options[:to][:left]} #{options[:to][:top]}"
|
158
|
+
end
|
159
|
+
|
160
|
+
|
161
|
+
#--------------------------------------------------------------------------
|
162
|
+
# WINDOWS AND APPLICATIONS DIRECTIONS
|
163
|
+
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
164
|
+
|
165
|
+
# The method will also look for application-specific commands for
|
166
|
+
# ensuring that a window is open & positioning that window.
|
167
|
+
# These methods should be named +ensure_window_for_app_name+
|
168
|
+
# and +positioning_for_app_name+ respectively. So, if you launch
|
169
|
+
# the "Address Book" application, the +ensure_window_for_address_book+
|
170
|
+
# and +positioning_for_address_book+ methods will be used.
|
171
|
+
#
|
172
|
+
# See Plugin::Safari#ensure_window_for_safari for an example.
|
173
|
+
#
|
174
|
+
def launch(app_name, *options)
|
175
|
+
options = combine_options(*options)
|
176
|
+
|
177
|
+
ensure_window = nil
|
178
|
+
begin
|
179
|
+
ensure_window = send("ensure_window_for_#{ app_name.downcase }")
|
180
|
+
rescue
|
181
|
+
end
|
182
|
+
ensure_window ||= ""
|
183
|
+
|
184
|
+
positioning = nil
|
185
|
+
begin
|
186
|
+
positioning = send("positioning_for_#{ app_name.downcase }")
|
187
|
+
rescue
|
188
|
+
end
|
189
|
+
unless positioning
|
190
|
+
if options[:to]
|
191
|
+
pos = "#{options[:to][:left]}, #{options[:to][:top]}"
|
192
|
+
dims = "#{options[:to][:left] + options[:to][:width]}, " +
|
193
|
+
"#{options[:to][:top] + options[:to][:height]}"
|
194
|
+
if options[:to][:width]
|
195
|
+
positioning = "set bounds of front window to {#{pos}, #{dims}}"
|
196
|
+
else
|
197
|
+
positioning = "set position of front window to {#{pos}}"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
execute_applescript(%Q`
|
203
|
+
tell application "#{app_name}"
|
204
|
+
activate
|
205
|
+
#{ensure_window}
|
206
|
+
#{positioning}
|
207
|
+
end tell
|
208
|
+
`)
|
209
|
+
end
|
210
|
+
|
211
|
+
alias :activate :launch
|
212
|
+
|
213
|
+
|
214
|
+
# Returns a region hash describing the entire screen area.
|
215
|
+
# (May be wonky for multi-monitor set-ups.)
|
216
|
+
#
|
217
|
+
def screen_size
|
218
|
+
coords = execute_applescript(%Q`
|
219
|
+
tell application "Finder"
|
220
|
+
get bounds of window of desktop
|
221
|
+
end tell
|
222
|
+
`)
|
223
|
+
coords = coords.split(", ").collect {|c| c.to_i}
|
224
|
+
to(*coords)
|
225
|
+
end
|
226
|
+
|
227
|
+
|
228
|
+
# Click a menu item in any application. The name of the application
|
229
|
+
# should be the first argument.
|
230
|
+
#
|
231
|
+
# Three dots will be automatically replaced by the appropriate ellipsis.
|
232
|
+
#
|
233
|
+
# click_menu_item("TextMate", "Navigation", "Go to Symbol...")
|
234
|
+
#
|
235
|
+
# Based on menu_click, by Jacob Rus, September 2006:
|
236
|
+
# http://www.macosxhints.com/article.php?story=20060921045743404
|
237
|
+
#
|
238
|
+
def click_menu_item(*items)
|
239
|
+
items_as_applescript_array = items.map { |i|
|
240
|
+
%("#{i.gsub('...', "\342\200\246")}")
|
241
|
+
}.join(", ")
|
242
|
+
|
243
|
+
ascript = %Q`
|
244
|
+
on menu_click(mList)
|
245
|
+
local appName, topMenu, r
|
246
|
+
if mList's length < 3 then error "Menu list is not long enough"
|
247
|
+
|
248
|
+
set {appName, topMenu} to (items 1 through 2 of mList)
|
249
|
+
set r to (items 3 through (mList's length) of mList)
|
250
|
+
|
251
|
+
tell application "System Events" to my menu_click_recurse(r, ((process appName)'s (menu bar 1)'s (menu bar item topMenu)'s (menu topMenu)))
|
252
|
+
end menu_click
|
253
|
+
|
254
|
+
on menu_click_recurse(mList, parentObject)
|
255
|
+
local f, r
|
256
|
+
|
257
|
+
set f to item 1 of mList
|
258
|
+
if mList's length > 1 then set r to (items 2 through (mList's length) of mList)
|
259
|
+
|
260
|
+
tell application "System Events"
|
261
|
+
if mList's length is 1 then
|
262
|
+
click parentObject's menu item f
|
263
|
+
else
|
264
|
+
my menu_click_recurse(r, (parentObject's (menu item f)'s (menu f)))
|
265
|
+
end if
|
266
|
+
end tell
|
267
|
+
end menu_click_recurse
|
268
|
+
|
269
|
+
menu_click({#{items_as_applescript_array}})
|
270
|
+
`
|
271
|
+
execute_applescript(ascript)
|
272
|
+
end
|
273
|
+
|
274
|
+
|
275
|
+
#--------------------------------------------------------------------------
|
276
|
+
# USEFUL UTILITIES
|
277
|
+
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
278
|
+
|
279
|
+
# Runs an applescript from a string.
|
280
|
+
# Returns the result.
|
281
|
+
#
|
282
|
+
def execute_applescript(scpt)
|
283
|
+
File.open(FILE_APPLESCRIPT, 'w') {|f| f.write(scpt)}
|
284
|
+
result = run("osascript #{FILE_APPLESCRIPT}")
|
285
|
+
File.unlink(FILE_APPLESCRIPT)
|
286
|
+
result
|
287
|
+
end
|
288
|
+
|
289
|
+
|
290
|
+
# Use MacOS's native text-to-speech functionality to emulate a human
|
291
|
+
# voice saying the narrative text.
|
292
|
+
#
|
293
|
+
def say(narrative)
|
294
|
+
run(%Q`say "#{escape_dq(narrative)}"`) unless ENV['SHHH']
|
295
|
+
end
|
296
|
+
|
297
|
+
|
298
|
+
protected
|
299
|
+
|
300
|
+
def automatically(cmd)
|
301
|
+
perms_test
|
302
|
+
run("\"#{osxautomation_path}\" \"#{cmd}\"")
|
303
|
+
end
|
304
|
+
|
305
|
+
|
306
|
+
private
|
307
|
+
|
308
|
+
def perms_test
|
309
|
+
return if File.executable?(osxautomation_path)
|
310
|
+
puts "IMPORTANT: Castanaut has recently been installed or updated. " +
|
311
|
+
"You need to give it the right to control mouse and keyboard " +
|
312
|
+
"input during screenplays."
|
313
|
+
|
314
|
+
run("sudo chmod a+x #{osxautomation_path}")
|
315
|
+
|
316
|
+
if File.executable?(osxautomation_path)
|
317
|
+
puts "Permission granted. Thanks."
|
318
|
+
else
|
319
|
+
raise Castanaut::Exceptions::OSXAutomationPermissionError
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
|
324
|
+
def osxautomation_path
|
325
|
+
File.join(PATH, "cbin", "osxautomation")
|
326
|
+
end
|
327
|
+
|
328
|
+
|
329
|
+
def mouse_button_translate(btn)
|
330
|
+
return btn if btn.is_a?(Integer)
|
331
|
+
{"left" => 1, "right" => 2, "middle" => 3}[btn]
|
332
|
+
end
|
333
|
+
|
334
|
+
end
|
335
|
+
|
336
|
+
end; end; end
|
@@ -0,0 +1,223 @@
|
|
1
|
+
module Castanaut; module OS; module MacOSX
|
2
|
+
|
3
|
+
# The TigerMovie class is intended to work on machines running
|
4
|
+
# Mac OS X 10.4.x. In order for it to work correctly, the Extras Suites
|
5
|
+
# application must be installed.
|
6
|
+
#
|
7
|
+
# Get it at <http://www.kanzu.com/main.html#extrasuites>
|
8
|
+
#
|
9
|
+
# KNOWN LIMITATIONS
|
10
|
+
#
|
11
|
+
# Not supported:
|
12
|
+
# * Movie#mousedown
|
13
|
+
# * Movie#mouseup
|
14
|
+
# * Movie#drag
|
15
|
+
#
|
16
|
+
# Partially supported:
|
17
|
+
# * click - only work with left-clicks
|
18
|
+
#
|
19
|
+
class TigerMovie < Castanaut::OS::MacOSX::Movie
|
20
|
+
|
21
|
+
register("Mac OS X 10.4")
|
22
|
+
|
23
|
+
|
24
|
+
# Returns true if the current platform is Mac OS X 10.5 or greater.
|
25
|
+
#
|
26
|
+
def self.platform_supported?
|
27
|
+
vers = `/usr/bin/sw_vers -productVersion`.match(/10\.(\d)\.\d+/)
|
28
|
+
vers[1].to_i == 4
|
29
|
+
rescue
|
30
|
+
false
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
#--------------------------------------------------------------------------
|
35
|
+
# KEYBOARD INPUT DIRECTIONS
|
36
|
+
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
37
|
+
|
38
|
+
def hit(key, *modifiers)
|
39
|
+
script = ''
|
40
|
+
if key == '"'
|
41
|
+
type(key)
|
42
|
+
return
|
43
|
+
elsif key.index('0x') == 0
|
44
|
+
script = hit_with_system_events(key, *modifiers)
|
45
|
+
else
|
46
|
+
script = hit_with_extra_suites(key, *modifiers)
|
47
|
+
end
|
48
|
+
execute_applescript(script)
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
def keystroke(*args)
|
53
|
+
not_supported("keystroke")
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
def type(str, opts = {})
|
58
|
+
type_via_applescript(str, opts)
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
#---------------------------------------------------------------------------
|
63
|
+
# MOUSE INPUT DIRECTIONS
|
64
|
+
#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
65
|
+
|
66
|
+
def cursor(*options)
|
67
|
+
options = combine_options(*options)
|
68
|
+
|
69
|
+
apply_offset(options)
|
70
|
+
@cursor_loc ||= {}
|
71
|
+
@cursor_loc[:x] = options[:to][:left]
|
72
|
+
@cursor_loc[:y] = options[:to][:top]
|
73
|
+
|
74
|
+
|
75
|
+
start_arr ||= execute_applescript(%Q`
|
76
|
+
tell application "Extra Suites"
|
77
|
+
ES mouse location
|
78
|
+
end tell
|
79
|
+
`).to_s.split(',').collect{|p| p.to_s.to_i}
|
80
|
+
start_loc = {:x=>start_arr[0], :y=>start_arr[1]}
|
81
|
+
dist = {
|
82
|
+
:x=>(start_loc[:x] - @cursor_loc[:x]),
|
83
|
+
:y=>(start_loc[:y] - @cursor_loc[:y])
|
84
|
+
}
|
85
|
+
steps = dist.values.collect{|p| p.to_s.to_i.abs}.max / 10.0
|
86
|
+
|
87
|
+
dist = {:x=>dist[:x] / BigDecimal.new(steps.to_s), :y=>dist[:y] / BigDecimal.new(steps.to_s)}
|
88
|
+
|
89
|
+
execute_applescript(%Q`
|
90
|
+
tell application "Extra Suites"
|
91
|
+
set x to #{start_loc[:x]}
|
92
|
+
set y to #{start_loc[:y]}
|
93
|
+
repeat while x #{dist[:x] > 0 ? '>' : '<'} #{@cursor_loc[:x]} or y #{dist[:y] > 0 ? '>' : '<'} #{@cursor_loc[:y]}
|
94
|
+
ES move mouse {x, y}
|
95
|
+
set x to x - #{dist[:x].round(2)}
|
96
|
+
set y to y - #{dist[:y].round(2)}
|
97
|
+
delay 1.0E-6
|
98
|
+
end repeat
|
99
|
+
ES move mouse {#{@cursor_loc[:x]}, #{@cursor_loc[:y]}}
|
100
|
+
end tell
|
101
|
+
`)
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
def cursor_location
|
106
|
+
loc = execute_applescript(%Q`
|
107
|
+
tell application "Extra Suites"
|
108
|
+
ES mouse location
|
109
|
+
end tell
|
110
|
+
`).split(/\D+/)
|
111
|
+
{:x => loc[0].to_i, :y => loc[1].to_i}
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
def click(btn = "left")
|
116
|
+
not_supported "anything other than left clicking" unless btn == 'left'
|
117
|
+
execute_applescript(%Q`
|
118
|
+
tell application "Extra Suites"
|
119
|
+
ES click mouse
|
120
|
+
end tell
|
121
|
+
`)
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
def doubleclick(btn = "left")
|
126
|
+
not_supported "anything other than left clicking" unless btn == 'left'
|
127
|
+
execute_applescript(%Q`
|
128
|
+
tell application "Extra Suites"
|
129
|
+
ES click mouse with double click
|
130
|
+
end tell
|
131
|
+
`)
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
def tripleclick(btn = "left")
|
136
|
+
not_supported "anything other than left clicking" unless btn == 'left'
|
137
|
+
execute_applescript(%Q`
|
138
|
+
tell application "Extra Suites"
|
139
|
+
ES click mouse
|
140
|
+
ES click mouse
|
141
|
+
ES click mouse
|
142
|
+
end tell
|
143
|
+
`)
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
def mousedown(*args)
|
148
|
+
not_supported("mousedown")
|
149
|
+
end
|
150
|
+
|
151
|
+
|
152
|
+
def mouseup(*args)
|
153
|
+
not_supported("mouseup")
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
def drag(*options)
|
158
|
+
not_supported("drag")
|
159
|
+
end
|
160
|
+
|
161
|
+
|
162
|
+
private
|
163
|
+
|
164
|
+
def hit_with_extra_suites(key, *modifiers)
|
165
|
+
str = %Q{"#{ key }"}
|
166
|
+
if !modifiers.empty?
|
167
|
+
modifiers = modifiers.collect do |mod|
|
168
|
+
case mod
|
169
|
+
when Castanaut::Command
|
170
|
+
"command"
|
171
|
+
when Castanaut::Ctrl
|
172
|
+
"control"
|
173
|
+
when Castanaut::Alt
|
174
|
+
"option"
|
175
|
+
when Castanaut::Shift
|
176
|
+
"shift"
|
177
|
+
else
|
178
|
+
nil
|
179
|
+
end
|
180
|
+
end.select{ |mod| !mod.nil? }
|
181
|
+
|
182
|
+
str += modifiers.empty? ? "" : " with #{ modifiers.join(' and ') }"
|
183
|
+
end
|
184
|
+
|
185
|
+
%Q`
|
186
|
+
tell application "Extra Suites"
|
187
|
+
ES type key #{ str }
|
188
|
+
end tell
|
189
|
+
`
|
190
|
+
end
|
191
|
+
|
192
|
+
|
193
|
+
def hit_with_system_events(key, *modifiers)
|
194
|
+
str = key.hex.to_s
|
195
|
+
if !modifiers.empty?
|
196
|
+
modifiers = modifiers.collect do |mod|
|
197
|
+
case mod
|
198
|
+
when Castanaut::Command
|
199
|
+
"command down"
|
200
|
+
when Castanaut::Ctrl
|
201
|
+
"control down"
|
202
|
+
when Castanaut::Alt
|
203
|
+
"option down"
|
204
|
+
when Castanaut::Shift
|
205
|
+
"shift down"
|
206
|
+
else
|
207
|
+
nil
|
208
|
+
end
|
209
|
+
end.select{ |mod| !mod.nil? }
|
210
|
+
str += modifiers.empty? ? "" : " using {#{ modifiers.join(', ') }}"
|
211
|
+
end
|
212
|
+
|
213
|
+
%Q`
|
214
|
+
tell application "System Events"
|
215
|
+
key code #{ str }
|
216
|
+
end tell
|
217
|
+
`
|
218
|
+
end
|
219
|
+
|
220
|
+
|
221
|
+
end
|
222
|
+
|
223
|
+
end; end; end
|
data/lib/castanaut/plugin.rb
CHANGED
@@ -16,8 +16,8 @@ module Castanaut
|
|
16
16
|
# end
|
17
17
|
# end
|
18
18
|
# end
|
19
|
-
#
|
20
|
-
# The script must exist in a sub-directory of the screenplay's location
|
19
|
+
#
|
20
|
+
# The script must exist in a sub-directory of the screenplay's location
|
21
21
|
# called "plugins", and must be called (in this case): foo.rb.
|
22
22
|
#
|
23
23
|
module Plugin
|
data/lib/castanaut.rb
CHANGED
@@ -1,64 +1,60 @@
|
|
1
|
-
# $Id$
|
2
|
-
|
3
1
|
# Equivalent to a header guard in C/C++
|
4
2
|
# Used to prevent the class/module from being loaded more than once
|
5
3
|
unless defined? Castanaut
|
6
4
|
|
7
|
-
# The Castanaut module. For orienting yourself within the code, it's
|
8
|
-
# recommended you begin with the documentation for the Movie class,
|
9
|
-
# which is the big one.
|
10
|
-
#
|
11
|
-
# Execution typically begins with the Main class.
|
12
|
-
module Castanaut
|
13
|
-
|
14
|
-
# :stopdoc:
|
15
|
-
VERSION = '1.0.0'
|
16
|
-
LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
|
17
|
-
PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
|
18
|
-
|
19
|
-
FILE_RUNNING = "/tmp/castanaut.running"
|
20
|
-
FILE_APPLESCRIPT = "/tmp/castanaut.scpt"
|
21
|
-
# :startdoc:
|
22
|
-
|
23
|
-
# Returns the version string for the library.
|
24
|
-
#
|
25
|
-
def self.version
|
26
|
-
VERSION
|
27
|
-
end
|
28
|
-
|
29
|
-
# Returns the library path for the module. If any arguments are given,
|
30
|
-
# they will be joined to the end of the libray path using
|
31
|
-
# <tt>File.join</tt>.
|
32
|
-
#
|
33
|
-
def self.libpath( *args )
|
34
|
-
args.empty? ? LIBPATH : ::File.join(LIBPATH, *args)
|
35
|
-
end
|
36
|
-
|
37
|
-
# Returns the lpath for the module. If any arguments are given,
|
38
|
-
# they will be joined to the end of the path using
|
39
|
-
# <tt>File.join</tt>.
|
40
|
-
#
|
41
|
-
def self.path( *args )
|
42
|
-
args.empty? ? PATH : ::File.join(PATH, *args)
|
43
|
-
end
|
44
|
-
|
45
|
-
# Utility method used to rquire all files ending in .rb that lie in the
|
46
|
-
# directory below this file that has the same name as the filename passed
|
47
|
-
# in. Optionally, a specific _directory_ name can be passed in such that
|
48
|
-
# the _filename_ does not have to be equivalent to the directory.
|
5
|
+
# The Castanaut module. For orienting yourself within the code, it's
|
6
|
+
# recommended you begin with the documentation for the Movie class,
|
7
|
+
# which is the big one.
|
49
8
|
#
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
9
|
+
# Execution typically begins with the Main class.
|
10
|
+
module Castanaut
|
11
|
+
|
12
|
+
# :stopdoc:
|
13
|
+
VERSION = '1.1.0'
|
14
|
+
LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
|
15
|
+
PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
|
16
|
+
|
17
|
+
FILE_RUNNING = "/tmp/castanaut.running"
|
18
|
+
FILE_APPLESCRIPT = "/tmp/castanaut.scpt"
|
19
|
+
# :startdoc:
|
20
|
+
|
21
|
+
# Returns the version string for the library.
|
22
|
+
#
|
23
|
+
def self.version
|
24
|
+
VERSION
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns the library path for the module. If any arguments are given,
|
28
|
+
# they will be joined to the end of the libray path using
|
29
|
+
# <tt>File.join</tt>.
|
30
|
+
#
|
31
|
+
def self.libpath( *args )
|
32
|
+
args.empty? ? LIBPATH : ::File.join(LIBPATH, *args)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns the lpath for the module. If any arguments are given,
|
36
|
+
# they will be joined to the end of the path using
|
37
|
+
# <tt>File.join</tt>.
|
38
|
+
#
|
39
|
+
def self.path( *args )
|
40
|
+
args.empty? ? PATH : ::File.join(PATH, *args)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Utility method used to rquire all files ending in .rb that lie in the
|
44
|
+
# directory below this file that has the same name as the filename passed
|
45
|
+
# in. Optionally, a specific _directory_ name can be passed in such that
|
46
|
+
# the _filename_ does not have to be equivalent to the directory.
|
47
|
+
#
|
48
|
+
def self.require_all_libs_relative_to( fname, dir = nil )
|
49
|
+
dir ||= ::File.basename(fname, '.*')
|
50
|
+
search_me = ::File.expand_path(
|
51
|
+
::File.join(::File.dirname(fname), dir, '**', '*.rb'))
|
52
|
+
|
53
|
+
Dir.glob(search_me).sort.each {|rb| require rb}
|
54
|
+
end
|
55
|
+
|
56
|
+
end # module Castanaut
|
57
|
+
|
58
|
+
Castanaut.require_all_libs_relative_to __FILE__
|
61
59
|
|
62
60
|
end # unless defined?
|
63
|
-
|
64
|
-
# EOF
|