autogui 1.0.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/lib/autogui.rb ADDED
@@ -0,0 +1,738 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rbconfig"
4
+ require "time"
5
+
6
+ require_relative "autogui/version"
7
+ require_relative "autogui/exceptions"
8
+ require_relative "autogui/geometry"
9
+ require_relative "autogui/keys"
10
+ require_relative "autogui/tween"
11
+ require_relative "autogui/image"
12
+ require_relative "autogui/screenshot"
13
+ require_relative "autogui/platform"
14
+ require_relative "autogui/message_box"
15
+ require_relative "autogui/window"
16
+ require_relative "autogui/run"
17
+
18
+ # AutoGUI — desktop GUI automation for Ruby (mouse, keyboard, screenshots).
19
+ #
20
+ # require "autogui"
21
+ # AutoGUI.move_to(100, 150)
22
+ # AutoGUI.click
23
+ # AutoGUI.write("Hello world!")
24
+ #
25
+ # Install: <tt>gem install autogui</tt>
26
+ # Docs: README.md, docs/api.md, docs/guide.md
27
+ # Fail-safe: moving the cursor to a screen corner raises {FailSafeException}.
28
+ module AutoGUI
29
+ class << self
30
+ attr_accessor :pause, :failsafe, :minimum_duration, :minimum_sleep,
31
+ :darwin_catch_up_time, :log_screenshots, :log_screenshots_limit,
32
+ :use_image_not_found_exception
33
+ attr_reader :failsafe_points
34
+ end
35
+
36
+ @pause = 0.1
37
+ @failsafe = true
38
+ @minimum_duration = 0.1
39
+ @minimum_sleep = 0.05
40
+ @darwin_catch_up_time = 0.01
41
+ @log_screenshots = false
42
+ @log_screenshots_limit = 10
43
+ @log_screenshot_filenames = []
44
+ @use_image_not_found_exception = false
45
+ @failsafe_points = [Point.new(0, 0)]
46
+
47
+ # Python-style constant aliases
48
+ def self.PAUSE
49
+ @pause
50
+ end
51
+
52
+ def self.PAUSE=(value)
53
+ @pause = value
54
+ end
55
+
56
+ def self.FAILSAFE
57
+ @failsafe
58
+ end
59
+
60
+ def self.FAILSAFE=(value)
61
+ @failsafe = value
62
+ end
63
+
64
+ def self.FAILSAFE_POINTS
65
+ @failsafe_points
66
+ end
67
+
68
+ def self.MINIMUM_DURATION
69
+ @minimum_duration
70
+ end
71
+
72
+ def self.MINIMUM_DURATION=(value)
73
+ @minimum_duration = value
74
+ end
75
+
76
+ def self.MINIMUM_SLEEP
77
+ @minimum_sleep
78
+ end
79
+
80
+ def self.MINIMUM_SLEEP=(value)
81
+ @minimum_sleep = value
82
+ end
83
+
84
+ def self.DARWIN_CATCH_UP_TIME
85
+ @darwin_catch_up_time
86
+ end
87
+
88
+ def self.DARWIN_CATCH_UP_TIME=(value)
89
+ @darwin_catch_up_time = value
90
+ end
91
+
92
+ def self.LOG_SCREENSHOTS
93
+ @log_screenshots
94
+ end
95
+
96
+ def self.LOG_SCREENSHOTS=(value)
97
+ @log_screenshots = value
98
+ end
99
+
100
+ module_function
101
+
102
+ def platform_module
103
+ Platform.current
104
+ end
105
+
106
+ def getPointOnLine(x1, y1, x2, y2, n)
107
+ [((x2 - x1) * n) + x1, ((y2 - y1) * n) + y1]
108
+ end
109
+ alias get_point_on_line getPointOnLine
110
+
111
+ def linear(n)
112
+ Tween.linear(n)
113
+ end
114
+
115
+ Tween.singleton_methods(false).each do |name|
116
+ next if method_defined?(name) || name == :check
117
+
118
+ define_singleton_method(name) { |*args| Tween.public_send(name, *args) }
119
+ end
120
+
121
+ def failSafeCheck
122
+ return unless @failsafe
123
+
124
+ pos = position
125
+ return unless @failsafe_points.any? { |p| p.x == pos.x && p.y == pos.y }
126
+
127
+ raise FailSafeException,
128
+ "AutoGUI fail-safe triggered from mouse moving to a corner of the screen. " \
129
+ "To disable this fail-safe, set AutoGUI.FAILSAFE to false. DISABLING FAIL-SAFE IS NOT RECOMMENDED."
130
+ end
131
+ alias fail_safe_check failSafeCheck
132
+
133
+ def sleep(seconds)
134
+ Kernel.sleep(seconds.to_f)
135
+ end
136
+
137
+ def countdown(seconds)
138
+ seconds.to_i.downto(1) do |i|
139
+ print "#{i} "
140
+ $stdout.flush
141
+ Kernel.sleep(1)
142
+ end
143
+ puts
144
+ end
145
+
146
+ def useImageNotFoundException(value = true)
147
+ @use_image_not_found_exception = value
148
+ end
149
+ alias use_image_not_found_exception! useImageNotFoundException
150
+
151
+ def position(x = nil, y = nil)
152
+ posx, posy = platform_module.position
153
+ posx = x.to_i unless x.nil?
154
+ posy = y.to_i unless y.nil?
155
+ Point.new(posx, posy)
156
+ end
157
+
158
+ def size
159
+ Size.new(*platform_module.size)
160
+ end
161
+ alias resolution size
162
+
163
+ def onScreen(x, y = nil)
164
+ pt = normalize_xy(x, y)
165
+ return false if pt.nil?
166
+
167
+ w, h = platform_module.size
168
+ pt.x >= 0 && pt.x < w && pt.y >= 0 && pt.y < h
169
+ end
170
+ alias on_screen onScreen
171
+
172
+ def isValidKey(key)
173
+ !platform_module.keyboard_mapping[key].nil? || key.length == 1
174
+ end
175
+ alias valid_key? isValidKey
176
+ alias is_valid_key isValidKey
177
+
178
+ def mouseDown(x = nil, y = nil, button: PRIMARY, duration: 0.0, tween: nil, logScreenshot: nil, _pause: true)
179
+ with_checks(_pause) do
180
+ button = normalize_button(button)
181
+ pt = normalize_xy(x, y)
182
+ mouse_move_drag("move", pt.x, pt.y, 0, 0, duration, tween)
183
+ log_screenshot(logScreenshot, "mouseDown", "#{pt.x},#{pt.y}")
184
+ platform_module.mouse_down(pt.x, pt.y, button)
185
+ end
186
+ end
187
+ alias mouse_down mouseDown
188
+
189
+ def mouseUp(x = nil, y = nil, button: PRIMARY, duration: 0.0, tween: nil, logScreenshot: nil, _pause: true)
190
+ with_checks(_pause) do
191
+ button = normalize_button(button)
192
+ pt = normalize_xy(x, y)
193
+ mouse_move_drag("move", pt.x, pt.y, 0, 0, duration, tween)
194
+ log_screenshot(logScreenshot, "mouseUp", "#{pt.x},#{pt.y}")
195
+ platform_module.mouse_up(pt.x, pt.y, button)
196
+ end
197
+ end
198
+ alias mouse_up mouseUp
199
+
200
+ def click(x = nil, y = nil, clicks: 1, interval: 0.0, button: PRIMARY, duration: 0.0, tween: nil, logScreenshot: nil, _pause: true)
201
+ with_checks(_pause) do
202
+ button = normalize_button(button)
203
+ pt = normalize_xy(x, y)
204
+ mouse_move_drag("move", pt.x, pt.y, 0, 0, duration, tween)
205
+ log_screenshot(logScreenshot, "click", "#{button},#{clicks},#{pt.x},#{pt.y}")
206
+ clicks.to_i.times do
207
+ failSafeCheck
208
+ platform_module.click(pt.x, pt.y, button)
209
+ Kernel.sleep(interval.to_f) if interval.to_f.positive?
210
+ end
211
+ end
212
+ end
213
+
214
+ def leftClick(x = nil, y = nil, interval: 0.0, duration: 0.0, tween: nil, logScreenshot: nil, _pause: true)
215
+ click(x, y, clicks: 1, interval: interval, button: LEFT, duration: duration, tween: tween, logScreenshot: logScreenshot, _pause: _pause)
216
+ end
217
+ alias left_click leftClick
218
+
219
+ def rightClick(x = nil, y = nil, interval: 0.0, duration: 0.0, tween: nil, logScreenshot: nil, _pause: true)
220
+ click(x, y, clicks: 1, interval: interval, button: RIGHT, duration: duration, tween: tween, logScreenshot: logScreenshot, _pause: _pause)
221
+ end
222
+ alias right_click rightClick
223
+
224
+ def middleClick(x = nil, y = nil, interval: 0.0, duration: 0.0, tween: nil, logScreenshot: nil, _pause: true)
225
+ click(x, y, clicks: 1, interval: interval, button: MIDDLE, duration: duration, tween: tween, logScreenshot: logScreenshot, _pause: _pause)
226
+ end
227
+ alias middle_click middleClick
228
+
229
+ def doubleClick(x = nil, y = nil, interval: 0.0, button: LEFT, duration: 0.0, tween: nil, logScreenshot: nil, _pause: true)
230
+ click(x, y, clicks: 2, interval: interval, button: button, duration: duration, tween: tween, logScreenshot: logScreenshot, _pause: _pause)
231
+ end
232
+ alias double_click doubleClick
233
+
234
+ def tripleClick(x = nil, y = nil, interval: 0.0, button: LEFT, duration: 0.0, tween: nil, logScreenshot: nil, _pause: true)
235
+ click(x, y, clicks: 3, interval: interval, button: button, duration: duration, tween: tween, logScreenshot: logScreenshot, _pause: _pause)
236
+ end
237
+ alias triple_click tripleClick
238
+
239
+ def scroll(clicks, x = nil, y = nil, logScreenshot: nil, _pause: true)
240
+ with_checks(_pause) do
241
+ x, y = x.to_a if x.is_a?(Array)
242
+ pt = position(x, y)
243
+ log_screenshot(logScreenshot, "scroll", "#{clicks},#{pt.x},#{pt.y}")
244
+ platform_module.scroll(clicks, pt.x, pt.y)
245
+ end
246
+ end
247
+
248
+ def hscroll(clicks, x = nil, y = nil, logScreenshot: nil, _pause: true)
249
+ with_checks(_pause) do
250
+ x, y = x.to_a if x.is_a?(Array)
251
+ pt = position(x, y)
252
+ log_screenshot(logScreenshot, "hscroll", "#{clicks},#{pt.x},#{pt.y}")
253
+ platform_module.hscroll(clicks, pt.x, pt.y)
254
+ end
255
+ end
256
+
257
+ def vscroll(clicks, x = nil, y = nil, logScreenshot: nil, _pause: true)
258
+ with_checks(_pause) do
259
+ x, y = x.to_a if x.is_a?(Array)
260
+ pt = position(x, y)
261
+ log_screenshot(logScreenshot, "vscroll", "#{clicks},#{pt.x},#{pt.y}")
262
+ platform_module.vscroll(clicks, pt.x, pt.y)
263
+ end
264
+ end
265
+
266
+ def moveTo(x = nil, y = nil, duration: 0.0, tween: nil, logScreenshot: false, _pause: true)
267
+ with_checks(_pause) do
268
+ pt = normalize_xy(x, y)
269
+ log_screenshot(logScreenshot, "moveTo", "#{pt.x},#{pt.y}")
270
+ mouse_move_drag("move", pt.x, pt.y, 0, 0, duration, tween)
271
+ end
272
+ end
273
+ alias move_to moveTo
274
+
275
+ def moveRel(xOffset = nil, yOffset = nil, duration: 0.0, tween: nil, logScreenshot: false, _pause: true)
276
+ with_checks(_pause) do
277
+ if xOffset.is_a?(Array)
278
+ yOffset = xOffset[1]
279
+ xOffset = xOffset[0]
280
+ end
281
+ xOffset = 0 if xOffset.nil?
282
+ yOffset = 0 if yOffset.nil?
283
+ log_screenshot(logScreenshot, "moveRel", "#{xOffset},#{yOffset}")
284
+ mouse_move_drag("move", nil, nil, xOffset, yOffset, duration, tween)
285
+ end
286
+ end
287
+ alias move_rel moveRel
288
+ alias move moveRel
289
+
290
+ def dragTo(x = nil, y = nil, duration: 0.0, tween: nil, button: PRIMARY, logScreenshot: nil, _pause: true, mouseDownUp: true)
291
+ with_checks(_pause) do
292
+ pt = normalize_xy(x, y)
293
+ log_screenshot(logScreenshot, "dragTo", "#{pt.x},#{pt.y}")
294
+ mouseDown(button: button, logScreenshot: false, _pause: false) if mouseDownUp
295
+ mouse_move_drag("drag", pt.x, pt.y, 0, 0, duration, tween, button)
296
+ mouseUp(button: button, logScreenshot: false, _pause: false) if mouseDownUp
297
+ end
298
+ end
299
+ alias drag_to dragTo
300
+
301
+ def dragRel(xOffset = 0, yOffset = 0, duration: 0.0, tween: nil, button: PRIMARY, logScreenshot: nil, _pause: true, mouseDownUp: true)
302
+ with_checks(_pause) do
303
+ if xOffset.is_a?(Array)
304
+ yOffset = xOffset[1]
305
+ xOffset = xOffset[0]
306
+ end
307
+ xOffset = 0 if xOffset.nil?
308
+ yOffset = 0 if yOffset.nil?
309
+ return if xOffset.to_i.zero? && yOffset.to_i.zero?
310
+
311
+ mousex, mousey = platform_module.position
312
+ log_screenshot(logScreenshot, "dragRel", "#{xOffset},#{yOffset}")
313
+ mouseDown(button: button, logScreenshot: false, _pause: false) if mouseDownUp
314
+ mouse_move_drag("drag", mousex, mousey, xOffset, yOffset, duration, tween, button)
315
+ mouseUp(button: button, logScreenshot: false, _pause: false) if mouseDownUp
316
+ end
317
+ end
318
+ alias drag_rel dragRel
319
+ alias drag dragRel
320
+
321
+ def keyDown(key, logScreenshot: nil, _pause: true)
322
+ with_checks(_pause) do
323
+ key = key.downcase if key.length > 1
324
+ log_screenshot(logScreenshot, "keyDown", key)
325
+ platform_module.key_down(key)
326
+ end
327
+ end
328
+ alias key_down keyDown
329
+
330
+ def keyUp(key, logScreenshot: nil, _pause: true)
331
+ with_checks(_pause) do
332
+ key = key.downcase if key.length > 1
333
+ log_screenshot(logScreenshot, "keyUp", key)
334
+ platform_module.key_up(key)
335
+ end
336
+ end
337
+ alias key_up keyUp
338
+
339
+ def press(keys, presses: 1, interval: 0.0, logScreenshot: nil, _pause: true)
340
+ with_checks(_pause) do
341
+ keys = normalize_keys(keys)
342
+ log_screenshot(logScreenshot, "press", keys.join(","))
343
+ presses.to_i.times do
344
+ keys.each do |k|
345
+ failSafeCheck
346
+ platform_module.key_down(k)
347
+ platform_module.key_up(k)
348
+ end
349
+ Kernel.sleep(interval.to_f) if interval.to_f.positive?
350
+ end
351
+ end
352
+ end
353
+
354
+ def hold(keys, logScreenshot: nil, _pause: true)
355
+ raise ArgumentError, "hold requires a block (AutoGUI.hold('shift') { ... })" unless block_given?
356
+
357
+ keys = normalize_keys(keys)
358
+ with_checks(_pause) do
359
+ log_screenshot(logScreenshot, "hold", keys.join(","))
360
+ keys.each do |k|
361
+ failSafeCheck
362
+ platform_module.key_down(k)
363
+ end
364
+ end
365
+ begin
366
+ yield
367
+ ensure
368
+ keys.each do |k|
369
+ failSafeCheck
370
+ platform_module.key_up(k)
371
+ end
372
+ end
373
+ end
374
+
375
+ def typewrite(message, interval: 0.0, logScreenshot: nil, _pause: true)
376
+ with_checks(_pause) do
377
+ log_screenshot(logScreenshot, "write", message.to_s[0, 12])
378
+ chars = message.is_a?(String) ? message.chars : Array(message)
379
+ chars.each do |c|
380
+ c = c.downcase if c.length > 1
381
+ press(c, _pause: false)
382
+ Kernel.sleep(interval.to_f) if interval.to_f.positive?
383
+ failSafeCheck
384
+ end
385
+ end
386
+ end
387
+ alias write typewrite
388
+
389
+ def hotkey(*args, interval: 0.0, logScreenshot: nil, _pause: true)
390
+ with_checks(_pause) do
391
+ args = args[0].to_a if args.length == 1 && args[0].is_a?(Array)
392
+ log_screenshot(logScreenshot, "hotkey", args.join(","))
393
+ args.each do |c|
394
+ c = c.downcase if c.length > 1
395
+ platform_module.key_down(c)
396
+ Kernel.sleep(interval.to_f) if interval.to_f.positive?
397
+ end
398
+ args.reverse_each do |c|
399
+ c = c.downcase if c.length > 1
400
+ platform_module.key_up(c)
401
+ Kernel.sleep(interval.to_f) if interval.to_f.positive?
402
+ end
403
+ end
404
+ end
405
+ alias shortcut hotkey
406
+
407
+ def screenshot(imageFilename = nil, region: nil)
408
+ img = platform_module.screenshot(region)
409
+ img.save(imageFilename) if imageFilename
410
+ img
411
+ end
412
+ alias grab screenshot
413
+
414
+ def locateOnScreen(image, grayscale: false, confidence: nil, region: nil, minSearchTime: 0)
415
+ deadline = Time.now + minSearchTime.to_f
416
+ loop do
417
+ hay = screenshot(region: region)
418
+ box = Screenshot.locate(image, hay, grayscale: grayscale, confidence: confidence)
419
+ if box
420
+ if region
421
+ return Box.new(box.left + region[0], box.top + region[1], box.width, box.height)
422
+ end
423
+
424
+ return box
425
+ end
426
+ break if Time.now >= deadline
427
+
428
+ Kernel.sleep(0.05)
429
+ end
430
+ raise ImageNotFoundException, "image not found on screen" if @use_image_not_found_exception
431
+
432
+ nil
433
+ end
434
+ alias locate_on_screen locateOnScreen
435
+
436
+ def locateAllOnScreen(image, grayscale: false, confidence: nil, region: nil)
437
+ hay = screenshot(region: region)
438
+ enum = Screenshot.locate_all(image, hay, grayscale: grayscale, confidence: confidence)
439
+ if region
440
+ enum = enum.lazy.map { |b| Box.new(b.left + region[0], b.top + region[1], b.width, b.height) }
441
+ end
442
+ enum
443
+ end
444
+ alias locate_all_on_screen locateAllOnScreen
445
+
446
+ def locateCenterOnScreen(image, grayscale: false, confidence: nil, region: nil, minSearchTime: 0)
447
+ box = locateOnScreen(image, grayscale: grayscale, confidence: confidence, region: region, minSearchTime: minSearchTime)
448
+ box && center(box)
449
+ end
450
+ alias locate_center_on_screen locateCenterOnScreen
451
+
452
+ def locate(needleImage, haystackImage, grayscale: false, confidence: nil)
453
+ box = Screenshot.locate(needleImage, haystackImage, grayscale: grayscale, confidence: confidence)
454
+ raise ImageNotFoundException, "image not found" if box.nil? && @use_image_not_found_exception
455
+
456
+ box
457
+ end
458
+
459
+ def locateAll(needleImage, haystackImage, grayscale: false, confidence: nil)
460
+ Screenshot.locate_all(needleImage, haystackImage, grayscale: grayscale, confidence: confidence)
461
+ end
462
+ alias locate_all locateAll
463
+
464
+ def locateOnWindow(image, title, grayscale: false, confidence: nil)
465
+ wins = getWindowsWithTitle(title)
466
+ raise AutoGUIException, "no window with title #{title.inspect}" if wins.empty?
467
+
468
+ w = wins.first
469
+ locateOnScreen(image, grayscale: grayscale, confidence: confidence, region: [w.left, w.top, w.width, w.height])
470
+ end
471
+ alias locate_on_window locateOnWindow
472
+
473
+ def center(coords)
474
+ Screenshot.center(coords)
475
+ end
476
+
477
+ def pixel(x, y)
478
+ platform_module.pixel(x.to_i, y.to_i)
479
+ end
480
+
481
+ def pixelMatchesColor(x, y, expectedRGBColor, tolerance: 0)
482
+ Screenshot.pixel_matches_color(x, y, expectedRGBColor, tolerance)
483
+ end
484
+ alias pixel_matches_color pixelMatchesColor
485
+
486
+ def alert(text = "", title = "", button = "OK")
487
+ MessageBox.alert(text, title.empty? ? "AutoGUI Alert" : title, button)
488
+ end
489
+
490
+ def confirm(text = "", title = "", buttons = %w[OK Cancel])
491
+ MessageBox.confirm(text, title.empty? ? "AutoGUI Confirm" : title, buttons)
492
+ end
493
+
494
+ def prompt(text = "", title = "", default = "")
495
+ MessageBox.prompt(text, title.empty? ? "AutoGUI Prompt" : title, default)
496
+ end
497
+
498
+ def password(text = "", title = "", default = "", mask = "*")
499
+ MessageBox.password(text, title.empty? ? "AutoGUI Password" : title, default, mask)
500
+ end
501
+
502
+ def mouseInfo
503
+ displayMousePosition
504
+ end
505
+ alias mouse_info mouseInfo
506
+
507
+ def displayMousePosition(xOffset = 0, yOffset = 0)
508
+ puts "Press Ctrl-C to quit."
509
+ puts "xOffset: #{xOffset} yOffset: #{yOffset}" unless xOffset.zero? && yOffset.zero?
510
+ loop do
511
+ x, y = position
512
+ rx = x - xOffset
513
+ ry = y - yOffset
514
+ rgb =
515
+ begin
516
+ onScreen(rx, ry) ? pixel(x, y) : %w[NaN NaN NaN]
517
+ rescue StandardError
518
+ %w[NaN NaN NaN]
519
+ end
520
+ line = format("X: %4d Y: %4d RGB: (%3s, %3s, %3s)", rx, ry, rgb[0], rgb[1], rgb[2])
521
+ print line
522
+ print "\b" * line.length
523
+ $stdout.flush
524
+ Kernel.sleep(0.05)
525
+ end
526
+ rescue Interrupt
527
+ puts
528
+ end
529
+ alias display_mouse_position displayMousePosition
530
+
531
+ def run(commandStr, _ssCount = nil)
532
+ Run.run(commandStr, _ssCount)
533
+ end
534
+
535
+ def printInfo(dontPrint = false)
536
+ plat, ruby_ver, ver, exe, res, ts = getInfo
537
+ msg = <<~MSG
538
+ Platform: #{plat}
539
+ Ruby Version: #{ruby_ver}
540
+ AutoGUI Version: #{ver}
541
+ Executable: #{exe}
542
+ Resolution: #{res}
543
+ Timestamp: #{ts}
544
+ MSG
545
+ puts msg unless dontPrint
546
+ msg
547
+ end
548
+ alias print_info printInfo
549
+
550
+ def getInfo
551
+ [RbConfig::CONFIG["host_os"], RUBY_DESCRIPTION, VERSION, RbConfig.ruby, size, Time.now]
552
+ end
553
+ alias get_info getInfo
554
+
555
+ %i[getAllWindows getAllTitles getWindowsWithTitle getWindowsAt getActiveWindow getActiveWindowTitle
556
+ get_all_windows get_all_titles get_windows_with_title get_windows_at get_active_window get_active_window_title].each do |name|
557
+ define_singleton_method(name) { |*args, **kwargs| WindowFunctions.public_send(name, *args, **kwargs) }
558
+ end
559
+
560
+ def with_checks(_pause)
561
+ failSafeCheck
562
+ result = yield
563
+ Kernel.sleep(@pause.to_f) if _pause && @pause.to_f.positive?
564
+ result
565
+ end
566
+
567
+ def normalize_button(button)
568
+ button = button.to_s.downcase
569
+ linux = Platform.linux?
570
+ valid = linux ? %w[left middle right primary secondary 1 2 3 4 5 6 7] : %w[left middle right primary secondary 1 2 3]
571
+ unless valid.include?(button)
572
+ raise AutoGUIException, "button argument must be one of #{valid.inspect}"
573
+ end
574
+
575
+ if %w[primary secondary].include?(button)
576
+ swapped = platform_module.mouse_is_swapped?
577
+ if button == "primary"
578
+ return swapped ? RIGHT : LEFT
579
+ end
580
+
581
+ return swapped ? LEFT : RIGHT
582
+ end
583
+
584
+ { "left" => LEFT, "middle" => MIDDLE, "right" => RIGHT, "1" => LEFT, "2" => MIDDLE, "3" => RIGHT,
585
+ "4" => "4", "5" => "5", "6" => "6", "7" => "7" }[button]
586
+ end
587
+
588
+ def normalize_xy(first, second)
589
+ if first.nil? && second.nil?
590
+ return position
591
+ elsif first.nil? && !second.nil?
592
+ return Point.new(position.x, second)
593
+ elsif second.nil? && !first.nil? && !first.is_a?(Array) && !first.is_a?(String) && !first.is_a?(Point) && !first.is_a?(Box)
594
+ return Point.new(first, position.y)
595
+ elsif first.is_a?(String)
596
+ loc = locateOnScreen(first)
597
+ return loc && center(loc)
598
+ elsif first.is_a?(Point)
599
+ return first
600
+ elsif first.is_a?(Box)
601
+ return center(first)
602
+ elsif first.is_a?(Array) || first.is_a?(Size)
603
+ arr = first.to_a
604
+ if arr.length == 2 && second.nil?
605
+ return Point.new(arr[0], arr[1])
606
+ elsif arr.length == 4 && second.nil?
607
+ return center(arr)
608
+ elsif !second.nil?
609
+ raise AutoGUIException, "When passing a sequence for firstArg, secondArg must not be passed (received #{second.inspect})."
610
+ else
611
+ raise AutoGUIException, "The supplied sequence must have exactly 2 or exactly 4 elements (#{arr.length} were received)."
612
+ end
613
+ else
614
+ Point.new(first, second)
615
+ end
616
+ end
617
+
618
+ def normalize_keys(keys)
619
+ if keys.is_a?(String)
620
+ keys = keys.downcase if keys.length > 1
621
+ [keys]
622
+ else
623
+ Array(keys).map { |s| s.length > 1 ? s.downcase : s }
624
+ end
625
+ end
626
+
627
+ def mouse_move_drag(move_or_drag, x, y, x_offset, y_offset, duration, tween = nil, _button = nil)
628
+ tween ||= method(:linear)
629
+ x_offset = x_offset.nil? ? 0 : x_offset.to_i
630
+ y_offset = y_offset.nil? ? 0 : y_offset.to_i
631
+ return if x.nil? && y.nil? && x_offset.zero? && y_offset.zero?
632
+
633
+ startx, starty = position
634
+ x = x.nil? ? startx : x.to_i
635
+ y = y.nil? ? starty : y.to_i
636
+ x += x_offset
637
+ y += y_offset
638
+
639
+ steps = [[x, y]]
640
+ sleep_amount = 0
641
+ if duration.to_f > @minimum_duration
642
+ width, height = size
643
+ num_steps = [width, height].max
644
+ sleep_amount = duration.to_f / num_steps
645
+ if sleep_amount < @minimum_sleep
646
+ num_steps = (duration.to_f / @minimum_sleep).to_i
647
+ num_steps = 1 if num_steps < 1
648
+ sleep_amount = duration.to_f / num_steps
649
+ end
650
+ steps = (0...num_steps).map do |n|
651
+ getPointOnLine(startx, starty, x, y, tween.call(n.to_f / num_steps))
652
+ end
653
+ steps << [x, y]
654
+ end
655
+
656
+ tween_x = x
657
+ tween_y = y
658
+ steps.each do |sx, sy|
659
+ Kernel.sleep(sleep_amount) if steps.length > 1
660
+ tween_x = sx.round
661
+ tween_y = sy.round
662
+ failSafeCheck unless @failsafe_points.any? { |p| p.x == tween_x && p.y == tween_y }
663
+ platform_module.move_to(tween_x, tween_y)
664
+ end
665
+ failSafeCheck unless @failsafe_points.any? { |p| p.x == tween_x && p.y == tween_y }
666
+ end
667
+
668
+ def log_screenshot(log, func_name, func_args, folder = ".")
669
+ return if log == false
670
+ return if log.nil? && !@log_screenshots
671
+
672
+ func_args = "#{func_args[0, 12]}..." if func_args.length > 12
673
+ now = Time.now
674
+ filename = format(
675
+ "%04d-%02d-%02d_%02d-%02d-%02d-%03d_%s_%s.png",
676
+ now.year, now.month, now.day, now.hour, now.min, now.sec,
677
+ (now.usec / 1000), func_name, func_args.gsub(/[^\w,.-]/, "_")
678
+ )
679
+ if @log_screenshots_limit && @log_screenshot_filenames.length >= @log_screenshots_limit
680
+ old = File.join(folder, @log_screenshot_filenames.shift)
681
+ File.unlink(old) if File.exist?(old)
682
+ end
683
+ screenshot(File.join(folder, filename))
684
+ @log_screenshot_filenames << filename
685
+ end
686
+
687
+ # module_function copies methods to the singleton class but does not copy
688
+ # subsequent `alias` names. Re-alias them on the singleton class.
689
+ class << self
690
+ {
691
+ get_point_on_line: :getPointOnLine,
692
+ fail_safe_check: :failSafeCheck,
693
+ use_image_not_found_exception!: :useImageNotFoundException,
694
+ on_screen: :onScreen,
695
+ valid_key?: :isValidKey,
696
+ is_valid_key: :isValidKey,
697
+ mouse_down: :mouseDown,
698
+ mouse_up: :mouseUp,
699
+ left_click: :leftClick,
700
+ right_click: :rightClick,
701
+ middle_click: :middleClick,
702
+ double_click: :doubleClick,
703
+ triple_click: :tripleClick,
704
+ move_to: :moveTo,
705
+ move_rel: :moveRel,
706
+ move: :moveRel,
707
+ drag_to: :dragTo,
708
+ drag_rel: :dragRel,
709
+ drag: :dragRel,
710
+ key_down: :keyDown,
711
+ key_up: :keyUp,
712
+ write: :typewrite,
713
+ shortcut: :hotkey,
714
+ grab: :screenshot,
715
+ locate_on_screen: :locateOnScreen,
716
+ locate_all_on_screen: :locateAllOnScreen,
717
+ locate_center_on_screen: :locateCenterOnScreen,
718
+ locate_all: :locateAll,
719
+ locate_on_window: :locateOnWindow,
720
+ pixel_matches_color: :pixelMatchesColor,
721
+ mouse_info: :mouseInfo,
722
+ display_mouse_position: :displayMousePosition,
723
+ print_info: :printInfo,
724
+ get_info: :getInfo
725
+ }.each do |snake, camel|
726
+ alias_method snake, camel if method_defined?(camel) || private_method_defined?(camel)
727
+ end
728
+ end
729
+ end
730
+
731
+ begin
732
+ _right, _bottom = AutoGUI.size
733
+ AutoGUI.failsafe_points.concat(
734
+ [AutoGUI::Point.new(0, _bottom - 1), AutoGUI::Point.new(_right - 1, 0), AutoGUI::Point.new(_right - 1, _bottom - 1)]
735
+ )
736
+ rescue StandardError
737
+ nil
738
+ end