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
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
|