castanaut 1.0.0 → 1.1.0
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/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
|