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