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
data/lib/castanaut/movie.rb
CHANGED
@@ -1,24 +1,67 @@
|
|
1
1
|
module Castanaut
|
2
|
-
|
3
|
-
#
|
2
|
+
|
3
|
+
# The movie class is the containing context within which screenplays are
|
4
|
+
# invoked. It provides a number of basic stage directions for your
|
4
5
|
# screenplays, and can be extended with plugins.
|
6
|
+
#
|
7
|
+
# If you're working to make Castanaut compatible with your operating system,
|
8
|
+
# you must make sure that *all* methods in this class work correctly.
|
5
9
|
class Movie
|
6
10
|
|
7
|
-
|
11
|
+
def self.register(name)
|
12
|
+
unless reg = Castanaut::Movie.instance_variable_get(:@movie_classes)
|
13
|
+
reg = Castanaut::Movie.instance_variable_set(:@movie_classes, {})
|
14
|
+
end
|
15
|
+
self.instance_variable_set(:@name, name)
|
16
|
+
reg.update(self => name)
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
def self.spawn(screenplay = nil, monitor = true)
|
21
|
+
reg = Castanaut::Movie.instance_variable_get(:@movie_classes)
|
22
|
+
klass = reg.keys.detect { |k| k.platform_supported? }
|
23
|
+
klass.new(screenplay, monitor)
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
# Creates the movie. If a screenplay is provided here, it will be run.
|
28
|
+
# If monitor is true, we'll monitor the kill file (FILE_RUNNING) -
|
29
|
+
# if it is deleted, we abort.
|
8
30
|
#
|
9
|
-
def initialize(screenplay)
|
10
|
-
|
31
|
+
def initialize(screenplay = nil, monitor = true)
|
32
|
+
if self.class == Castanaut::Movie
|
33
|
+
raise "#{self} is an abstract class. Try the spawn method."
|
34
|
+
end
|
11
35
|
|
12
|
-
if
|
13
|
-
|
36
|
+
if screenplay
|
37
|
+
monitor ? _play_and_monitor(screenplay) : _play(screenplay)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
# Simply plays the screenplay in the current thread.
|
43
|
+
def _play(screenplay)
|
44
|
+
unless File.exists?(@screenplay_path = screenplay)
|
45
|
+
raise Castanaut::Exceptions::ScreenplayNotFound
|
46
|
+
end
|
47
|
+
eval(IO.read(@screenplay_path), binding)
|
48
|
+
roll_credits
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
# Plays the screenplay in a separate thread, and monitors the killfile
|
53
|
+
# (which is at FILE_RUNNING) - if it is deleted, the screenplay will
|
54
|
+
# abort.
|
55
|
+
def _play_and_monitor(screenplay)
|
56
|
+
unless File.exists?(@screenplay_path = screenplay)
|
57
|
+
raise Castanaut::Exceptions::ScreenplayNotFound
|
14
58
|
end
|
15
|
-
@screenplay_path = screenplay
|
16
59
|
|
17
60
|
File.open(FILE_RUNNING, 'w') {|f| f.write('')}
|
18
61
|
|
19
62
|
begin
|
20
|
-
# We run the movie in a separate thread; in the main thread we
|
21
|
-
# continue to check the "running" file flag and kill the movie if
|
63
|
+
# We run the movie in a separate thread; in the main thread we
|
64
|
+
# continue to check the "running" file flag and kill the movie if
|
22
65
|
# it is removed.
|
23
66
|
movie = Thread.new do
|
24
67
|
begin
|
@@ -49,123 +92,55 @@ module Castanaut
|
|
49
92
|
end
|
50
93
|
end
|
51
94
|
|
52
|
-
# Launch the application matching the string given in the first argument.
|
53
|
-
# (This resolution is handled by Applescript.)
|
54
|
-
#
|
55
|
-
# If the options hash is given, it should contain the co-ordinates for
|
56
|
-
# the window (top, left, width, height). The to method will format these
|
57
|
-
# co-ordinates appropriately.
|
58
|
-
#
|
59
|
-
def launch(app_name, *options)
|
60
|
-
options = combine_options(*options)
|
61
|
-
|
62
|
-
ensure_window = ""
|
63
|
-
case app_name.downcase
|
64
|
-
when "safari"
|
65
|
-
ensure_window = "if (count(windows)) < 1 then make new document"
|
66
|
-
end
|
67
95
|
|
68
|
-
positioning = ""
|
69
|
-
if options[:to]
|
70
|
-
pos = "#{options[:to][:left]}, #{options[:to][:top]}"
|
71
|
-
dims = "#{options[:to][:left] + options[:to][:width]}, " +
|
72
|
-
"#{options[:to][:top] + options[:to][:height]}"
|
73
|
-
if options[:to][:width]
|
74
|
-
positioning = "set bounds of front window to {#{pos}, #{dims}}"
|
75
|
-
else
|
76
|
-
positioning = "set position of front window to {#{pos}}"
|
77
|
-
end
|
78
|
-
end
|
79
96
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
`)
|
87
|
-
end
|
97
|
+
#--------------------------------------------------------------------------
|
98
|
+
# IMPLEMENTED DIRECTIONS
|
99
|
+
#
|
100
|
+
# You can override these in subclasses, but you'd probably want to have
|
101
|
+
# a very good reason.
|
102
|
+
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
88
103
|
|
89
|
-
#
|
104
|
+
# Don't do anything for the specified number of seconds (can be portions
|
105
|
+
# of a second).
|
90
106
|
#
|
91
|
-
def
|
92
|
-
|
93
|
-
apply_offset(options)
|
94
|
-
@cursor_loc ||= {}
|
95
|
-
@cursor_loc[:x] = options[:to][:left]
|
96
|
-
@cursor_loc[:y] = options[:to][:top]
|
97
|
-
automatically "mousemove #{@cursor_loc[:x]} #{@cursor_loc[:y]}"
|
107
|
+
def pause(seconds)
|
108
|
+
sleep seconds
|
98
109
|
end
|
99
110
|
|
100
|
-
alias :move :cursor
|
101
111
|
|
102
|
-
#
|
112
|
+
# Groups directions into labelled blocks. This lets you skip (see below)
|
113
|
+
# to the end of the block if you need to.
|
103
114
|
#
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
#
|
115
|
+
# perform "Build CouchDB from source" do
|
116
|
+
# launch "Terminal"
|
117
|
+
# type "./configure"
|
118
|
+
# hit Enter
|
119
|
+
# ...
|
120
|
+
# end
|
109
121
|
#
|
110
|
-
def
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
# Send a triple-click at the current mouse location.
|
115
|
-
#
|
116
|
-
def tripleclick(btn = 'left')
|
117
|
-
automatically "mousetripleclick #{mouse_button_translate(btn)}"
|
122
|
+
def perform(label)
|
123
|
+
yield
|
124
|
+
rescue Castanaut::Exceptions::SkipError => e
|
125
|
+
puts "Skipping remaining directions in '#{label}'"
|
118
126
|
end
|
119
127
|
|
120
|
-
# Press the button down at the current mouse location. Does not
|
121
|
-
# release the button until the mouseup method is invoked.
|
122
|
-
#
|
123
|
-
def mousedown(btn = 'left')
|
124
|
-
automatically "mousedown #{mouse_button_translate(btn)}"
|
125
|
-
end
|
126
128
|
|
127
|
-
#
|
129
|
+
# Lets you skip out of a perform block if you need to. Usually raised
|
130
|
+
# when some condition fails. For example:
|
128
131
|
#
|
129
|
-
|
130
|
-
automatically "mouseup #{mouse_button_translate(btn)}"
|
131
|
-
end
|
132
|
-
|
133
|
-
# "Drags" the mouse by (effectively) issuing a mousedown at the current
|
134
|
-
# mouse location, then moving the mouse to the specified coordinates, then
|
135
|
-
# issuing a mouseup.
|
132
|
+
# perform "Point to heading" do
|
136
133
|
#
|
137
|
-
|
138
|
-
|
139
|
-
apply_offset(options)
|
140
|
-
automatically "mousedrag #{options[:to][:left]} #{options[:to][:top]}"
|
141
|
-
end
|
142
|
-
|
143
|
-
# Sends the characters into the active control in the active window.
|
134
|
+
# move to_element('h2') rescue skip
|
135
|
+
# say "This is the heading."
|
144
136
|
#
|
145
|
-
|
146
|
-
automatically "type #{str}"
|
147
|
-
end
|
148
|
-
|
149
|
-
# Sends the keycode (a hex value) to the active control in the active
|
150
|
-
# window. For more about keycode values, see Mac Developer documentation.
|
137
|
+
# end
|
151
138
|
#
|
152
|
-
def
|
153
|
-
|
139
|
+
def skip
|
140
|
+
raise Castanaut::Exceptions::SkipError
|
154
141
|
end
|
155
142
|
|
156
|
-
# Don't do anything for the specified number of seconds (can be portions
|
157
|
-
# of a second).
|
158
|
-
#
|
159
|
-
def pause(seconds)
|
160
|
-
sleep seconds
|
161
|
-
end
|
162
143
|
|
163
|
-
# Use Leopard's native text-to-speech functionality to emulate a human
|
164
|
-
# voice saying the narrative text.
|
165
|
-
#
|
166
|
-
def say(narrative)
|
167
|
-
run(%Q`say "#{escape_dq(narrative)}"`)
|
168
|
-
end
|
169
144
|
|
170
145
|
# Starts saying the narrative text, and simultaneously begins executing
|
171
146
|
# the given block. Waits until both are finished.
|
@@ -180,6 +155,7 @@ module Castanaut
|
|
180
155
|
end
|
181
156
|
end
|
182
157
|
|
158
|
+
|
183
159
|
# Get a hash representing specific screen co-ordinates. Use in combination
|
184
160
|
# with cursor, drag, launch, and similar methods.
|
185
161
|
#
|
@@ -197,18 +173,17 @@ module Castanaut
|
|
197
173
|
|
198
174
|
alias :at :to
|
199
175
|
|
176
|
+
|
200
177
|
# Get a hash representing specific screen co-ordinates *relative to the
|
201
178
|
# current mouse location.
|
202
179
|
#
|
203
180
|
def by(x, y)
|
204
|
-
|
205
|
-
@cursor_loc = automatically("mouselocation").strip.split(' ')
|
206
|
-
@cursor_loc = {:x => @cursor_loc[0].to_i, :y => @cursor_loc[1].to_i}
|
207
|
-
end
|
181
|
+
@cursor_loc ||= cursor_location
|
208
182
|
to(@cursor_loc[:x] + x, @cursor_loc[:y] + y)
|
209
183
|
end
|
210
184
|
|
211
|
-
|
185
|
+
|
186
|
+
# The result of this method can be added +to+ a co-ordinates hash,
|
212
187
|
# offsetting the top and left values by the given margins.
|
213
188
|
#
|
214
189
|
def offset(x, y)
|
@@ -216,41 +191,15 @@ module Castanaut
|
|
216
191
|
end
|
217
192
|
|
218
193
|
|
219
|
-
#
|
220
|
-
# for multi-monitor set-ups.)
|
221
|
-
#
|
222
|
-
def screen_size
|
223
|
-
coords = execute_applescript(%Q`
|
224
|
-
tell application "Finder"
|
225
|
-
get bounds of window of desktop
|
226
|
-
end tell
|
227
|
-
`)
|
228
|
-
coords = coords.split(", ").collect {|c| c.to_i}
|
229
|
-
to(*coords)
|
230
|
-
end
|
231
|
-
|
232
|
-
# Runs a shell command, performing fairly naive (but effective!) exit
|
194
|
+
# Runs a shell command, performing fairly naive (but effective!) exit
|
233
195
|
# status handling. Returns the stdout result of the command.
|
234
196
|
#
|
235
197
|
def run(cmd)
|
236
|
-
#puts("Executing: #{cmd}")
|
237
198
|
result = `#{cmd}`
|
238
199
|
raise Castanaut::Exceptions::ExternalActionError if $?.exitstatus > 0
|
239
200
|
result
|
240
201
|
end
|
241
|
-
|
242
|
-
# Adds custom methods to this movie instance, allowing you to perform
|
243
|
-
# additional actions. See the README.txt for more information.
|
244
|
-
#
|
245
|
-
def plugin(str)
|
246
|
-
str.downcase!
|
247
|
-
begin
|
248
|
-
require File.join(File.dirname(@screenplay_path),"plugins","#{str}.rb")
|
249
|
-
rescue LoadError
|
250
|
-
require File.join(LIBPATH, "plugins", "#{str}.rb")
|
251
|
-
end
|
252
|
-
extend eval("Castanaut::Plugin::#{str.capitalize}")
|
253
|
-
end
|
202
|
+
|
254
203
|
|
255
204
|
# Loads a script from a file into a string, looking first in the
|
256
205
|
# scripts directory beneath the path where Castanaut was executed,
|
@@ -259,8 +208,7 @@ module Castanaut
|
|
259
208
|
def script(filename)
|
260
209
|
@cached_scripts ||= {}
|
261
210
|
unless @cached_scripts[filename]
|
262
|
-
fpath =
|
263
|
-
scpt = nil
|
211
|
+
fpath = contextual_path("scripts", filename)
|
264
212
|
if File.exists?(fpath)
|
265
213
|
scpt = IO.read(fpath)
|
266
214
|
else
|
@@ -272,6 +220,34 @@ module Castanaut
|
|
272
220
|
@cached_scripts[filename]
|
273
221
|
end
|
274
222
|
|
223
|
+
|
224
|
+
# Adds custom methods to this movie instance, allowing you to perform
|
225
|
+
# additional actions. The str can be either the file name
|
226
|
+
# (e.g. 'snapz_pro') or the class name (e.g. 'SnapzPro').
|
227
|
+
# See the README.txt for more information.
|
228
|
+
#
|
229
|
+
# FIXME: sort out this underscore/camelize mess.
|
230
|
+
#
|
231
|
+
def plugin(str)
|
232
|
+
# copied stright from the Rails underscore helper
|
233
|
+
str = str.to_s
|
234
|
+
str.gsub!(/::/, '/')
|
235
|
+
str.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
|
236
|
+
str.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
237
|
+
str.tr!("-", "_")
|
238
|
+
str.downcase!
|
239
|
+
fpath =
|
240
|
+
begin
|
241
|
+
require contextual_path("plugins", "#{str}.rb")
|
242
|
+
rescue LoadError
|
243
|
+
require File.join(LIBPATH, "plugins", "#{str}.rb")
|
244
|
+
end
|
245
|
+
# copied stright from the Rails camelize helper
|
246
|
+
str = str.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
|
247
|
+
extend eval("Castanaut::Plugin::#{str}")
|
248
|
+
end
|
249
|
+
|
250
|
+
|
275
251
|
# This stage direction is slightly different to the other ones. It collects
|
276
252
|
# a set of directions to be executed when the movie ends, or when it is
|
277
253
|
# aborted by the user. Mostly, it's used for cleaning up stuff. Here's
|
@@ -293,45 +269,202 @@ module Castanaut
|
|
293
269
|
@end_credits << blk
|
294
270
|
end
|
295
271
|
|
272
|
+
|
273
|
+
#--------------------------------------------------------------------------
|
274
|
+
# KEYBOARD INPUT DIRECTIONS
|
275
|
+
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
276
|
+
|
277
|
+
# Sends the characters into the active control in the active window.
|
278
|
+
#
|
279
|
+
# Options are:
|
280
|
+
#
|
281
|
+
# * <tt>:speed</tt> - approximate umber of characters per second
|
282
|
+
# A speed of 0 types as quickly as possible. (default - 50)
|
283
|
+
#
|
284
|
+
def type(str, opts = {})
|
285
|
+
not_supported('type')
|
286
|
+
end
|
287
|
+
|
288
|
+
|
289
|
+
# Hit a single key on the keyboard (with optional modifiers).
|
290
|
+
#
|
291
|
+
# Valid keys include any single character or any of the constants in keys.rb
|
292
|
+
#
|
293
|
+
# Valid modifiers include one or more of the following:
|
294
|
+
# Command
|
295
|
+
# Ctrl
|
296
|
+
# Alt
|
297
|
+
# Shift
|
298
|
+
#
|
299
|
+
# Examples:
|
300
|
+
# hit Castanaut::Tab
|
301
|
+
# hit 'a', Castanaut::Command
|
302
|
+
#
|
303
|
+
def hit(key, *modifiers)
|
304
|
+
not_supported('hit')
|
305
|
+
end
|
306
|
+
|
307
|
+
|
308
|
+
#---------------------------------------------------------------------------
|
309
|
+
# MOUSE INPUT DIRECTIONS
|
310
|
+
#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
311
|
+
|
312
|
+
# Move the mouse cursor to the specified co-ordinates.
|
313
|
+
# Example:
|
314
|
+
#
|
315
|
+
# cursor to(20, 20)
|
316
|
+
#
|
317
|
+
def cursor(*options)
|
318
|
+
not_supported('cursor')
|
319
|
+
end
|
320
|
+
|
321
|
+
alias :move :cursor
|
322
|
+
|
323
|
+
|
324
|
+
# Get a hash representing the current mouse cursor co-ordinates.
|
325
|
+
#
|
326
|
+
# Should return a hash with :x & :y keys.
|
327
|
+
#
|
328
|
+
def cursor_location
|
329
|
+
not_supported('cursor_location')
|
330
|
+
end
|
331
|
+
|
332
|
+
|
333
|
+
# Send a mouse-click at the current mouse location.
|
334
|
+
#
|
335
|
+
def click(btn = 'left')
|
336
|
+
not_supported('click')
|
337
|
+
end
|
338
|
+
|
339
|
+
|
340
|
+
# Send a double-click at the current mouse location.
|
341
|
+
#
|
342
|
+
def doubleclick(btn = 'left')
|
343
|
+
not_supported('doubleclick')
|
344
|
+
end
|
345
|
+
|
346
|
+
|
347
|
+
# Send a triple-click at the current mouse location.
|
348
|
+
#
|
349
|
+
def tripleclick(btn = 'left')
|
350
|
+
not_supported('tripleclick')
|
351
|
+
end
|
352
|
+
|
353
|
+
|
354
|
+
# Press the button down at the current mouse location. Does not
|
355
|
+
# release the button until the mouseup method is invoked.
|
356
|
+
#
|
357
|
+
def mousedown(btn = 'left')
|
358
|
+
not_supported('mousedown')
|
359
|
+
end
|
360
|
+
|
361
|
+
|
362
|
+
# Releases the mouse button pressed by a previous mousedown.
|
363
|
+
#
|
364
|
+
def mouseup(btn = 'left')
|
365
|
+
not_supported('mouseup')
|
366
|
+
end
|
367
|
+
|
368
|
+
|
369
|
+
# "Drags" the mouse by (effectively) issuing a mousedown at the current
|
370
|
+
# mouse location, then moving the mouse to the specified coordinates, then
|
371
|
+
# issuing a mouseup.
|
372
|
+
#
|
373
|
+
def drag(*options)
|
374
|
+
not_supported('drag')
|
375
|
+
end
|
376
|
+
|
377
|
+
|
378
|
+
#--------------------------------------------------------------------------
|
379
|
+
# WINDOWS AND APPLICATIONS DIRECTIONS
|
380
|
+
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
381
|
+
|
382
|
+
# Launch the application matching the string given in the first argument.
|
383
|
+
# If the options hash is given, it should contain the co-ordinates for
|
384
|
+
# the window.
|
385
|
+
#
|
386
|
+
# Example:
|
387
|
+
#
|
388
|
+
# launch "Firefox", at(10, 10, 800, 600)
|
389
|
+
#
|
390
|
+
def launch(app_name, *options)
|
391
|
+
not_supported('launch')
|
392
|
+
end
|
393
|
+
|
394
|
+
alias :activate :launch
|
395
|
+
|
396
|
+
|
397
|
+
# Returns a region hash describing the entire screen area.
|
398
|
+
#
|
399
|
+
# Should return a hash with :width & :height keys.
|
400
|
+
#
|
401
|
+
def screen_size
|
402
|
+
not_supported('screen_size')
|
403
|
+
end
|
404
|
+
|
405
|
+
|
406
|
+
#--------------------------------------------------------------------------
|
407
|
+
# USEFUL UTILITIES
|
408
|
+
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
409
|
+
|
410
|
+
# Use text-to-speech functionality to emulate a human
|
411
|
+
# voice saying the narrative text.
|
412
|
+
#
|
413
|
+
def say(narrative)
|
414
|
+
not_supported('say')
|
415
|
+
end
|
416
|
+
|
417
|
+
|
296
418
|
protected
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
419
|
+
|
420
|
+
# Find a file relative to the movie execution context -- that is,
|
421
|
+
# either the location of the screenplay file, or the present working
|
422
|
+
# directory if there is no screenplay file.
|
423
|
+
#
|
424
|
+
def contextual_path(*args)
|
425
|
+
if @screenplay_path
|
426
|
+
File.join(*([File.dirname(@screenplay_path)] + args))
|
427
|
+
else
|
428
|
+
File.join(*args)
|
429
|
+
end
|
302
430
|
end
|
303
431
|
|
304
|
-
|
305
|
-
|
432
|
+
|
433
|
+
# A method used by the compatibility layer to raise a NotSupportedError
|
434
|
+
# explaining which requested options are not supported by the current
|
435
|
+
# operating system.
|
436
|
+
#
|
437
|
+
# Example:
|
438
|
+
# # On a Mac OS 10.5 (Leopard) machine
|
439
|
+
# hit 'a', Castanaut::Command
|
440
|
+
# => "Mac OS 10.5 (Leopard) does not support modifier keys for
|
441
|
+
# the 'hit' method."
|
442
|
+
#
|
443
|
+
def not_supported(message)
|
444
|
+
message.gsub!(/\.$/, '')
|
445
|
+
raise Castanaut::Exceptions::NotSupportedError.new(
|
446
|
+
"#{self.class.to_s} does not support #{message}."
|
447
|
+
)
|
306
448
|
end
|
307
449
|
|
450
|
+
|
451
|
+
# Escapes double quotes.
|
452
|
+
#
|
308
453
|
def escape_dq(str)
|
309
454
|
str.gsub(/\\/,'\\\\\\').gsub(/"/, '\"')
|
310
455
|
end
|
311
456
|
|
312
|
-
def combine_options(*args)
|
313
|
-
options = args.inject({}) { |result, option| result.update(option) }
|
314
|
-
end
|
315
457
|
|
316
|
-
|
317
|
-
|
318
|
-
|
458
|
+
# Combines a list of hashes into one hash.
|
459
|
+
# Example:
|
460
|
+
#
|
461
|
+
# combine_options({:x=>10}, {:y=>20})
|
462
|
+
# # => {:y=>20, :x=>10}
|
463
|
+
#
|
464
|
+
def combine_options(*args)
|
465
|
+
args.inject({}) { |result, option| result.update(option) }
|
319
466
|
end
|
320
467
|
|
321
|
-
def perms_test
|
322
|
-
return if File.executable?(osxautomation_path)
|
323
|
-
puts "IMPORTANT: Castanaut has recently been installed or updated. " +
|
324
|
-
"You need to give it the right to control mouse and keyboard " +
|
325
|
-
"input during screenplays."
|
326
|
-
|
327
|
-
run("sudo chmod a+x #{osxautomation_path}")
|
328
|
-
|
329
|
-
if File.executable?(osxautomation_path)
|
330
|
-
puts "Permission granted. Thanks."
|
331
|
-
else
|
332
|
-
raise Castanaut::Exceptions::OSXAutomationPermissionError
|
333
|
-
end
|
334
|
-
end
|
335
468
|
|
336
469
|
def apply_offset(options)
|
337
470
|
return unless options[:to] && options[:offset]
|
@@ -339,15 +472,12 @@ module Castanaut
|
|
339
472
|
options[:to][:top] += options[:offset][:y] || 0
|
340
473
|
end
|
341
474
|
|
342
|
-
def mouse_button_translate(btn)
|
343
|
-
return btn if btn.is_a?(Integer)
|
344
|
-
{"left" => 1, "right" => 2, "middle" => 3}[btn]
|
345
|
-
end
|
346
475
|
|
347
476
|
def roll_credits
|
348
477
|
return unless @end_credits && @end_credits.any?
|
349
478
|
@end_credits.each {|credit| credit.call}
|
350
479
|
end
|
351
|
-
|
480
|
+
|
352
481
|
end
|
482
|
+
|
353
483
|
end
|