Skip to content

Commit 14fd338

Browse files
fix bounds
1 parent 6a16234 commit 14fd338

File tree

1 file changed

+233
-0
lines changed

1 file changed

+233
-0
lines changed

simtouch.zig

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
const std = @import("std");
2+
const c = @cImport({
3+
@cInclude("CoreGraphics/CoreGraphics.h");
4+
@cInclude("ApplicationServices/ApplicationServices.h");
5+
});
6+
7+
const SimulatorBounds = struct {
8+
x: f32,
9+
y: f32,
10+
width: f32,
11+
height: f32,
12+
};
13+
14+
fn findSimulatorWindow() !SimulatorBounds {
15+
const window_list = c.CGWindowListCopyWindowInfo(
16+
c.kCGWindowListOptionOnScreenOnly | c.kCGWindowListExcludeDesktopElements,
17+
c.kCGNullWindowID,
18+
);
19+
defer c.CFRelease(window_list);
20+
21+
const count = c.CFArrayGetCount(window_list);
22+
var i: usize = 0;
23+
24+
while (i < count) : (i += 1) {
25+
const window = c.CFArrayGetValueAtIndex(window_list, @intCast(i));
26+
const window_dict = @as(c.CFDictionaryRef, @ptrCast(window));
27+
28+
const owner_key = c.kCGWindowOwnerName;
29+
const owner_value = c.CFDictionaryGetValue(window_dict, owner_key);
30+
if (owner_value == null) continue;
31+
32+
const owner_str = @as(c.CFStringRef, @ptrCast(owner_value));
33+
var buffer: [256]u8 = undefined;
34+
35+
if (c.CFStringGetCString(owner_str, &buffer, buffer.len, c.kCFStringEncodingUTF8) == 0) {
36+
continue;
37+
}
38+
39+
const owner_name = std.mem.sliceTo(&buffer, 0);
40+
41+
if (std.mem.indexOf(u8, owner_name, "Simulator") != null) {
42+
const bounds_key = c.kCGWindowBounds;
43+
const bounds_value = c.CFDictionaryGetValue(window_dict, bounds_key);
44+
if (bounds_value == null) continue;
45+
46+
const bounds_dict = @as(c.CFDictionaryRef, @ptrCast(bounds_value));
47+
48+
const x_key = c.CFStringCreateWithCString(null, "X", c.kCFStringEncodingUTF8);
49+
defer c.CFRelease(x_key);
50+
const x_value = c.CFDictionaryGetValue(bounds_dict, x_key);
51+
52+
const y_key = c.CFStringCreateWithCString(null, "Y", c.kCFStringEncodingUTF8);
53+
defer c.CFRelease(y_key);
54+
const y_value = c.CFDictionaryGetValue(bounds_dict, y_key);
55+
56+
const width_key = c.CFStringCreateWithCString(null, "Width", c.kCFStringEncodingUTF8);
57+
defer c.CFRelease(width_key);
58+
const width_value = c.CFDictionaryGetValue(bounds_dict, width_key);
59+
60+
const height_key = c.CFStringCreateWithCString(null, "Height", c.kCFStringEncodingUTF8);
61+
defer c.CFRelease(height_key);
62+
const height_value = c.CFDictionaryGetValue(bounds_dict, height_key);
63+
64+
var x: f64 = 0;
65+
var y: f64 = 0;
66+
var width: f64 = 0;
67+
var height: f64 = 0;
68+
69+
_ = c.CFNumberGetValue(@as(c.CFNumberRef, @ptrCast(x_value)), c.kCFNumberFloat64Type, &x);
70+
_ = c.CFNumberGetValue(@as(c.CFNumberRef, @ptrCast(y_value)), c.kCFNumberFloat64Type, &y);
71+
_ = c.CFNumberGetValue(@as(c.CFNumberRef, @ptrCast(width_value)), c.kCFNumberFloat64Type, &width);
72+
_ = c.CFNumberGetValue(@as(c.CFNumberRef, @ptrCast(height_value)), c.kCFNumberFloat64Type, &height);
73+
74+
std.debug.print("Found Simulator at ({d:.0}, {d:.0}) size: {d:.0}x{d:.0}\n", .{ x, y, width, height });
75+
76+
return SimulatorBounds{
77+
.x = @floatCast(x),
78+
.y = @floatCast(y),
79+
.width = @floatCast(width),
80+
.height = @floatCast(height),
81+
};
82+
}
83+
}
84+
85+
return error.SimulatorNotFound;
86+
}
87+
88+
fn sendMouseEvent(x: f32, y: f32, event_type: c.CGEventType, button: c.CGMouseButton) void {
89+
const point = c.CGPointMake(x, y);
90+
const event = c.CGEventCreateMouseEvent(null, event_type, point, button);
91+
if (event != null) {
92+
c.CGEventPost(c.kCGHIDEventTap, event);
93+
c.CFRelease(event);
94+
}
95+
}
96+
97+
fn sendTap(x: f32, y: f32) !void {
98+
const bounds = try findSimulatorWindow();
99+
100+
const absolute_x = bounds.x + x;
101+
const absolute_y = bounds.y + y;
102+
103+
std.debug.print("Sending tap at ({d:.1}, {d:.1})\n", .{ x, y });
104+
105+
sendMouseEvent(absolute_x, absolute_y, c.kCGEventMouseMoved, c.kCGMouseButtonLeft);
106+
std.time.sleep(10_000_000); // 10ms
107+
108+
sendMouseEvent(absolute_x, absolute_y, c.kCGEventLeftMouseDown, c.kCGMouseButtonLeft);
109+
std.time.sleep(50_000_000); // 50ms
110+
111+
sendMouseEvent(absolute_x, absolute_y, c.kCGEventLeftMouseUp, c.kCGMouseButtonLeft);
112+
}
113+
114+
fn sendSwipe(x1: f32, y1: f32, x2: f32, y2: f32, steps: u32) !void {
115+
const bounds = try findSimulatorWindow();
116+
117+
const absolute_x1 = bounds.x + x1;
118+
const absolute_y1 = bounds.y + y1;
119+
const absolute_x2 = bounds.x + x2;
120+
const absolute_y2 = bounds.y + y2;
121+
122+
std.debug.print("Sending swipe from ({d:.1}, {d:.1}) to ({d:.1}, {d:.1})\n", .{ x1, y1, x2, y2 });
123+
124+
const dx = (absolute_x2 - absolute_x1) / @as(f32, @floatFromInt(steps));
125+
const dy = (absolute_y2 - absolute_y1) / @as(f32, @floatFromInt(steps));
126+
127+
// Move to start
128+
sendMouseEvent(absolute_x1, absolute_y1, c.kCGEventMouseMoved, c.kCGMouseButtonLeft);
129+
std.time.sleep(20_000_000); // 20ms
130+
131+
// Mouse down
132+
sendMouseEvent(absolute_x1, absolute_y1, c.kCGEventLeftMouseDown, c.kCGMouseButtonLeft);
133+
std.time.sleep(20_000_000); // 20ms
134+
135+
// Drag through points
136+
var i: u32 = 1;
137+
while (i <= steps) : (i += 1) {
138+
const x = absolute_x1 + (dx * @as(f32, @floatFromInt(i)));
139+
const y = absolute_y1 + (dy * @as(f32, @floatFromInt(i)));
140+
sendMouseEvent(x, y, c.kCGEventLeftMouseDragged, c.kCGMouseButtonLeft);
141+
std.time.sleep(10_000_000); // 10ms
142+
}
143+
144+
// Mouse up
145+
sendMouseEvent(absolute_x2, absolute_y2, c.kCGEventLeftMouseUp, c.kCGMouseButtonLeft);
146+
}
147+
148+
fn sendLongPress(x: f32, y: f32, duration_ms: u32) !void {
149+
const bounds = try findSimulatorWindow();
150+
151+
const absolute_x = bounds.x + x;
152+
const absolute_y = bounds.y + y;
153+
154+
std.debug.print("Sending long press at ({d:.1}, {d:.1}) for {d}ms\n", .{ x, y, duration_ms });
155+
156+
sendMouseEvent(absolute_x, absolute_y, c.kCGEventMouseMoved, c.kCGMouseButtonLeft);
157+
std.time.sleep(10_000_000); // 10ms
158+
159+
sendMouseEvent(absolute_x, absolute_y, c.kCGEventLeftMouseDown, c.kCGMouseButtonLeft);
160+
161+
std.time.sleep(duration_ms * 1_000_000);
162+
163+
sendMouseEvent(absolute_x, absolute_y, c.kCGEventLeftMouseUp, c.kCGMouseButtonLeft);
164+
}
165+
166+
fn printUsage(program_name: []const u8) void {
167+
std.debug.print(
168+
\\iOS Simulator Touch Injector (Zig Version)
169+
\\Usage:
170+
\\ {s} tap <x> <y> - Send a tap at coordinates
171+
\\ {s} swipe <x1> <y1> <x2> <y2> - Send a swipe gesture
172+
\\ {s} longpress <x> <y> <duration> - Send a long press (duration in ms)
173+
\\
174+
\\Examples:
175+
\\ {s} tap 200 300
176+
\\ {s} swipe 100 500 100 100
177+
\\ {s} longpress 200 200 2000
178+
\\
179+
, .{ program_name, program_name, program_name, program_name, program_name, program_name });
180+
}
181+
182+
pub fn main() !void {
183+
const allocator = std.heap.page_allocator;
184+
const args = try std.process.argsAlloc(allocator);
185+
defer std.process.argsFree(allocator, args);
186+
187+
if (args.len < 2) {
188+
printUsage(args[0]);
189+
return;
190+
}
191+
192+
const command = args[1];
193+
194+
if (std.mem.eql(u8, command, "tap")) {
195+
if (args.len != 4) {
196+
std.debug.print("Error: tap requires x and y coordinates\n", .{});
197+
printUsage(args[0]);
198+
return;
199+
}
200+
201+
const x = try std.fmt.parseFloat(f32, args[2]);
202+
const y = try std.fmt.parseFloat(f32, args[3]);
203+
try sendTap(x, y);
204+
} else if (std.mem.eql(u8, command, "swipe")) {
205+
if (args.len != 6) {
206+
std.debug.print("Error: swipe requires x1, y1, x2, y2 coordinates\n", .{});
207+
printUsage(args[0]);
208+
return;
209+
}
210+
211+
const x1 = try std.fmt.parseFloat(f32, args[2]);
212+
const y1 = try std.fmt.parseFloat(f32, args[3]);
213+
const x2 = try std.fmt.parseFloat(f32, args[4]);
214+
const y2 = try std.fmt.parseFloat(f32, args[5]);
215+
try sendSwipe(x1, y1, x2, y2, 20);
216+
} else if (std.mem.eql(u8, command, "longpress")) {
217+
if (args.len != 5) {
218+
std.debug.print("Error: longpress requires x, y coordinates and duration\n", .{});
219+
printUsage(args[0]);
220+
return;
221+
}
222+
223+
const x = try std.fmt.parseFloat(f32, args[2]);
224+
const y = try std.fmt.parseFloat(f32, args[3]);
225+
const duration = try std.fmt.parseInt(u32, args[4], 10);
226+
try sendLongPress(x, y, duration);
227+
} else {
228+
std.debug.print("Unknown command: {s}\n", .{command});
229+
printUsage(args[0]);
230+
}
231+
232+
std.debug.print("Touch event sent successfully\n", .{});
233+
}

0 commit comments

Comments
 (0)