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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +20 -0
- data/LICENSE +29 -0
- data/README.md +431 -0
- data/bin/autogui +19 -0
- data/docs/api.md +186 -0
- data/docs/guide.md +243 -0
- data/docs/publishing.md +90 -0
- data/examples/hello.rb +20 -0
- data/examples/locate_demo.rb +16 -0
- data/lib/autogui/exceptions.rb +9 -0
- data/lib/autogui/geometry.rb +98 -0
- data/lib/autogui/image.rb +344 -0
- data/lib/autogui/keys.rb +50 -0
- data/lib/autogui/message_box.rb +125 -0
- data/lib/autogui/platform/darwin.rb +185 -0
- data/lib/autogui/platform/linux.rb +172 -0
- data/lib/autogui/platform/windows.rb +400 -0
- data/lib/autogui/platform.rb +41 -0
- data/lib/autogui/run.rb +168 -0
- data/lib/autogui/screenshot.rb +95 -0
- data/lib/autogui/tween.rb +254 -0
- data/lib/autogui/version.rb +5 -0
- data/lib/autogui/window.rb +170 -0
- data/lib/autogui.rb +738 -0
- metadata +90 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
require "tempfile"
|
|
5
|
+
|
|
6
|
+
module AutoGUI
|
|
7
|
+
module Platform
|
|
8
|
+
# macOS backend. Prefers CoreGraphics via Fiddle; falls back to osascript / screencapture.
|
|
9
|
+
module Darwin
|
|
10
|
+
KEYBOARD_CODES = {
|
|
11
|
+
"a" => 0x00, "s" => 0x01, "d" => 0x02, "f" => 0x03, "h" => 0x04, "g" => 0x05,
|
|
12
|
+
"z" => 0x06, "x" => 0x07, "c" => 0x08, "v" => 0x09, "b" => 0x0B, "q" => 0x0C,
|
|
13
|
+
"w" => 0x0D, "e" => 0x0E, "r" => 0x0F, "y" => 0x10, "t" => 0x11, "1" => 0x12,
|
|
14
|
+
"2" => 0x13, "3" => 0x14, "4" => 0x15, "6" => 0x16, "5" => 0x17, "=" => 0x18,
|
|
15
|
+
"9" => 0x19, "7" => 0x1A, "-" => 0x1B, "8" => 0x1C, "0" => 0x1D, "]" => 0x1E,
|
|
16
|
+
"o" => 0x1F, "u" => 0x20, "[" => 0x21, "i" => 0x22, "p" => 0x23, "l" => 0x25,
|
|
17
|
+
"j" => 0x26, "'" => 0x27, "k" => 0x28, ";" => 0x29, "\\" => 0x2A, "," => 0x2B,
|
|
18
|
+
"/" => 0x2C, "n" => 0x2D, "m" => 0x2E, "." => 0x2F, "`" => 0x32, " " => 0x31,
|
|
19
|
+
"space" => 0x31, "enter" => 0x24, "return" => 0x24, "\n" => 0x24, "\r" => 0x24,
|
|
20
|
+
"tab" => 0x30, "\t" => 0x30, "backspace" => 0x33, "esc" => 0x35, "escape" => 0x35,
|
|
21
|
+
"command" => 0x37, "shift" => 0x38, "shiftleft" => 0x38, "capslock" => 0x39,
|
|
22
|
+
"alt" => 0x3A, "option" => 0x3A, "optionleft" => 0x3A, "altleft" => 0x3A,
|
|
23
|
+
"ctrl" => 0x3B, "ctrlleft" => 0x3B, "shiftright" => 0x3C, "altright" => 0x3D,
|
|
24
|
+
"optionright" => 0x3D, "ctrlright" => 0x3E, "fn" => 0x3F, "f17" => 0x40,
|
|
25
|
+
"volumeup" => 0x48, "volumedown" => 0x49, "volumemute" => 0x4A, "f18" => 0x4F,
|
|
26
|
+
"f19" => 0x50, "f20" => 0x5A, "f5" => 0x60, "f6" => 0x61, "f7" => 0x62,
|
|
27
|
+
"f3" => 0x63, "f8" => 0x64, "f9" => 0x65, "f11" => 0x67, "f13" => 0x69,
|
|
28
|
+
"f16" => 0x6A, "f14" => 0x6B, "f10" => 0x6D, "f12" => 0x6F, "f15" => 0x71,
|
|
29
|
+
"help" => 0x72, "home" => 0x73, "pageup" => 0x74, "pgup" => 0x74, "del" => 0x75,
|
|
30
|
+
"delete" => 0x75, "f4" => 0x76, "end" => 0x77, "f2" => 0x78, "pagedown" => 0x79,
|
|
31
|
+
"pgdn" => 0x79, "f1" => 0x7A, "left" => 0x7B, "right" => 0x7C, "down" => 0x7D,
|
|
32
|
+
"up" => 0x7E, "win" => 0x37, "winleft" => 0x37
|
|
33
|
+
}.freeze
|
|
34
|
+
|
|
35
|
+
OSA_KEYS = {
|
|
36
|
+
"enter" => "return", "return" => "return", "esc" => "escape", "escape" => "escape",
|
|
37
|
+
"ctrl" => "control", "ctrlleft" => "control", "alt" => "option", "option" => "option",
|
|
38
|
+
"command" => "command", "win" => "command", "shift" => "shift", "tab" => "tab",
|
|
39
|
+
"space" => "space", "up" => "up arrow", "down" => "down arrow",
|
|
40
|
+
"left" => "left arrow", "right" => "right arrow", "backspace" => "delete",
|
|
41
|
+
"delete" => "forward delete", "home" => "home", "end" => "end",
|
|
42
|
+
"pageup" => "page up", "pagedown" => "page down"
|
|
43
|
+
}.freeze
|
|
44
|
+
|
|
45
|
+
module_function
|
|
46
|
+
|
|
47
|
+
def init!; end
|
|
48
|
+
|
|
49
|
+
def keyboard_mapping
|
|
50
|
+
@keyboard_mapping ||= begin
|
|
51
|
+
map = {}
|
|
52
|
+
KEY_NAMES.each { |k| map[k] = KEYBOARD_CODES[k] }
|
|
53
|
+
("a".."z").each { |c| map[c] = KEYBOARD_CODES[c] }
|
|
54
|
+
map
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def position
|
|
59
|
+
out, status = Open3.capture2("osascript", "-e",
|
|
60
|
+
'tell application "System Events" to get the {x, y} of the mouse')
|
|
61
|
+
if status.success?
|
|
62
|
+
x, y = out.strip.split(",").map { |v| v.strip.to_i }
|
|
63
|
+
return [x, y]
|
|
64
|
+
end
|
|
65
|
+
quartz_position || [0, 0]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def size
|
|
69
|
+
out, status = Open3.capture2("osascript", "-e",
|
|
70
|
+
'tell application "Finder" to get bounds of window of desktop')
|
|
71
|
+
if status.success?
|
|
72
|
+
parts = out.split(",").map { |v| v.strip.to_i }
|
|
73
|
+
return [parts[2], parts[3]] if parts.length == 4
|
|
74
|
+
end
|
|
75
|
+
quartz_size || [1440, 900]
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def move_to(x, y)
|
|
79
|
+
system("osascript", "-e", "tell application \"System Events\" to set the mouse location to {#{x.to_i}, #{y.to_i}}")
|
|
80
|
+
sleep(AutoGUI.darwin_catch_up_time)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def mouse_is_swapped?
|
|
84
|
+
false
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def mouse_down(x, y, button)
|
|
88
|
+
osa_click(x, y, button, "mousedown")
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def mouse_up(x, y, button)
|
|
92
|
+
osa_click(x, y, button, "mouseup")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def click(x, y, button)
|
|
96
|
+
btn = osa_button(button)
|
|
97
|
+
system("osascript", "-e",
|
|
98
|
+
"tell application \"System Events\" to click at {#{x.to_i}, #{y.to_i}}") if btn == "left"
|
|
99
|
+
system("osascript", "-e",
|
|
100
|
+
"tell application \"System Events\" to #{btn} click at {#{x.to_i}, #{y.to_i}}") unless btn == "left"
|
|
101
|
+
sleep(AutoGUI.darwin_catch_up_time)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def scroll(clicks, x, y)
|
|
105
|
+
move_to(x, y)
|
|
106
|
+
direction = clicks.to_i.positive? ? "up" : "down"
|
|
107
|
+
n = clicks.to_i.abs
|
|
108
|
+
system("osascript", "-e",
|
|
109
|
+
"tell application \"System Events\" to scroll #{direction} #{n}")
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
alias vscroll scroll
|
|
113
|
+
|
|
114
|
+
def hscroll(clicks, x, y)
|
|
115
|
+
scroll(clicks, x, y)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def key_down(key)
|
|
119
|
+
osa_key(key, "key down")
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def key_up(key)
|
|
123
|
+
osa_key(key, "key up")
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def screenshot(region = nil)
|
|
127
|
+
file = Tempfile.new(["autogui", ".png"])
|
|
128
|
+
file.close
|
|
129
|
+
system("screencapture", "-x", file.path)
|
|
130
|
+
img = Image.open(file.path)
|
|
131
|
+
File.unlink(file.path) rescue nil
|
|
132
|
+
if region
|
|
133
|
+
left, top, width, height = region.to_a
|
|
134
|
+
img.crop(left, top, width, height)
|
|
135
|
+
else
|
|
136
|
+
img
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def pixel(x, y)
|
|
141
|
+
screenshot([x, y, 1, 1]).getpixel(0, 0)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def message_box(text, title, _flags)
|
|
145
|
+
script = %(display dialog #{text.to_s.inspect} with title #{title.to_s.inspect} buttons {"OK"} default button 1)
|
|
146
|
+
system("osascript", "-e", script)
|
|
147
|
+
1
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def each_hwnd; end
|
|
151
|
+
|
|
152
|
+
def quartz_position
|
|
153
|
+
nil
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def quartz_size
|
|
157
|
+
nil
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def osa_button(button)
|
|
161
|
+
case button.to_s
|
|
162
|
+
when "left" then "left"
|
|
163
|
+
when "right" then "right"
|
|
164
|
+
when "middle" then "middle"
|
|
165
|
+
else "left"
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def osa_click(x, y, button, _kind)
|
|
170
|
+
move_to(x, y)
|
|
171
|
+
click(x, y, button)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def osa_key(key, action)
|
|
175
|
+
name = OSA_KEYS[key] || (key.length == 1 ? key : key)
|
|
176
|
+
if name.length == 1
|
|
177
|
+
system("osascript", "-e", "tell application \"System Events\" to #{action} #{name.inspect}")
|
|
178
|
+
else
|
|
179
|
+
system("osascript", "-e", "tell application \"System Events\" to #{action} #{name}")
|
|
180
|
+
end
|
|
181
|
+
sleep(AutoGUI.darwin_catch_up_time)
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
require "tempfile"
|
|
5
|
+
|
|
6
|
+
module AutoGUI
|
|
7
|
+
module Platform
|
|
8
|
+
# Linux backend. Uses xdotool when present, with xte / ImageMagick / scrot fallbacks.
|
|
9
|
+
module Linux
|
|
10
|
+
XDOTOOL_KEYS = {
|
|
11
|
+
"enter" => "Return", "return" => "Return", "\n" => "Return", "\r" => "Return",
|
|
12
|
+
"esc" => "Escape", "escape" => "Escape", "tab" => "Tab", "\t" => "Tab",
|
|
13
|
+
"backspace" => "BackSpace", "delete" => "Delete", "del" => "Delete",
|
|
14
|
+
"space" => "space", " " => "space", "shift" => "shift", "shiftleft" => "Shift_L",
|
|
15
|
+
"shiftright" => "Shift_R", "ctrl" => "ctrl", "ctrlleft" => "Control_L",
|
|
16
|
+
"ctrlright" => "Control_R", "alt" => "alt", "altleft" => "Alt_L",
|
|
17
|
+
"altright" => "Alt_R", "win" => "super", "winleft" => "Super_L",
|
|
18
|
+
"winright" => "Super_R", "command" => "super", "up" => "Up", "down" => "Down",
|
|
19
|
+
"left" => "Left", "right" => "Right", "home" => "Home", "end" => "End",
|
|
20
|
+
"pageup" => "Page_Up", "pgup" => "Page_Up", "pagedown" => "Page_Down",
|
|
21
|
+
"pgdn" => "Page_Down", "capslock" => "Caps_Lock", "numlock" => "Num_Lock",
|
|
22
|
+
"scrolllock" => "Scroll_Lock", "printscreen" => "Print", "insert" => "Insert"
|
|
23
|
+
}.freeze
|
|
24
|
+
|
|
25
|
+
module_function
|
|
26
|
+
|
|
27
|
+
def init!; end
|
|
28
|
+
|
|
29
|
+
def keyboard_mapping
|
|
30
|
+
@keyboard_mapping ||= begin
|
|
31
|
+
map = {}
|
|
32
|
+
KEY_NAMES.each { |k| map[k] = xdo_key(k) }
|
|
33
|
+
map
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def position
|
|
38
|
+
out, status = Open3.capture2("xdotool", "getmouselocation", "--shell")
|
|
39
|
+
if status.success?
|
|
40
|
+
x = out[/X=(\d+)/, 1].to_i
|
|
41
|
+
y = out[/Y=(\d+)/, 1].to_i
|
|
42
|
+
return [x, y]
|
|
43
|
+
end
|
|
44
|
+
[0, 0]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def size
|
|
48
|
+
out, status = Open3.capture2("xdotool", "getdisplaygeometry")
|
|
49
|
+
if status.success?
|
|
50
|
+
w, h = out.split.map(&:to_i)
|
|
51
|
+
return [w, h] if w.positive? && h.positive?
|
|
52
|
+
end
|
|
53
|
+
out, status = Open3.capture2("xdpyinfo")
|
|
54
|
+
if status.success? && out =~ /dimensions:\s+(\d+)x(\d+)/
|
|
55
|
+
return [Regexp.last_match(1).to_i, Regexp.last_match(2).to_i]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
[1920, 1080]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def move_to(x, y)
|
|
62
|
+
system("xdotool", "mousemove", x.to_i.to_s, y.to_i.to_s)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def mouse_is_swapped?
|
|
66
|
+
false
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def mouse_down(x, y, button)
|
|
70
|
+
move_to(x, y)
|
|
71
|
+
system("xdotool", "mousedown", xdo_button(button))
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def mouse_up(x, y, button)
|
|
75
|
+
move_to(x, y)
|
|
76
|
+
system("xdotool", "mouseup", xdo_button(button))
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def click(x, y, button)
|
|
80
|
+
move_to(x, y)
|
|
81
|
+
system("xdotool", "click", xdo_button(button))
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def scroll(clicks, x, y)
|
|
85
|
+
move_to(x, y)
|
|
86
|
+
btn = clicks.to_i.positive? ? "4" : "5"
|
|
87
|
+
clicks.to_i.abs.times { system("xdotool", "click", btn) }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
alias vscroll scroll
|
|
91
|
+
|
|
92
|
+
def hscroll(clicks, x, y)
|
|
93
|
+
move_to(x, y)
|
|
94
|
+
btn = clicks.to_i.positive? ? "6" : "7"
|
|
95
|
+
clicks.to_i.abs.times { system("xdotool", "click", btn) }
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def key_down(key)
|
|
99
|
+
system("xdotool", "keydown", xdo_key(key))
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def key_up(key)
|
|
103
|
+
system("xdotool", "keyup", xdo_key(key))
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def screenshot(region = nil)
|
|
107
|
+
file = Tempfile.new(["autogui", ".png"])
|
|
108
|
+
file.close
|
|
109
|
+
captured = false
|
|
110
|
+
if command?("scrot")
|
|
111
|
+
captured = system("scrot", "-o", file.path)
|
|
112
|
+
elsif command?("import")
|
|
113
|
+
captured = system("import", "-window", "root", file.path)
|
|
114
|
+
elsif command?("gnome-screenshot")
|
|
115
|
+
captured = system("gnome-screenshot", "-f", file.path)
|
|
116
|
+
elsif command?("grim")
|
|
117
|
+
captured = system("grim", file.path)
|
|
118
|
+
end
|
|
119
|
+
raise AutoGUIException, "no screenshot tool found (install scrot, imagemagick, or grim)" unless captured && File.size?(file.path)
|
|
120
|
+
|
|
121
|
+
img = Image.open(file.path)
|
|
122
|
+
File.unlink(file.path) rescue nil
|
|
123
|
+
if region
|
|
124
|
+
left, top, width, height = region.to_a
|
|
125
|
+
img.crop(left, top, width, height)
|
|
126
|
+
else
|
|
127
|
+
img
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def pixel(x, y)
|
|
132
|
+
screenshot([x, y, 1, 1]).getpixel(0, 0)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def message_box(text, title, _flags)
|
|
136
|
+
if command?("zenity")
|
|
137
|
+
system("zenity", "--info", "--title=#{title}", "--text=#{text}")
|
|
138
|
+
elsif command?("kdialog")
|
|
139
|
+
system("kdialog", "--title", title.to_s, "--msgbox", text.to_s)
|
|
140
|
+
elsif command?("xmessage")
|
|
141
|
+
system("xmessage", "-center", "#{title}: #{text}")
|
|
142
|
+
else
|
|
143
|
+
warn("#{title}: #{text}")
|
|
144
|
+
end
|
|
145
|
+
1
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def each_hwnd; end
|
|
149
|
+
|
|
150
|
+
def xdo_button(button)
|
|
151
|
+
case button.to_s
|
|
152
|
+
when "left" then "1"
|
|
153
|
+
when "middle" then "2"
|
|
154
|
+
when "right" then "3"
|
|
155
|
+
else button.to_s
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def xdo_key(key)
|
|
160
|
+
return XDOTOOL_KEYS[key] if XDOTOOL_KEYS.key?(key)
|
|
161
|
+
return "F#{Regexp.last_match(1)}" if key =~ /^f(\d+)$/
|
|
162
|
+
return key if key.length == 1
|
|
163
|
+
|
|
164
|
+
key
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def command?(name)
|
|
168
|
+
system("which", name, out: File::NULL, err: File::NULL)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|