usecomputer 0.1.2 → 0.1.3
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/CHANGELOG.md +37 -0
- package/README.md +33 -13
- package/bin.sh +49 -0
- package/build.zig +12 -0
- package/dist/darwin-arm64/usecomputer +0 -0
- package/dist/darwin-arm64/usecomputer.node +0 -0
- package/dist/darwin-x64/usecomputer +0 -0
- package/dist/darwin-x64/usecomputer.node +0 -0
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -3
- package/package.json +15 -23
- package/src/index.ts +1 -3
- package/zig/src/kitty-graphics.zig +151 -0
- package/zig/src/lib.zig +121 -0
- package/zig/src/main.zig +667 -47
- package/zig/src/table.zig +170 -0
- package/bin.js +0 -4
- package/dist/linux-x64/usecomputer.node +0 -0
- package/src/cli-parsing.test.ts +0 -61
- package/src/cli.ts +0 -648
- package/src/command-parsers.test.ts +0 -50
- package/src/command-parsers.ts +0 -60
- package/src/debug-point-image.test.ts +0 -50
- package/src/debug-point-image.ts +0 -69
- package/src/terminal-table.test.ts +0 -44
- package/src/terminal-table.ts +0 -88
package/zig/src/main.zig
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
const std = @import("std");
|
|
4
4
|
const zeke = @import("zeke");
|
|
5
5
|
const lib = @import("usecomputer_lib");
|
|
6
|
+
const table = @import("table.zig");
|
|
7
|
+
const kitty_graphics = @import("kitty-graphics.zig");
|
|
6
8
|
|
|
7
9
|
const File = std.fs.File;
|
|
8
10
|
const Writer = File.DeprecatedWriter;
|
|
@@ -15,6 +17,85 @@ fn getStderr() Writer {
|
|
|
15
17
|
return File.stderr().deprecatedWriter();
|
|
16
18
|
}
|
|
17
19
|
|
|
20
|
+
// ─── Coord-map ───
|
|
21
|
+
// Port of src/coord-map.ts — maps screenshot-space pixels to desktop coordinates.
|
|
22
|
+
|
|
23
|
+
const CoordMap = struct {
|
|
24
|
+
captureX: f64,
|
|
25
|
+
captureY: f64,
|
|
26
|
+
captureWidth: f64,
|
|
27
|
+
captureHeight: f64,
|
|
28
|
+
imageWidth: f64,
|
|
29
|
+
imageHeight: f64,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
fn parseCoordMap(s: []const u8) ?CoordMap {
|
|
33
|
+
var iter = std.mem.splitScalar(u8, s, ',');
|
|
34
|
+
const cx_str = iter.next() orelse return null;
|
|
35
|
+
const cy_str = iter.next() orelse return null;
|
|
36
|
+
const cw_str = iter.next() orelse return null;
|
|
37
|
+
const ch_str = iter.next() orelse return null;
|
|
38
|
+
const iw_str = iter.next() orelse return null;
|
|
39
|
+
const ih_str = iter.next() orelse return null;
|
|
40
|
+
const cx = std.fmt.parseFloat(f64, cx_str) catch return null;
|
|
41
|
+
const cy = std.fmt.parseFloat(f64, cy_str) catch return null;
|
|
42
|
+
const cw = std.fmt.parseFloat(f64, cw_str) catch return null;
|
|
43
|
+
const ch = std.fmt.parseFloat(f64, ch_str) catch return null;
|
|
44
|
+
const iw = std.fmt.parseFloat(f64, iw_str) catch return null;
|
|
45
|
+
const ih = std.fmt.parseFloat(f64, ih_str) catch return null;
|
|
46
|
+
if (cw <= 0 or ch <= 0 or iw <= 0 or ih <= 0) return null;
|
|
47
|
+
return .{
|
|
48
|
+
.captureX = cx,
|
|
49
|
+
.captureY = cy,
|
|
50
|
+
.captureWidth = cw,
|
|
51
|
+
.captureHeight = ch,
|
|
52
|
+
.imageWidth = iw,
|
|
53
|
+
.imageHeight = ih,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
fn mapPointFromCoordMap(point: lib.Point, cm: ?CoordMap) lib.Point {
|
|
58
|
+
const m = cm orelse return point;
|
|
59
|
+
const iw_span = @max(m.imageWidth - 1, 1);
|
|
60
|
+
const ih_span = @max(m.imageHeight - 1, 1);
|
|
61
|
+
const cw_span = @max(m.captureWidth - 1, 0);
|
|
62
|
+
const ch_span = @max(m.captureHeight - 1, 0);
|
|
63
|
+
const max_cx = m.captureX + cw_span;
|
|
64
|
+
const max_cy = m.captureY + ch_span;
|
|
65
|
+
const mapped_x = m.captureX + (point.x / iw_span) * cw_span;
|
|
66
|
+
const mapped_y = m.captureY + (point.y / ih_span) * ch_span;
|
|
67
|
+
return .{
|
|
68
|
+
.x = @round(std.math.clamp(mapped_x, m.captureX, max_cx)),
|
|
69
|
+
.y = @round(std.math.clamp(mapped_y, m.captureY, max_cy)),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
fn mapPointToCoordMap(point: lib.Point, cm: ?CoordMap) lib.Point {
|
|
74
|
+
const m = cm orelse return point;
|
|
75
|
+
const cw_span = @max(m.captureWidth - 1, 1);
|
|
76
|
+
const ch_span = @max(m.captureHeight - 1, 1);
|
|
77
|
+
const iw_span = @max(m.imageWidth - 1, 0);
|
|
78
|
+
const ih_span = @max(m.imageHeight - 1, 0);
|
|
79
|
+
const rel_x = (point.x - m.captureX) / cw_span;
|
|
80
|
+
const rel_y = (point.y - m.captureY) / ch_span;
|
|
81
|
+
const mapped_x = rel_x * iw_span;
|
|
82
|
+
const mapped_y = rel_y * ih_span;
|
|
83
|
+
return .{
|
|
84
|
+
.x = @round(std.math.clamp(mapped_x, 0, iw_span)),
|
|
85
|
+
.y = @round(std.math.clamp(mapped_y, 0, ih_span)),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
fn getRegionFromCoordMap(cm: ?CoordMap) ?lib.ScreenshotRegion {
|
|
90
|
+
const m = cm orelse return null;
|
|
91
|
+
return .{
|
|
92
|
+
.x = m.captureX,
|
|
93
|
+
.y = m.captureY,
|
|
94
|
+
.width = m.captureWidth,
|
|
95
|
+
.height = m.captureHeight,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
18
99
|
// ─── Helpers ───
|
|
19
100
|
|
|
20
101
|
fn parseF64(s: []const u8) ?f64 {
|
|
@@ -45,11 +126,11 @@ fn printError(result: anytype) void {
|
|
|
45
126
|
}
|
|
46
127
|
}
|
|
47
128
|
|
|
48
|
-
fn printScreenshotJson(data: lib.ScreenshotOutput) void {
|
|
129
|
+
fn printScreenshotJson(data: lib.ScreenshotOutput, agent_graphics: bool) void {
|
|
49
130
|
const stdout = getStdout();
|
|
50
131
|
stdout.print(
|
|
51
|
-
"{{\"path\":\"{s}\",\"desktopIndex\":{d:.0},\"captureX\":{d:.0},\"captureY\":{d:.0},\"captureWidth\":{d:.0},\"captureHeight\":{d:.0},\"imageWidth\":{d:.0},\"imageHeight\":{d:.0}}}\n",
|
|
52
|
-
.{ data.path, data.desktopIndex, data.captureX, data.captureY, data.captureWidth, data.captureHeight, data.imageWidth, data.imageHeight },
|
|
132
|
+
"{{\"path\":\"{s}\",\"desktopIndex\":{d:.0},\"captureX\":{d:.0},\"captureY\":{d:.0},\"captureWidth\":{d:.0},\"captureHeight\":{d:.0},\"imageWidth\":{d:.0},\"imageHeight\":{d:.0},\"agentGraphics\":{s}}}\n",
|
|
133
|
+
.{ data.path, data.desktopIndex, data.captureX, data.captureY, data.captureWidth, data.captureHeight, data.imageWidth, data.imageHeight, if (agent_graphics) "true" else "false" },
|
|
53
134
|
) catch {};
|
|
54
135
|
}
|
|
55
136
|
|
|
@@ -63,19 +144,26 @@ const Screenshot = zeke.cmd("screenshot [path]", "Take a screenshot")
|
|
|
63
144
|
.option("--json", "Output as JSON");
|
|
64
145
|
|
|
65
146
|
const Click = zeke.cmd("click [target]", "Click at coordinates or target")
|
|
66
|
-
.option("-x
|
|
67
|
-
.option("-y
|
|
147
|
+
.option("-x [x]", "X coordinate")
|
|
148
|
+
.option("-y [y]", "Y coordinate")
|
|
68
149
|
.option("--button [button]", "Mouse button: left, right, middle")
|
|
69
|
-
.option("--count [count]", "Click count")
|
|
150
|
+
.option("--count [count]", "Click count")
|
|
151
|
+
.option("--modifiers [modifiers]", "Modifiers as ctrl,shift,alt,meta")
|
|
152
|
+
.option("--coord-map [map]", "Map screenshot-space pixels to desktop coordinates");
|
|
70
153
|
|
|
71
154
|
const DebugPoint = zeke.cmd("debug-point [target]", "Validate click coordinates visually")
|
|
72
155
|
.option("-x [x]", "X coordinate")
|
|
73
156
|
.option("-y [y]", "Y coordinate")
|
|
157
|
+
.option("--coord-map [map]", "Map input coordinates from screenshot space")
|
|
74
158
|
.option("--output [path]", "Save annotated screenshot")
|
|
75
159
|
.option("--json", "Output as JSON");
|
|
76
160
|
|
|
77
161
|
const TypeText = zeke.cmd("type [text]", "Type text using keyboard")
|
|
78
|
-
.option("--
|
|
162
|
+
.option("--stdin", "Read text from stdin instead of [text] argument")
|
|
163
|
+
.option("--delay [ms]", "Delay between keystrokes in ms")
|
|
164
|
+
.option("--chunk-size [n]", "Split text into fixed-size chunks before typing")
|
|
165
|
+
.option("--chunk-delay [ms]", "Delay in milliseconds between chunks")
|
|
166
|
+
.option("--max-length [n]", "Fail when input text exceeds this maximum length");
|
|
79
167
|
|
|
80
168
|
const Press = zeke.cmd("press <key>", "Press a key or key combination")
|
|
81
169
|
.option("--count [n]", "Number of times to press")
|
|
@@ -86,15 +174,18 @@ const Scroll = zeke.cmd("scroll <direction> [amount]", "Scroll in a direction")
|
|
|
86
174
|
|
|
87
175
|
const Drag = zeke.cmd("drag <from> <to>", "Drag from one point to another")
|
|
88
176
|
.option("--duration [ms]", "Drag duration in ms")
|
|
89
|
-
.option("--button [button]", "Mouse button")
|
|
177
|
+
.option("--button [button]", "Mouse button")
|
|
178
|
+
.option("--coord-map [map]", "Map input coordinates from screenshot space");
|
|
90
179
|
|
|
91
|
-
const Hover = zeke.cmd("hover", "Move mouse without clicking")
|
|
92
|
-
.option("-x
|
|
93
|
-
.option("-y
|
|
180
|
+
const Hover = zeke.cmd("hover [target]", "Move mouse without clicking")
|
|
181
|
+
.option("-x [x]", "X coordinate")
|
|
182
|
+
.option("-y [y]", "Y coordinate")
|
|
183
|
+
.option("--coord-map [map]", "Map input coordinates from screenshot space");
|
|
94
184
|
|
|
95
185
|
const MouseMove = zeke.cmd("mouse move", "Move to absolute coordinates")
|
|
96
|
-
.option("-x
|
|
97
|
-
.option("-y
|
|
186
|
+
.option("-x [x]", "X coordinate")
|
|
187
|
+
.option("-y [y]", "Y coordinate")
|
|
188
|
+
.option("--coord-map [map]", "Map input coordinates from screenshot space");
|
|
98
189
|
|
|
99
190
|
const MouseDown = zeke.cmd("mouse down", "Press and hold mouse button")
|
|
100
191
|
.option("--button [button]", "Mouse button");
|
|
@@ -111,6 +202,10 @@ const DisplayList = zeke.cmd("display list", "List connected displays")
|
|
|
111
202
|
const WindowList = zeke.cmd("window list", "List open windows")
|
|
112
203
|
.option("--json", "Output as JSON");
|
|
113
204
|
|
|
205
|
+
const DesktopList = zeke.cmd("desktop list", "List desktops as display indexes and sizes")
|
|
206
|
+
.option("--windows", "Include available windows grouped by desktop index")
|
|
207
|
+
.option("--json", "Output as JSON");
|
|
208
|
+
|
|
114
209
|
const ClipboardGet = zeke.cmd("clipboard get", "Print clipboard text");
|
|
115
210
|
|
|
116
211
|
const ClipboardSet = zeke.cmd("clipboard set <text>", "Set clipboard text");
|
|
@@ -129,9 +224,18 @@ fn screenshotAction(args: Screenshot.Args, opts: Screenshot.Options) !void {
|
|
|
129
224
|
printError(result);
|
|
130
225
|
return error.CommandFailed;
|
|
131
226
|
}
|
|
227
|
+
|
|
228
|
+
// Attempt Kitty Graphics Protocol emission when AGENT_GRAPHICS=kitty is set.
|
|
229
|
+
// Track whether emission actually succeeded so JSON reports the real state
|
|
230
|
+
// (not just that it was requested).
|
|
231
|
+
const agent_graphics_emitted = if (kitty_graphics.canEmitAgentGraphics())
|
|
232
|
+
emitScreenshotKittyGraphics(result.data)
|
|
233
|
+
else
|
|
234
|
+
false;
|
|
235
|
+
|
|
132
236
|
if (opts.json) {
|
|
133
237
|
if (result.data) |data| {
|
|
134
|
-
printScreenshotJson(data);
|
|
238
|
+
printScreenshotJson(data, agent_graphics_emitted);
|
|
135
239
|
}
|
|
136
240
|
} else {
|
|
137
241
|
const stdout = getStdout();
|
|
@@ -140,14 +244,63 @@ fn screenshotAction(args: Screenshot.Args, opts: Screenshot.Options) !void {
|
|
|
140
244
|
data.path, data.imageWidth, data.imageHeight,
|
|
141
245
|
});
|
|
142
246
|
}
|
|
247
|
+
if (agent_graphics_emitted) {
|
|
248
|
+
try stdout.print("The screenshot image is in your context. No need to read the file.\n", .{});
|
|
249
|
+
}
|
|
143
250
|
}
|
|
144
251
|
}
|
|
145
252
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
253
|
+
/// Read a screenshot PNG and emit it as Kitty Graphics Protocol escape sequences.
|
|
254
|
+
/// Handles both absolute and relative paths. Returns true if emission succeeded.
|
|
255
|
+
fn emitScreenshotKittyGraphics(data: ?lib.ScreenshotOutput) bool {
|
|
256
|
+
const d = data orelse return false;
|
|
257
|
+
const path = d.path;
|
|
258
|
+
|
|
259
|
+
// Open file — handle absolute vs relative paths
|
|
260
|
+
const file = if (path.len > 0 and path[0] == '/')
|
|
261
|
+
std.fs.openFileAbsolute(path, .{})
|
|
262
|
+
else
|
|
263
|
+
std.fs.cwd().openFile(path, .{});
|
|
264
|
+
|
|
265
|
+
const f = file catch |err| {
|
|
266
|
+
const stderr = getStderr();
|
|
267
|
+
stderr.print("warning: could not open screenshot for kitty graphics: {}\n", .{err}) catch {};
|
|
268
|
+
return false;
|
|
269
|
+
};
|
|
270
|
+
defer f.close();
|
|
271
|
+
|
|
272
|
+
const bytes = f.readToEndAlloc(std.heap.page_allocator, 50 * 1024 * 1024) catch |err| {
|
|
273
|
+
const stderr = getStderr();
|
|
274
|
+
stderr.print("warning: could not read screenshot for kitty graphics: {}\n", .{err}) catch {};
|
|
275
|
+
return false;
|
|
276
|
+
};
|
|
277
|
+
defer std.heap.page_allocator.free(bytes);
|
|
278
|
+
|
|
279
|
+
if (bytes.len == 0) return false;
|
|
280
|
+
|
|
281
|
+
const stdout = getStdout();
|
|
282
|
+
kitty_graphics.emitKittyGraphics(bytes, stdout) catch |err| {
|
|
283
|
+
const stderr = getStderr();
|
|
284
|
+
stderr.print("warning: kitty graphics emission failed: {}\n", .{err}) catch {};
|
|
285
|
+
return false;
|
|
286
|
+
};
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
fn clickAction(args: Click.Args, opts: Click.Options) !void {
|
|
291
|
+
const raw_point = resolvePoint(args.target, opts.x, opts.y) orelse {
|
|
292
|
+
const stderr = getStderr();
|
|
293
|
+
try stderr.print("error: coordinates required (-x and -y, or positional x,y)\n", .{});
|
|
294
|
+
return error.InvalidCoordinate;
|
|
295
|
+
};
|
|
296
|
+
const cm = if (opts.coord_map) |s| (parseCoordMap(s) orelse {
|
|
297
|
+
const stderr = getStderr();
|
|
298
|
+
try stderr.print("error: invalid --coord-map, expected x,y,width,height,imageWidth,imageHeight\n", .{});
|
|
299
|
+
return error.CommandFailed;
|
|
300
|
+
}) else null;
|
|
301
|
+
const point = mapPointFromCoordMap(raw_point, cm);
|
|
149
302
|
const result = lib.click(.{
|
|
150
|
-
.point =
|
|
303
|
+
.point = point,
|
|
151
304
|
.button = opts.button,
|
|
152
305
|
.count = if (opts.count) |c| parseF64(c) else null,
|
|
153
306
|
});
|
|
@@ -157,25 +310,198 @@ fn clickAction(_: Click.Args, opts: Click.Options) !void {
|
|
|
157
310
|
}
|
|
158
311
|
}
|
|
159
312
|
|
|
160
|
-
fn debugPointAction(
|
|
313
|
+
fn debugPointAction(args: DebugPoint.Args, opts: DebugPoint.Options) !void {
|
|
161
314
|
const stderr = getStderr();
|
|
162
|
-
|
|
163
|
-
}
|
|
315
|
+
const stdout = getStdout();
|
|
164
316
|
|
|
165
|
-
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
return error.MissingArgument;
|
|
317
|
+
// Resolve input point
|
|
318
|
+
const input_point = resolvePoint(args.target, opts.x, opts.y) orelse {
|
|
319
|
+
try stderr.print("error: coordinates required (-x and -y, or positional x,y)\n", .{});
|
|
320
|
+
return error.InvalidCoordinate;
|
|
170
321
|
};
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
322
|
+
|
|
323
|
+
// Parse coord-map and compute desktop point
|
|
324
|
+
const cm = if (opts.coord_map) |s| parseCoordMap(s) else null;
|
|
325
|
+
const desktop_point = mapPointFromCoordMap(input_point, cm);
|
|
326
|
+
|
|
327
|
+
// Take screenshot (using coord-map region if provided)
|
|
328
|
+
const output_path = opts.output orelse "./tmp/debug-point.png";
|
|
329
|
+
const screenshot_result = lib.screenshot(.{
|
|
330
|
+
.path = output_path,
|
|
331
|
+
.region = getRegionFromCoordMap(cm),
|
|
174
332
|
});
|
|
175
|
-
if (!
|
|
176
|
-
printError(
|
|
333
|
+
if (!screenshot_result.ok) {
|
|
334
|
+
printError(screenshot_result);
|
|
177
335
|
return error.CommandFailed;
|
|
178
336
|
}
|
|
337
|
+
const data = screenshot_result.data orelse {
|
|
338
|
+
try stderr.print("error: screenshot returned no data\n", .{});
|
|
339
|
+
return error.CommandFailed;
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
// Compute screenshot-space point for the marker
|
|
343
|
+
const screenshot_cm = CoordMap{
|
|
344
|
+
.captureX = data.captureX,
|
|
345
|
+
.captureY = data.captureY,
|
|
346
|
+
.captureWidth = data.captureWidth,
|
|
347
|
+
.captureHeight = data.captureHeight,
|
|
348
|
+
.imageWidth = data.imageWidth,
|
|
349
|
+
.imageHeight = data.imageHeight,
|
|
350
|
+
};
|
|
351
|
+
const screenshot_point = mapPointToCoordMap(desktop_point, screenshot_cm);
|
|
352
|
+
|
|
353
|
+
// Draw marker on the screenshot
|
|
354
|
+
const draw_result = lib.drawMarkerOnPng(.{
|
|
355
|
+
.path = data.path,
|
|
356
|
+
.x = screenshot_point.x,
|
|
357
|
+
.y = screenshot_point.y,
|
|
358
|
+
.imageWidth = data.imageWidth,
|
|
359
|
+
.imageHeight = data.imageHeight,
|
|
360
|
+
});
|
|
361
|
+
if (!draw_result.ok) {
|
|
362
|
+
// Non-fatal: print warning but still output coordinates
|
|
363
|
+
try stderr.print("warning: could not draw marker on screenshot\n", .{});
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (opts.json) {
|
|
367
|
+
stdout.print(
|
|
368
|
+
"{{\"path\":\"{s}\",\"inputPoint\":{{\"x\":{d:.0},\"y\":{d:.0}}},\"desktopPoint\":{{\"x\":{d:.0},\"y\":{d:.0}}},\"screenshotPoint\":{{\"x\":{d:.0},\"y\":{d:.0}}}}}\n",
|
|
369
|
+
.{
|
|
370
|
+
data.path,
|
|
371
|
+
input_point.x,
|
|
372
|
+
input_point.y,
|
|
373
|
+
desktop_point.x,
|
|
374
|
+
desktop_point.y,
|
|
375
|
+
screenshot_point.x,
|
|
376
|
+
screenshot_point.y,
|
|
377
|
+
},
|
|
378
|
+
) catch {};
|
|
379
|
+
} else {
|
|
380
|
+
try stdout.print("{s}\n", .{data.path});
|
|
381
|
+
try stdout.print("input-point={d:.0},{d:.0}\n", .{ input_point.x, input_point.y });
|
|
382
|
+
try stdout.print("desktop-point={d:.0},{d:.0}\n", .{ desktop_point.x, desktop_point.y });
|
|
383
|
+
try stdout.print("screenshot-point={d:.0},{d:.0}\n", .{ screenshot_point.x, screenshot_point.y });
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
fn readAllStdin(allocator: std.mem.Allocator) ![]const u8 {
|
|
388
|
+
const stdin = std.fs.File.stdin();
|
|
389
|
+
var buf: [8192]u8 = undefined;
|
|
390
|
+
var list: std.ArrayListUnmanaged(u8) = .empty;
|
|
391
|
+
errdefer list.deinit(allocator);
|
|
392
|
+
while (true) {
|
|
393
|
+
const n = stdin.read(&buf) catch return error.StdinReadFailed;
|
|
394
|
+
if (n == 0) break;
|
|
395
|
+
list.appendSlice(allocator, buf[0..n]) catch return error.StdinReadFailed;
|
|
396
|
+
if (list.items.len > 10 * 1024 * 1024) return error.StdinReadFailed;
|
|
397
|
+
}
|
|
398
|
+
return list.toOwnedSlice(allocator) catch return error.StdinReadFailed;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
fn typeTextAction(args: TypeText.Args, opts: TypeText.Options) !void {
|
|
402
|
+
const stderr = getStderr();
|
|
403
|
+
const from_stdin = opts.stdin;
|
|
404
|
+
|
|
405
|
+
if (from_stdin and args.text != null) {
|
|
406
|
+
try stderr.print("error: use either [text] or --stdin, not both\n", .{});
|
|
407
|
+
return error.MissingArgument;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Get the text to type
|
|
411
|
+
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
412
|
+
defer _ = gpa.deinit();
|
|
413
|
+
const allocator = gpa.allocator();
|
|
414
|
+
|
|
415
|
+
const text: []const u8 = if (from_stdin)
|
|
416
|
+
readAllStdin(allocator) catch {
|
|
417
|
+
try stderr.print("error: failed to read from stdin\n", .{});
|
|
418
|
+
return error.StdinReadFailed;
|
|
419
|
+
}
|
|
420
|
+
else
|
|
421
|
+
args.text orelse {
|
|
422
|
+
try stderr.print("error: text argument or --stdin required\n", .{});
|
|
423
|
+
return error.MissingArgument;
|
|
424
|
+
};
|
|
425
|
+
defer if (from_stdin) allocator.free(text);
|
|
426
|
+
|
|
427
|
+
// Check max-length
|
|
428
|
+
if (opts.max_length) |ml_str| {
|
|
429
|
+
const max_len = parseF64(ml_str) orelse {
|
|
430
|
+
try stderr.print("error: --max-length must be a positive number\n", .{});
|
|
431
|
+
return error.CommandFailed;
|
|
432
|
+
};
|
|
433
|
+
if (max_len <= 0) {
|
|
434
|
+
try stderr.print("error: --max-length must be a positive number\n", .{});
|
|
435
|
+
return error.CommandFailed;
|
|
436
|
+
}
|
|
437
|
+
if (@as(f64, @floatFromInt(text.len)) > max_len) {
|
|
438
|
+
try stderr.print("error: input text length {d} exceeds --max-length {d:.0}\n", .{ text.len, max_len });
|
|
439
|
+
return error.CommandFailed;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Determine chunk size
|
|
444
|
+
const chunk_size: ?usize = if (opts.chunk_size) |cs_str| blk: {
|
|
445
|
+
const cs = parseF64(cs_str) orelse {
|
|
446
|
+
try stderr.print("error: --chunk-size must be a positive number\n", .{});
|
|
447
|
+
return error.CommandFailed;
|
|
448
|
+
};
|
|
449
|
+
if (cs <= 0) {
|
|
450
|
+
try stderr.print("error: --chunk-size must be a positive number\n", .{});
|
|
451
|
+
return error.CommandFailed;
|
|
452
|
+
}
|
|
453
|
+
break :blk @as(usize, @intFromFloat(cs));
|
|
454
|
+
} else null;
|
|
455
|
+
|
|
456
|
+
const chunk_delay_ns: ?u64 = if (opts.chunk_delay) |cd_str| blk: {
|
|
457
|
+
const cd = parseF64(cd_str) orelse {
|
|
458
|
+
try stderr.print("error: --chunk-delay must be a positive number\n", .{});
|
|
459
|
+
return error.CommandFailed;
|
|
460
|
+
};
|
|
461
|
+
if (cd < 0) {
|
|
462
|
+
try stderr.print("error: --chunk-delay must be a non-negative number\n", .{});
|
|
463
|
+
return error.CommandFailed;
|
|
464
|
+
}
|
|
465
|
+
break :blk @as(u64, @intFromFloat(cd * 1_000_000));
|
|
466
|
+
} else null;
|
|
467
|
+
|
|
468
|
+
if (chunk_size) |cs| {
|
|
469
|
+
// Type in chunks (split on UTF-8 boundaries to avoid breaking codepoints)
|
|
470
|
+
var offset: usize = 0;
|
|
471
|
+
while (offset < text.len) {
|
|
472
|
+
var end = @min(offset + cs, text.len);
|
|
473
|
+
// Walk back to a UTF-8 character boundary if we split mid-codepoint
|
|
474
|
+
while (end < text.len and end > offset and (text[end] & 0xC0) == 0x80) {
|
|
475
|
+
end -= 1;
|
|
476
|
+
}
|
|
477
|
+
if (end == offset) end = @min(offset + cs, text.len); // fallback if all continuation bytes
|
|
478
|
+
const chunk = text[offset..end];
|
|
479
|
+
const result = lib.typeText(.{
|
|
480
|
+
.text = chunk,
|
|
481
|
+
.delayMs = if (opts.delay) |d| parseF64(d) else null,
|
|
482
|
+
});
|
|
483
|
+
if (!result.ok) {
|
|
484
|
+
printError(result);
|
|
485
|
+
return error.CommandFailed;
|
|
486
|
+
}
|
|
487
|
+
offset = end;
|
|
488
|
+
if (offset < text.len) {
|
|
489
|
+
if (chunk_delay_ns) |delay| {
|
|
490
|
+
std.Thread.sleep(delay);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
} else {
|
|
495
|
+
// Type all at once
|
|
496
|
+
const result = lib.typeText(.{
|
|
497
|
+
.text = text,
|
|
498
|
+
.delayMs = if (opts.delay) |d| parseF64(d) else null,
|
|
499
|
+
});
|
|
500
|
+
if (!result.ok) {
|
|
501
|
+
printError(result);
|
|
502
|
+
return error.CommandFailed;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
179
505
|
}
|
|
180
506
|
|
|
181
507
|
fn pressAction(args: Press.Args, opts: Press.Options) !void {
|
|
@@ -215,11 +541,12 @@ fn scrollAction(args: Scroll.Args, opts: Scroll.Options) !void {
|
|
|
215
541
|
|
|
216
542
|
fn dragAction(args: Drag.Args, opts: Drag.Options) !void {
|
|
217
543
|
// Parse "x,y" format for from and to
|
|
218
|
-
const
|
|
219
|
-
const
|
|
544
|
+
const from_raw = parsePointArg(args.from) orelse return error.InvalidCoordinate;
|
|
545
|
+
const to_raw = parsePointArg(args.to) orelse return error.InvalidCoordinate;
|
|
546
|
+
const cm = if (opts.coord_map) |s| parseCoordMap(s) else null;
|
|
220
547
|
const result = lib.drag(.{
|
|
221
|
-
.from =
|
|
222
|
-
.to =
|
|
548
|
+
.from = mapPointFromCoordMap(from_raw, cm),
|
|
549
|
+
.to = mapPointFromCoordMap(to_raw, cm),
|
|
223
550
|
.durationMs = if (opts.duration) |d| parseF64(d) else null,
|
|
224
551
|
.button = opts.button,
|
|
225
552
|
});
|
|
@@ -229,6 +556,18 @@ fn dragAction(args: Drag.Args, opts: Drag.Options) !void {
|
|
|
229
556
|
}
|
|
230
557
|
}
|
|
231
558
|
|
|
559
|
+
fn resolvePoint(target: ?[]const u8, opt_x: ?[]const u8, opt_y: ?[]const u8) ?lib.Point {
|
|
560
|
+
if (opt_x) |x_str| {
|
|
561
|
+
if (opt_y) |y_str| {
|
|
562
|
+
const x = parseF64(x_str) orelse return null;
|
|
563
|
+
const y = parseF64(y_str) orelse return null;
|
|
564
|
+
return .{ .x = x, .y = y };
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
if (target) |t| return parsePointArg(t);
|
|
568
|
+
return null;
|
|
569
|
+
}
|
|
570
|
+
|
|
232
571
|
fn parsePointArg(s: []const u8) ?lib.Point {
|
|
233
572
|
var iter = std.mem.splitScalar(u8, s, ',');
|
|
234
573
|
const x_str = iter.next() orelse return null;
|
|
@@ -239,10 +578,10 @@ fn parsePointArg(s: []const u8) ?lib.Point {
|
|
|
239
578
|
};
|
|
240
579
|
}
|
|
241
580
|
|
|
242
|
-
fn hoverAction(
|
|
243
|
-
const
|
|
244
|
-
const
|
|
245
|
-
const result = lib.hover(
|
|
581
|
+
fn hoverAction(args: Hover.Args, opts: Hover.Options) !void {
|
|
582
|
+
const point = resolvePoint(args.target, opts.x, opts.y) orelse return error.InvalidCoordinate;
|
|
583
|
+
const cm = if (opts.coord_map) |s| parseCoordMap(s) else null;
|
|
584
|
+
const result = lib.hover(mapPointFromCoordMap(point, cm));
|
|
246
585
|
if (!result.ok) {
|
|
247
586
|
printError(result);
|
|
248
587
|
return error.CommandFailed;
|
|
@@ -250,9 +589,13 @@ fn hoverAction(_: Hover.Args, opts: Hover.Options) !void {
|
|
|
250
589
|
}
|
|
251
590
|
|
|
252
591
|
fn mouseMoveAction(_: MouseMove.Args, opts: MouseMove.Options) !void {
|
|
253
|
-
const
|
|
254
|
-
const
|
|
255
|
-
const
|
|
592
|
+
const x_str = opts.x orelse return error.InvalidCoordinate;
|
|
593
|
+
const y_str = opts.y orelse return error.InvalidCoordinate;
|
|
594
|
+
const x = parseF64(x_str) orelse return error.InvalidCoordinate;
|
|
595
|
+
const y = parseF64(y_str) orelse return error.InvalidCoordinate;
|
|
596
|
+
const cm = if (opts.coord_map) |s| parseCoordMap(s) else null;
|
|
597
|
+
const point = mapPointFromCoordMap(.{ .x = x, .y = y }, cm);
|
|
598
|
+
const result = lib.mouseMove(point);
|
|
256
599
|
if (!result.ok) {
|
|
257
600
|
printError(result);
|
|
258
601
|
return error.CommandFailed;
|
|
@@ -291,6 +634,228 @@ fn mousePositionAction(_: MousePosition.Args, opts: MousePosition.Options) !void
|
|
|
291
634
|
}
|
|
292
635
|
}
|
|
293
636
|
|
|
637
|
+
// ─── Table rendering for list commands ───
|
|
638
|
+
|
|
639
|
+
fn jsonStr(value: std.json.Value) []const u8 {
|
|
640
|
+
return switch (value) {
|
|
641
|
+
.string => |s| s,
|
|
642
|
+
else => "",
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
fn jsonIntAlloc(allocator: std.mem.Allocator, value: std.json.Value) ![]u8 {
|
|
647
|
+
return switch (value) {
|
|
648
|
+
.integer => |n| try std.fmt.allocPrint(allocator, "{d}", .{n}),
|
|
649
|
+
.float => |f| try std.fmt.allocPrint(allocator, "{d:.0}", .{f}),
|
|
650
|
+
else => try allocator.dupe(u8, "?"),
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
fn jsonBool(value: std.json.Value) []const u8 {
|
|
655
|
+
return switch (value) {
|
|
656
|
+
.bool => |b| if (b) "yes" else "no",
|
|
657
|
+
else => "no",
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
fn printDisplayTable(allocator: std.mem.Allocator, json_data: []const u8) !void {
|
|
662
|
+
const stdout = getStdout();
|
|
663
|
+
const parsed = std.json.parseFromSlice(std.json.Value, allocator, json_data, .{}) catch {
|
|
664
|
+
try stdout.print("{s}\n", .{json_data});
|
|
665
|
+
return;
|
|
666
|
+
};
|
|
667
|
+
defer parsed.deinit();
|
|
668
|
+
|
|
669
|
+
const items = switch (parsed.value) {
|
|
670
|
+
.array => |a| a.items,
|
|
671
|
+
else => {
|
|
672
|
+
try stdout.print("{s}\n", .{json_data});
|
|
673
|
+
return;
|
|
674
|
+
},
|
|
675
|
+
};
|
|
676
|
+
|
|
677
|
+
if (items.len == 0) {
|
|
678
|
+
try stdout.print("no displays\n", .{});
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
const columns = &[_]table.Column{
|
|
683
|
+
.{ .header = "desktop" },
|
|
684
|
+
.{ .header = "primary" },
|
|
685
|
+
.{ .header = "size", .alignment = .right },
|
|
686
|
+
.{ .header = "position", .alignment = .right },
|
|
687
|
+
.{ .header = "id", .alignment = .right },
|
|
688
|
+
.{ .header = "scale", .alignment = .right },
|
|
689
|
+
.{ .header = "name" },
|
|
690
|
+
};
|
|
691
|
+
|
|
692
|
+
// Build rows — each row is an array of cell strings
|
|
693
|
+
var rows = std.ArrayListUnmanaged([]const []const u8).empty;
|
|
694
|
+
defer {
|
|
695
|
+
for (rows.items) |row| allocator.free(row);
|
|
696
|
+
rows.deinit(allocator);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// Buffers for formatted strings that outlive the loop iteration
|
|
700
|
+
var string_bufs = std.ArrayListUnmanaged([]u8).empty;
|
|
701
|
+
defer {
|
|
702
|
+
for (string_bufs.items) |buf| allocator.free(buf);
|
|
703
|
+
string_bufs.deinit(allocator);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
for (items) |item| {
|
|
707
|
+
const obj = switch (item) {
|
|
708
|
+
.object => |o| o,
|
|
709
|
+
else => continue,
|
|
710
|
+
};
|
|
711
|
+
|
|
712
|
+
const index_str = try jsonIntAlloc(allocator, obj.get("index") orelse continue);
|
|
713
|
+
try string_bufs.append(allocator, index_str);
|
|
714
|
+
const desktop_str = try std.fmt.allocPrint(allocator, "#{s}", .{index_str});
|
|
715
|
+
try string_bufs.append(allocator, desktop_str);
|
|
716
|
+
|
|
717
|
+
const w_str = try jsonIntAlloc(allocator, obj.get("width") orelse continue);
|
|
718
|
+
try string_bufs.append(allocator, w_str);
|
|
719
|
+
const h_str = try jsonIntAlloc(allocator, obj.get("height") orelse continue);
|
|
720
|
+
try string_bufs.append(allocator, h_str);
|
|
721
|
+
const size_str = try std.fmt.allocPrint(allocator, "{s}x{s}", .{ w_str, h_str });
|
|
722
|
+
try string_bufs.append(allocator, size_str);
|
|
723
|
+
|
|
724
|
+
const x_str = try jsonIntAlloc(allocator, obj.get("x") orelse continue);
|
|
725
|
+
try string_bufs.append(allocator, x_str);
|
|
726
|
+
const y_str = try jsonIntAlloc(allocator, obj.get("y") orelse continue);
|
|
727
|
+
try string_bufs.append(allocator, y_str);
|
|
728
|
+
const pos_str = try std.fmt.allocPrint(allocator, "{s},{s}", .{ x_str, y_str });
|
|
729
|
+
try string_bufs.append(allocator, pos_str);
|
|
730
|
+
|
|
731
|
+
const id_str = try jsonIntAlloc(allocator, obj.get("id") orelse continue);
|
|
732
|
+
try string_bufs.append(allocator, id_str);
|
|
733
|
+
|
|
734
|
+
const scale_str = try jsonIntAlloc(allocator, obj.get("scale") orelse continue);
|
|
735
|
+
try string_bufs.append(allocator, scale_str);
|
|
736
|
+
|
|
737
|
+
const name_val = obj.get("name") orelse continue;
|
|
738
|
+
|
|
739
|
+
const row = try allocator.alloc([]const u8, 7);
|
|
740
|
+
row[0] = desktop_str;
|
|
741
|
+
row[1] = jsonBool(obj.get("isPrimary") orelse .{ .bool = false });
|
|
742
|
+
row[2] = size_str;
|
|
743
|
+
row[3] = pos_str;
|
|
744
|
+
row[4] = id_str;
|
|
745
|
+
row[5] = scale_str;
|
|
746
|
+
row[6] = jsonStr(name_val);
|
|
747
|
+
try rows.append(allocator, row);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
const lines = try table.render(allocator, columns, rows.items);
|
|
751
|
+
defer {
|
|
752
|
+
for (lines) |line| allocator.free(line);
|
|
753
|
+
allocator.free(lines);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
for (lines) |line| {
|
|
757
|
+
try stdout.print("{s}\n", .{line});
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
fn printWindowTable(allocator: std.mem.Allocator, json_data: []const u8) !void {
|
|
762
|
+
const stdout = getStdout();
|
|
763
|
+
const parsed = std.json.parseFromSlice(std.json.Value, allocator, json_data, .{}) catch {
|
|
764
|
+
try stdout.print("{s}\n", .{json_data});
|
|
765
|
+
return;
|
|
766
|
+
};
|
|
767
|
+
defer parsed.deinit();
|
|
768
|
+
|
|
769
|
+
const items = switch (parsed.value) {
|
|
770
|
+
.array => |a| a.items,
|
|
771
|
+
else => {
|
|
772
|
+
try stdout.print("{s}\n", .{json_data});
|
|
773
|
+
return;
|
|
774
|
+
},
|
|
775
|
+
};
|
|
776
|
+
|
|
777
|
+
if (items.len == 0) {
|
|
778
|
+
try stdout.print("no windows\n", .{});
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
const columns = &[_]table.Column{
|
|
783
|
+
.{ .header = "id", .alignment = .right },
|
|
784
|
+
.{ .header = "desktop", .alignment = .right },
|
|
785
|
+
.{ .header = "app" },
|
|
786
|
+
.{ .header = "pid", .alignment = .right },
|
|
787
|
+
.{ .header = "size", .alignment = .right },
|
|
788
|
+
.{ .header = "position", .alignment = .right },
|
|
789
|
+
.{ .header = "title" },
|
|
790
|
+
};
|
|
791
|
+
|
|
792
|
+
var rows = std.ArrayListUnmanaged([]const []const u8).empty;
|
|
793
|
+
defer {
|
|
794
|
+
for (rows.items) |row| allocator.free(row);
|
|
795
|
+
rows.deinit(allocator);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
var string_bufs = std.ArrayListUnmanaged([]u8).empty;
|
|
799
|
+
defer {
|
|
800
|
+
for (string_bufs.items) |buf| allocator.free(buf);
|
|
801
|
+
string_bufs.deinit(allocator);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
for (items) |item| {
|
|
805
|
+
const obj = switch (item) {
|
|
806
|
+
.object => |o| o,
|
|
807
|
+
else => continue,
|
|
808
|
+
};
|
|
809
|
+
|
|
810
|
+
const id_str = try jsonIntAlloc(allocator, obj.get("id") orelse continue);
|
|
811
|
+
try string_bufs.append(allocator, id_str);
|
|
812
|
+
|
|
813
|
+
const di_str = try jsonIntAlloc(allocator, obj.get("desktopIndex") orelse continue);
|
|
814
|
+
try string_bufs.append(allocator, di_str);
|
|
815
|
+
const desktop_str = try std.fmt.allocPrint(allocator, "#{s}", .{di_str});
|
|
816
|
+
try string_bufs.append(allocator, desktop_str);
|
|
817
|
+
|
|
818
|
+
const pid_str = try jsonIntAlloc(allocator, obj.get("ownerPid") orelse continue);
|
|
819
|
+
try string_bufs.append(allocator, pid_str);
|
|
820
|
+
|
|
821
|
+
const w_str = try jsonIntAlloc(allocator, obj.get("width") orelse continue);
|
|
822
|
+
try string_bufs.append(allocator, w_str);
|
|
823
|
+
const h_str = try jsonIntAlloc(allocator, obj.get("height") orelse continue);
|
|
824
|
+
try string_bufs.append(allocator, h_str);
|
|
825
|
+
const size_str = try std.fmt.allocPrint(allocator, "{s}x{s}", .{ w_str, h_str });
|
|
826
|
+
try string_bufs.append(allocator, size_str);
|
|
827
|
+
|
|
828
|
+
const x_str = try jsonIntAlloc(allocator, obj.get("x") orelse continue);
|
|
829
|
+
try string_bufs.append(allocator, x_str);
|
|
830
|
+
const y_str = try jsonIntAlloc(allocator, obj.get("y") orelse continue);
|
|
831
|
+
try string_bufs.append(allocator, y_str);
|
|
832
|
+
const pos_str = try std.fmt.allocPrint(allocator, "{s},{s}", .{ x_str, y_str });
|
|
833
|
+
try string_bufs.append(allocator, pos_str);
|
|
834
|
+
|
|
835
|
+
const row = try allocator.alloc([]const u8, 7);
|
|
836
|
+
row[0] = id_str;
|
|
837
|
+
row[1] = desktop_str;
|
|
838
|
+
row[2] = jsonStr(obj.get("ownerName") orelse .{ .string = "" });
|
|
839
|
+
row[3] = pid_str;
|
|
840
|
+
row[4] = size_str;
|
|
841
|
+
row[5] = pos_str;
|
|
842
|
+
row[6] = jsonStr(obj.get("title") orelse .{ .string = "" });
|
|
843
|
+
try rows.append(allocator, row);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
const lines = try table.render(allocator, columns, rows.items);
|
|
847
|
+
defer {
|
|
848
|
+
for (lines) |line| allocator.free(line);
|
|
849
|
+
allocator.free(lines);
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
for (lines) |line| {
|
|
853
|
+
try stdout.print("{s}\n", .{line});
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// ─── List command actions ───
|
|
858
|
+
|
|
294
859
|
fn displayListAction(_: DisplayList.Args, opts: DisplayList.Options) !void {
|
|
295
860
|
const result = lib.displayList();
|
|
296
861
|
if (!result.ok) {
|
|
@@ -298,11 +863,16 @@ fn displayListAction(_: DisplayList.Args, opts: DisplayList.Options) !void {
|
|
|
298
863
|
return error.CommandFailed;
|
|
299
864
|
}
|
|
300
865
|
if (result.data) |data| {
|
|
301
|
-
const stdout = getStdout();
|
|
302
866
|
if (opts.json) {
|
|
867
|
+
const stdout = getStdout();
|
|
303
868
|
try stdout.print("{s}\n", .{data});
|
|
304
869
|
} else {
|
|
305
|
-
|
|
870
|
+
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
871
|
+
defer _ = gpa.deinit();
|
|
872
|
+
printDisplayTable(gpa.allocator(), data) catch {
|
|
873
|
+
const stdout = getStdout();
|
|
874
|
+
try stdout.print("{s}\n", .{data});
|
|
875
|
+
};
|
|
306
876
|
}
|
|
307
877
|
}
|
|
308
878
|
}
|
|
@@ -314,11 +884,60 @@ fn windowListAction(_: WindowList.Args, opts: WindowList.Options) !void {
|
|
|
314
884
|
return error.CommandFailed;
|
|
315
885
|
}
|
|
316
886
|
if (result.data) |data| {
|
|
317
|
-
const stdout = getStdout();
|
|
318
887
|
if (opts.json) {
|
|
888
|
+
const stdout = getStdout();
|
|
319
889
|
try stdout.print("{s}\n", .{data});
|
|
320
890
|
} else {
|
|
321
|
-
|
|
891
|
+
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
892
|
+
defer _ = gpa.deinit();
|
|
893
|
+
printWindowTable(gpa.allocator(), data) catch {
|
|
894
|
+
const stdout = getStdout();
|
|
895
|
+
try stdout.print("{s}\n", .{data});
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
fn desktopListAction(_: DesktopList.Args, opts: DesktopList.Options) !void {
|
|
902
|
+
const display_result = lib.displayList();
|
|
903
|
+
if (!display_result.ok) {
|
|
904
|
+
printError(display_result);
|
|
905
|
+
return error.CommandFailed;
|
|
906
|
+
}
|
|
907
|
+
const stdout = getStdout();
|
|
908
|
+
|
|
909
|
+
if (opts.windows) {
|
|
910
|
+
const window_result = lib.windowList();
|
|
911
|
+
if (!window_result.ok) {
|
|
912
|
+
printError(window_result);
|
|
913
|
+
return error.CommandFailed;
|
|
914
|
+
}
|
|
915
|
+
if (opts.json) {
|
|
916
|
+
try stdout.print("{{\"displays\":{s},\"windows\":{s}}}\n", .{
|
|
917
|
+
if (display_result.data) |d| d else "[]",
|
|
918
|
+
if (window_result.data) |w| w else "[]",
|
|
919
|
+
});
|
|
920
|
+
} else {
|
|
921
|
+
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
922
|
+
defer _ = gpa.deinit();
|
|
923
|
+
const allocator = gpa.allocator();
|
|
924
|
+
if (display_result.data) |d| {
|
|
925
|
+
printDisplayTable(allocator, d) catch try stdout.print("{s}\n", .{d});
|
|
926
|
+
}
|
|
927
|
+
try stdout.print("\n", .{});
|
|
928
|
+
if (window_result.data) |w| {
|
|
929
|
+
printWindowTable(allocator, w) catch try stdout.print("{s}\n", .{w});
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
} else {
|
|
933
|
+
if (opts.json) {
|
|
934
|
+
if (display_result.data) |d| try stdout.print("{s}\n", .{d});
|
|
935
|
+
} else {
|
|
936
|
+
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
937
|
+
defer _ = gpa.deinit();
|
|
938
|
+
if (display_result.data) |d| {
|
|
939
|
+
printDisplayTable(gpa.allocator(), d) catch try stdout.print("{s}\n", .{d});
|
|
940
|
+
}
|
|
322
941
|
}
|
|
323
942
|
}
|
|
324
943
|
}
|
|
@@ -363,12 +982,13 @@ pub fn main() !void {
|
|
|
363
982
|
MouseUp.bind(mouseUpAction),
|
|
364
983
|
MousePosition.bind(mousePositionAction),
|
|
365
984
|
DisplayList.bind(displayListAction),
|
|
985
|
+
DesktopList.bind(desktopListAction),
|
|
366
986
|
WindowList.bind(windowListAction),
|
|
367
987
|
ClipboardGet.bind(clipboardGetAction),
|
|
368
988
|
ClipboardSet.bind(clipboardSetAction),
|
|
369
989
|
}).init(gpa.allocator(), "usecomputer");
|
|
370
990
|
|
|
371
|
-
app.setVersion("0.
|
|
991
|
+
app.setVersion("0.1.2");
|
|
372
992
|
app.run() catch |err| {
|
|
373
993
|
switch (err) {
|
|
374
994
|
error.CommandFailed, error.InvalidCoordinate, error.MissingArgument => {},
|