usecomputer 0.0.3 → 0.0.4
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.
- package/README.md +324 -0
- package/dist/bridge-contract.test.js +124 -63
- package/dist/bridge.d.ts.map +1 -1
- package/dist/bridge.js +241 -46
- package/dist/cli-parsing.test.js +34 -11
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +323 -28
- package/dist/coord-map.d.ts +14 -0
- package/dist/coord-map.d.ts.map +1 -0
- package/dist/coord-map.js +75 -0
- package/dist/coord-map.test.d.ts +2 -0
- package/dist/coord-map.test.d.ts.map +1 -0
- package/dist/coord-map.test.js +157 -0
- package/dist/darwin-arm64/usecomputer.node +0 -0
- package/dist/darwin-x64/usecomputer.node +0 -0
- package/dist/debug-point-image.d.ts +8 -0
- package/dist/debug-point-image.d.ts.map +1 -0
- package/dist/debug-point-image.js +43 -0
- package/dist/debug-point-image.test.d.ts +2 -0
- package/dist/debug-point-image.test.d.ts.map +1 -0
- package/dist/debug-point-image.test.js +44 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/lib.d.ts +26 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +88 -0
- package/dist/native-click-smoke.test.js +69 -29
- package/dist/native-lib.d.ts +59 -1
- package/dist/native-lib.d.ts.map +1 -1
- package/dist/terminal-table.d.ts +10 -0
- package/dist/terminal-table.d.ts.map +1 -0
- package/dist/terminal-table.js +55 -0
- package/dist/terminal-table.test.d.ts +2 -0
- package/dist/terminal-table.test.d.ts.map +1 -0
- package/dist/terminal-table.test.js +41 -0
- package/dist/types.d.ts +45 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +16 -4
- package/src/bridge-contract.test.ts +140 -69
- package/src/bridge.ts +293 -53
- package/src/cli-parsing.test.ts +61 -0
- package/src/cli.ts +393 -32
- package/src/coord-map.test.ts +178 -0
- package/src/coord-map.ts +105 -0
- package/src/debug-point-image.test.ts +50 -0
- package/src/debug-point-image.ts +69 -0
- package/src/index.ts +3 -1
- package/src/lib.ts +125 -0
- package/src/native-click-smoke.test.ts +81 -63
- package/src/native-lib.ts +39 -1
- package/src/terminal-table.test.ts +44 -0
- package/src/terminal-table.ts +88 -0
- package/src/types.ts +50 -0
- package/zig/src/lib.zig +1258 -267
- package/zig/src/scroll.zig +213 -0
- package/zig/src/window.zig +123 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
// Cross-platform native scroll event helpers for the usecomputer Zig module.
|
|
2
|
+
|
|
3
|
+
const std = @import("std");
|
|
4
|
+
const builtin = @import("builtin");
|
|
5
|
+
|
|
6
|
+
const c_macos = if (builtin.target.os.tag == .macos) @cImport({
|
|
7
|
+
@cInclude("CoreGraphics/CoreGraphics.h");
|
|
8
|
+
@cInclude("CoreFoundation/CoreFoundation.h");
|
|
9
|
+
}) else struct {};
|
|
10
|
+
|
|
11
|
+
const c_windows = if (builtin.target.os.tag == .windows) @cImport({
|
|
12
|
+
@cInclude("windows.h");
|
|
13
|
+
}) else struct {};
|
|
14
|
+
|
|
15
|
+
const c_x11 = if (builtin.target.os.tag == .linux) @cImport({
|
|
16
|
+
@cInclude("X11/Xlib.h");
|
|
17
|
+
@cInclude("X11/extensions/XTest.h");
|
|
18
|
+
}) else struct {};
|
|
19
|
+
|
|
20
|
+
pub const ScrollArgs = struct {
|
|
21
|
+
direction: []const u8,
|
|
22
|
+
amount: f64,
|
|
23
|
+
at_x: ?f64 = null,
|
|
24
|
+
at_y: ?f64 = null,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const ScrollDirection = enum {
|
|
28
|
+
up,
|
|
29
|
+
down,
|
|
30
|
+
left,
|
|
31
|
+
right,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
pub fn scroll(args: ScrollArgs) !void {
|
|
35
|
+
const direction = try parseDirection(args.direction);
|
|
36
|
+
const steps = try normalizeAmount(args.amount);
|
|
37
|
+
|
|
38
|
+
switch (builtin.target.os.tag) {
|
|
39
|
+
.macos => {
|
|
40
|
+
try scrollMacos(.{ .direction = direction, .steps = steps, .at_x = args.at_x, .at_y = args.at_y });
|
|
41
|
+
},
|
|
42
|
+
.windows => {
|
|
43
|
+
try scrollWindows(.{ .direction = direction, .steps = steps, .at_x = args.at_x, .at_y = args.at_y });
|
|
44
|
+
},
|
|
45
|
+
.linux => {
|
|
46
|
+
try scrollX11(.{ .direction = direction, .steps = steps, .at_x = args.at_x, .at_y = args.at_y });
|
|
47
|
+
},
|
|
48
|
+
else => {
|
|
49
|
+
return error.UnsupportedPlatform;
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
fn parseDirection(direction: []const u8) !ScrollDirection {
|
|
55
|
+
if (std.ascii.eqlIgnoreCase(direction, "up")) {
|
|
56
|
+
return .up;
|
|
57
|
+
}
|
|
58
|
+
if (std.ascii.eqlIgnoreCase(direction, "down")) {
|
|
59
|
+
return .down;
|
|
60
|
+
}
|
|
61
|
+
if (std.ascii.eqlIgnoreCase(direction, "left")) {
|
|
62
|
+
return .left;
|
|
63
|
+
}
|
|
64
|
+
if (std.ascii.eqlIgnoreCase(direction, "right")) {
|
|
65
|
+
return .right;
|
|
66
|
+
}
|
|
67
|
+
return error.InvalidDirection;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
fn normalizeAmount(amount: f64) !i32 {
|
|
71
|
+
if (!std.math.isFinite(amount)) {
|
|
72
|
+
return error.InvalidAmount;
|
|
73
|
+
}
|
|
74
|
+
const rounded = @as(i64, @intFromFloat(std.math.round(amount)));
|
|
75
|
+
if (rounded <= 0) {
|
|
76
|
+
return error.InvalidAmount;
|
|
77
|
+
}
|
|
78
|
+
if (rounded > std.math.maxInt(i32)) {
|
|
79
|
+
return error.AmountTooLarge;
|
|
80
|
+
}
|
|
81
|
+
return @as(i32, @intCast(rounded));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
fn scrollMacos(args: struct {
|
|
85
|
+
direction: ScrollDirection,
|
|
86
|
+
steps: i32,
|
|
87
|
+
at_x: ?f64,
|
|
88
|
+
at_y: ?f64,
|
|
89
|
+
}) !void {
|
|
90
|
+
if (args.at_x != null and args.at_y != null) {
|
|
91
|
+
const point: c_macos.CGPoint = .{ .x = args.at_x.?, .y = args.at_y.? };
|
|
92
|
+
const warp_result = c_macos.CGWarpMouseCursorPosition(point);
|
|
93
|
+
if (warp_result != c_macos.kCGErrorSuccess) {
|
|
94
|
+
return error.CGWarpMouseFailed;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
var delta_y: i32 = 0;
|
|
99
|
+
var delta_x: i32 = 0;
|
|
100
|
+
switch (args.direction) {
|
|
101
|
+
.up => {
|
|
102
|
+
delta_y = args.steps;
|
|
103
|
+
},
|
|
104
|
+
.down => {
|
|
105
|
+
delta_y = -args.steps;
|
|
106
|
+
},
|
|
107
|
+
.left => {
|
|
108
|
+
delta_x = -args.steps;
|
|
109
|
+
},
|
|
110
|
+
.right => {
|
|
111
|
+
delta_x = args.steps;
|
|
112
|
+
},
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const event = c_macos.CGEventCreateScrollWheelEvent(
|
|
116
|
+
null,
|
|
117
|
+
c_macos.kCGScrollEventUnitLine,
|
|
118
|
+
2,
|
|
119
|
+
delta_y,
|
|
120
|
+
delta_x,
|
|
121
|
+
);
|
|
122
|
+
if (event == null) {
|
|
123
|
+
return error.CGEventCreateFailed;
|
|
124
|
+
}
|
|
125
|
+
defer c_macos.CFRelease(event);
|
|
126
|
+
|
|
127
|
+
if (args.at_x != null and args.at_y != null) {
|
|
128
|
+
const location: c_macos.CGPoint = .{ .x = args.at_x.?, .y = args.at_y.? };
|
|
129
|
+
c_macos.CGEventSetLocation(event, location);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
c_macos.CGEventPost(c_macos.kCGHIDEventTap, event);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
fn scrollWindows(args: struct {
|
|
136
|
+
direction: ScrollDirection,
|
|
137
|
+
steps: i32,
|
|
138
|
+
at_x: ?f64,
|
|
139
|
+
at_y: ?f64,
|
|
140
|
+
}) !void {
|
|
141
|
+
if (args.at_x != null and args.at_y != null) {
|
|
142
|
+
const x = @as(i64, @intFromFloat(std.math.round(args.at_x.?)));
|
|
143
|
+
const y = @as(i64, @intFromFloat(std.math.round(args.at_y.?)));
|
|
144
|
+
if (x < std.math.minInt(i32) or x > std.math.maxInt(i32) or y < std.math.minInt(i32) or y > std.math.maxInt(i32)) {
|
|
145
|
+
return error.InvalidPoint;
|
|
146
|
+
}
|
|
147
|
+
_ = c_windows.SetCursorPos(@as(c_int, @intCast(x)), @as(c_int, @intCast(y)));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
var flags: u32 = 0;
|
|
151
|
+
var delta: i32 = 0;
|
|
152
|
+
switch (args.direction) {
|
|
153
|
+
.up => {
|
|
154
|
+
flags = c_windows.MOUSEEVENTF_WHEEL;
|
|
155
|
+
delta = args.steps;
|
|
156
|
+
},
|
|
157
|
+
.down => {
|
|
158
|
+
flags = c_windows.MOUSEEVENTF_WHEEL;
|
|
159
|
+
delta = -args.steps;
|
|
160
|
+
},
|
|
161
|
+
.left => {
|
|
162
|
+
flags = c_windows.MOUSEEVENTF_HWHEEL;
|
|
163
|
+
delta = -args.steps;
|
|
164
|
+
},
|
|
165
|
+
.right => {
|
|
166
|
+
flags = c_windows.MOUSEEVENTF_HWHEEL;
|
|
167
|
+
delta = args.steps;
|
|
168
|
+
},
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
var event = std.mem.zeroes(c_windows.INPUT);
|
|
172
|
+
event.type = c_windows.INPUT_MOUSE;
|
|
173
|
+
event.Anonymous.mi.dwFlags = flags;
|
|
174
|
+
event.Anonymous.mi.mouseData = @as(c_uint, @intCast(delta * c_windows.WHEEL_DELTA));
|
|
175
|
+
const sent = c_windows.SendInput(1, &event, @sizeOf(c_windows.INPUT));
|
|
176
|
+
if (sent == 0) {
|
|
177
|
+
return error.EventPostFailed;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
fn scrollX11(args: struct {
|
|
182
|
+
direction: ScrollDirection,
|
|
183
|
+
steps: i32,
|
|
184
|
+
at_x: ?f64,
|
|
185
|
+
at_y: ?f64,
|
|
186
|
+
}) !void {
|
|
187
|
+
const display = c_x11.XOpenDisplay(null) orelse return error.XOpenDisplayFailed;
|
|
188
|
+
defer _ = c_x11.XCloseDisplay(display);
|
|
189
|
+
|
|
190
|
+
if (args.at_x != null and args.at_y != null) {
|
|
191
|
+
const x = @as(i64, @intFromFloat(std.math.round(args.at_x.?)));
|
|
192
|
+
const y = @as(i64, @intFromFloat(std.math.round(args.at_y.?)));
|
|
193
|
+
if (x < std.math.minInt(i32) or x > std.math.maxInt(i32) or y < std.math.minInt(i32) or y > std.math.maxInt(i32)) {
|
|
194
|
+
return error.InvalidPoint;
|
|
195
|
+
}
|
|
196
|
+
_ = c_x11.XWarpPointer(display, 0, c_x11.XDefaultRootWindow(display), 0, 0, 0, 0, @as(c_int, @intCast(x)), @as(c_int, @intCast(y)));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const button_code: c_uint = switch (args.direction) {
|
|
200
|
+
.up => 4,
|
|
201
|
+
.down => 5,
|
|
202
|
+
.left => 6,
|
|
203
|
+
.right => 7,
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const repeat_count: u32 = @as(u32, @intCast(args.steps));
|
|
207
|
+
var index: u32 = 0;
|
|
208
|
+
while (index < repeat_count) : (index += 1) {
|
|
209
|
+
_ = c_x11.XTestFakeButtonEvent(display, button_code, c_x11.True, c_x11.CurrentTime);
|
|
210
|
+
_ = c_x11.XTestFakeButtonEvent(display, button_code, c_x11.False, c_x11.CurrentTime);
|
|
211
|
+
}
|
|
212
|
+
_ = c_x11.XFlush(display);
|
|
213
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// Helpers for querying visible macOS windows via stable CoreGraphics APIs.
|
|
2
|
+
|
|
3
|
+
const std = @import("std");
|
|
4
|
+
const builtin = @import("builtin");
|
|
5
|
+
|
|
6
|
+
const c = if (builtin.target.os.tag == .macos) @cImport({
|
|
7
|
+
@cInclude("CoreGraphics/CoreGraphics.h");
|
|
8
|
+
@cInclude("CoreFoundation/CoreFoundation.h");
|
|
9
|
+
}) else struct {};
|
|
10
|
+
|
|
11
|
+
pub const Rect = struct {
|
|
12
|
+
x: f64,
|
|
13
|
+
y: f64,
|
|
14
|
+
width: f64,
|
|
15
|
+
height: f64,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
pub const WindowInfo = struct {
|
|
19
|
+
id: u32,
|
|
20
|
+
owner_pid: i32,
|
|
21
|
+
owner_name: []const u8,
|
|
22
|
+
title: []const u8,
|
|
23
|
+
bounds: Rect,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
pub fn forEachVisibleWindow(
|
|
27
|
+
comptime Context: type,
|
|
28
|
+
context: *Context,
|
|
29
|
+
callback: *const fn (ctx: *Context, info: WindowInfo) anyerror!void,
|
|
30
|
+
) !void {
|
|
31
|
+
if (builtin.target.os.tag != .macos) {
|
|
32
|
+
return error.UnsupportedPlatform;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const options = c.kCGWindowListOptionOnScreenOnly | c.kCGWindowListExcludeDesktopElements;
|
|
36
|
+
const windows = c.CGWindowListCopyWindowInfo(options, c.kCGNullWindowID);
|
|
37
|
+
if (windows == null) {
|
|
38
|
+
return error.WindowQueryFailed;
|
|
39
|
+
}
|
|
40
|
+
defer c.CFRelease(windows);
|
|
41
|
+
|
|
42
|
+
const count: usize = @intCast(c.CFArrayGetCount(windows));
|
|
43
|
+
var i: usize = 0;
|
|
44
|
+
while (i < count) : (i += 1) {
|
|
45
|
+
const value = c.CFArrayGetValueAtIndex(windows, @intCast(i));
|
|
46
|
+
if (value == null) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const dictionary: c.CFDictionaryRef = @ptrCast(value);
|
|
51
|
+
|
|
52
|
+
var id_raw: i64 = 0;
|
|
53
|
+
if (!readNumberI64(dictionary, c.kCGWindowNumber, &id_raw)) {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (id_raw <= 0) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
var owner_pid_raw: i64 = 0;
|
|
61
|
+
if (!readNumberI64(dictionary, c.kCGWindowOwnerPID, &owner_pid_raw)) {
|
|
62
|
+
owner_pid_raw = 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
var bounds: c.CGRect = undefined;
|
|
66
|
+
if (!readBoundsRect(dictionary, &bounds)) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
var owner_name_buffer: [256]u8 = undefined;
|
|
71
|
+
const owner_name = readString(dictionary, c.kCGWindowOwnerName, &owner_name_buffer);
|
|
72
|
+
var title_buffer: [256]u8 = undefined;
|
|
73
|
+
const title = readString(dictionary, c.kCGWindowName, &title_buffer);
|
|
74
|
+
|
|
75
|
+
try callback(context, .{
|
|
76
|
+
.id = @intCast(id_raw),
|
|
77
|
+
.owner_pid = @intCast(owner_pid_raw),
|
|
78
|
+
.owner_name = owner_name,
|
|
79
|
+
.title = title,
|
|
80
|
+
.bounds = .{
|
|
81
|
+
.x = std.math.round(bounds.origin.x),
|
|
82
|
+
.y = std.math.round(bounds.origin.y),
|
|
83
|
+
.width = std.math.round(bounds.size.width),
|
|
84
|
+
.height = std.math.round(bounds.size.height),
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
fn readNumberI64(dictionary: c.CFDictionaryRef, key: c.CFStringRef, out: *i64) bool {
|
|
91
|
+
const value = c.CFDictionaryGetValue(dictionary, key);
|
|
92
|
+
if (value == null) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
const number: c.CFNumberRef = @ptrCast(value);
|
|
96
|
+
return c.CFNumberGetValue(number, c.kCFNumberSInt64Type, out) != 0;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
fn readBoundsRect(dictionary: c.CFDictionaryRef, out: *c.CGRect) bool {
|
|
100
|
+
const value = c.CFDictionaryGetValue(dictionary, c.kCGWindowBounds);
|
|
101
|
+
if (value == null) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
const bounds_dictionary: c.CFDictionaryRef = @ptrCast(value);
|
|
105
|
+
return c.CGRectMakeWithDictionaryRepresentation(bounds_dictionary, out);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
fn readString(
|
|
109
|
+
dictionary: c.CFDictionaryRef,
|
|
110
|
+
key: c.CFStringRef,
|
|
111
|
+
buffer: *[256]u8,
|
|
112
|
+
) []const u8 {
|
|
113
|
+
const value = c.CFDictionaryGetValue(dictionary, key);
|
|
114
|
+
if (value == null) {
|
|
115
|
+
return "";
|
|
116
|
+
}
|
|
117
|
+
const str_ref: c.CFStringRef = @ptrCast(value);
|
|
118
|
+
if (c.CFStringGetCString(str_ref, buffer, buffer.len, c.kCFStringEncodingUTF8) == 0) {
|
|
119
|
+
return "";
|
|
120
|
+
}
|
|
121
|
+
const content = std.mem.sliceTo(buffer, 0);
|
|
122
|
+
return content;
|
|
123
|
+
}
|