@archastro/movie-harness 0.1.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.
- package/README.md +138 -0
- package/bin/astroshot-movie.mjs +5 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +388 -0
- package/dist/encode.d.ts +14 -0
- package/dist/encode.js +184 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +11 -0
- package/dist/paths.d.ts +9 -0
- package/dist/paths.js +57 -0
- package/dist/png.d.ts +8 -0
- package/dist/png.js +79 -0
- package/dist/session.d.ts +45 -0
- package/dist/session.js +170 -0
- package/dist/sink.d.ts +7 -0
- package/dist/sink.js +113 -0
- package/dist/source-help.d.ts +26 -0
- package/dist/source-help.js +213 -0
- package/dist/sources/browser.d.ts +7 -0
- package/dist/sources/browser.js +87 -0
- package/dist/sources/desktop-macos.d.ts +88 -0
- package/dist/sources/desktop-macos.js +380 -0
- package/dist/sources/frames-store.d.ts +19 -0
- package/dist/sources/frames-store.js +165 -0
- package/dist/sources/pty.d.ts +15 -0
- package/dist/sources/pty.js +330 -0
- package/dist/terminal-paint.d.ts +27 -0
- package/dist/terminal-paint.js +190 -0
- package/dist/types.d.ts +140 -0
- package/dist/types.js +1 -0
- package/native/macos/WindowTools.swift +150 -0
- package/package.json +67 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Window inventory + Screen Recording TCC helpers for desktop.window.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* swift WindowTools.swift list
|
|
6
|
+
* swift WindowTools.swift screen-access # preflight only (JSON)
|
|
7
|
+
* swift WindowTools.swift screen-access --request # may show system prompt
|
|
8
|
+
*/
|
|
9
|
+
import AppKit
|
|
10
|
+
import CoreGraphics
|
|
11
|
+
import Foundation
|
|
12
|
+
|
|
13
|
+
struct WindowRow: Encodable {
|
|
14
|
+
let id: Int
|
|
15
|
+
let pid: Int
|
|
16
|
+
let owner: String
|
|
17
|
+
let title: String
|
|
18
|
+
let bundleId: String?
|
|
19
|
+
let width: Int
|
|
20
|
+
let height: Int
|
|
21
|
+
let x: Int
|
|
22
|
+
let y: Int
|
|
23
|
+
let onScreen: Bool
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
struct ScreenAccessReport: Encodable {
|
|
27
|
+
let granted: Bool
|
|
28
|
+
/// True when we invoked CGRequestScreenCaptureAccess (may have shown a prompt).
|
|
29
|
+
let requested: Bool
|
|
30
|
+
let hostApp: String
|
|
31
|
+
let hostBundleId: String?
|
|
32
|
+
let settingsHint: String
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
func listWindows() -> [WindowRow] {
|
|
36
|
+
// Include off-screen windows so agents can still target them.
|
|
37
|
+
let opts = CGWindowListOption(arrayLiteral: .excludeDesktopElements)
|
|
38
|
+
guard let info = CGWindowListCopyWindowInfo(opts, kCGNullWindowID) as? [[String: Any]]
|
|
39
|
+
else {
|
|
40
|
+
return []
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
var bundleByPid: [Int32: String] = [:]
|
|
44
|
+
for app in NSWorkspace.shared.runningApplications {
|
|
45
|
+
if let bid = app.bundleIdentifier {
|
|
46
|
+
bundleByPid[app.processIdentifier] = bid
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
var rows: [WindowRow] = []
|
|
51
|
+
for w in info {
|
|
52
|
+
let layer = w[kCGWindowLayer as String] as? Int ?? -1
|
|
53
|
+
guard layer == 0 else { continue }
|
|
54
|
+
|
|
55
|
+
let id = w[kCGWindowNumber as String] as? Int ?? 0
|
|
56
|
+
let pid = w[kCGWindowOwnerPID as String] as? Int ?? 0
|
|
57
|
+
let owner = w[kCGWindowOwnerName as String] as? String ?? ""
|
|
58
|
+
let title = w[kCGWindowName as String] as? String ?? ""
|
|
59
|
+
let onScreen = w[kCGWindowIsOnscreen as String] as? Bool ?? false
|
|
60
|
+
let bounds = w[kCGWindowBounds as String] as? [String: Any]
|
|
61
|
+
let width = (bounds?["Width"] as? NSNumber)?.intValue ?? 0
|
|
62
|
+
let height = (bounds?["Height"] as? NSNumber)?.intValue ?? 0
|
|
63
|
+
let x = (bounds?["X"] as? NSNumber)?.intValue ?? 0
|
|
64
|
+
let y = (bounds?["Y"] as? NSNumber)?.intValue ?? 0
|
|
65
|
+
if width < 2 || height < 2 { continue }
|
|
66
|
+
|
|
67
|
+
rows.append(
|
|
68
|
+
WindowRow(
|
|
69
|
+
id: id,
|
|
70
|
+
pid: pid,
|
|
71
|
+
owner: owner,
|
|
72
|
+
title: title,
|
|
73
|
+
bundleId: bundleByPid[Int32(pid)],
|
|
74
|
+
width: width,
|
|
75
|
+
height: height,
|
|
76
|
+
x: x,
|
|
77
|
+
y: y,
|
|
78
|
+
onScreen: onScreen
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Largest first — better default when multiple windows share a bundle id.
|
|
84
|
+
return rows.sorted { ($0.width * $0.height) > ($1.width * $1.height) }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
func hostIdentity() -> (name: String, bundleId: String?) {
|
|
88
|
+
let app = NSRunningApplication.current
|
|
89
|
+
let name =
|
|
90
|
+
app.localizedName
|
|
91
|
+
?? ProcessInfo.processInfo.processName
|
|
92
|
+
return (name, app.bundleIdentifier)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
func screenAccess(request: Bool) -> ScreenAccessReport {
|
|
96
|
+
let host = hostIdentity()
|
|
97
|
+
var granted = CGPreflightScreenCaptureAccess()
|
|
98
|
+
var didRequest = false
|
|
99
|
+
if request && !granted {
|
|
100
|
+
// May show the system consent dialog (not always; Settings may still be required).
|
|
101
|
+
granted = CGRequestScreenCaptureAccess()
|
|
102
|
+
didRequest = true
|
|
103
|
+
}
|
|
104
|
+
return ScreenAccessReport(
|
|
105
|
+
granted: granted,
|
|
106
|
+
requested: didRequest,
|
|
107
|
+
hostApp: host.name,
|
|
108
|
+
hostBundleId: host.bundleId,
|
|
109
|
+
settingsHint:
|
|
110
|
+
"System Settings → Privacy & Security → Screen Recording → enable \(host.name), then quit & reopen it"
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
func emitJSON<T: Encodable>(_ value: T) {
|
|
115
|
+
let encoder = JSONEncoder()
|
|
116
|
+
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
|
|
117
|
+
let data = try! encoder.encode(value)
|
|
118
|
+
FileHandle.standardOutput.write(data)
|
|
119
|
+
fputs("\n", stdout)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let args = Array(CommandLine.arguments.dropFirst())
|
|
123
|
+
let command = args.first ?? "list"
|
|
124
|
+
|
|
125
|
+
if command == "list" || command == "--json" {
|
|
126
|
+
emitJSON(listWindows())
|
|
127
|
+
exit(0)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if command == "screen-access" {
|
|
131
|
+
let request = args.contains("--request") || args.contains("-r")
|
|
132
|
+
let report = screenAccess(request: request)
|
|
133
|
+
emitJSON(report)
|
|
134
|
+
// Exit 0 when granted, 2 when denied (easy for shells).
|
|
135
|
+
exit(report.granted ? 0 : 2)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
fputs(
|
|
139
|
+
"""
|
|
140
|
+
usage:
|
|
141
|
+
WindowTools.swift list
|
|
142
|
+
WindowTools.swift screen-access [--request]
|
|
143
|
+
|
|
144
|
+
list JSON windows for desktop.window matching
|
|
145
|
+
screen-access Screen Recording TCC preflight (optional --request prompt)
|
|
146
|
+
|
|
147
|
+
""",
|
|
148
|
+
stderr
|
|
149
|
+
)
|
|
150
|
+
exit(2)
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@archastro/movie-harness",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Universal movie capture harness for browser, PTY, desktop, and raw frames into .astroshot/",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"astroshots",
|
|
7
|
+
"movie",
|
|
8
|
+
"screencast",
|
|
9
|
+
"playwright",
|
|
10
|
+
"pty",
|
|
11
|
+
"harness"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/ArchAstro/astroshots.git",
|
|
17
|
+
"directory": "packages/movie-harness"
|
|
18
|
+
},
|
|
19
|
+
"homepage": "https://github.com/ArchAstro/astroshots#readme",
|
|
20
|
+
"bugs": "https://github.com/ArchAstro/astroshots/issues",
|
|
21
|
+
"type": "module",
|
|
22
|
+
"main": "./dist/index.js",
|
|
23
|
+
"bin": {
|
|
24
|
+
"astroshot-movie": "bin/astroshot-movie.mjs"
|
|
25
|
+
},
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"import": "./dist/index.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"bin",
|
|
35
|
+
"dist",
|
|
36
|
+
"native",
|
|
37
|
+
"README.md"
|
|
38
|
+
],
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsc -p tsconfig.build.json",
|
|
41
|
+
"clean": "rm -rf dist",
|
|
42
|
+
"test": "npm run build && vitest run",
|
|
43
|
+
"typecheck": "tsc --noEmit",
|
|
44
|
+
"prepack": "npm run clean && npm run build"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=22.14.0"
|
|
48
|
+
},
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public",
|
|
51
|
+
"provenance": true,
|
|
52
|
+
"registry": "https://registry.npmjs.org/"
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"@xterm/headless": "^5.5.0",
|
|
56
|
+
"playwright": "1.61.1",
|
|
57
|
+
"yaml": "^2.8.0"
|
|
58
|
+
},
|
|
59
|
+
"optionalDependencies": {
|
|
60
|
+
"node-pty": "1.2.0-beta.14"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@types/node": "^22.0.0",
|
|
64
|
+
"typescript": "^5.9.0",
|
|
65
|
+
"vitest": "^4.1.0"
|
|
66
|
+
}
|
|
67
|
+
}
|