-
-
Notifications
You must be signed in to change notification settings - Fork 31
Expand file tree
/
Copy pathEnvConfigFeatures.swift
More file actions
421 lines (325 loc) · 16.4 KB
/
Copy pathEnvConfigFeatures.swift
File metadata and controls
421 lines (325 loc) · 16.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
// Copyright 2026 jfarcand@apache.org
// Licensed under the Apache License, Version 2.0
//
// ABOUTME: Feature-specific EnvConfig properties (execution, icons, OCR, AI, exploration, app identity).
// ABOUTME: Split from EnvConfig.swift to stay under the 500-line limit.
import Foundation
extension EnvConfig {
// MARK: - Step Execution
public static var waitForTimeoutSeconds: Int {
readInt("waitForTimeoutSeconds", default: TimingConstants.waitForTimeoutSeconds)
}
public static var stepSettlingDelayMs: UInt32 {
readUInt32("stepSettlingDelayMs", default: TimingConstants.stepSettlingDelayMs)
}
public static var compiledSleepBufferMs: Int {
readInt("compiledSleepBufferMs", default: TimingConstants.compiledSleepBufferMs)
}
public static var waitForPollIntervalUs: UInt32 {
readUInt32("waitForPollIntervalUs", default: TimingConstants.waitForPollIntervalUs)
}
public static var measurePollIntervalUs: UInt32 {
readUInt32("measurePollIntervalUs", default: TimingConstants.measurePollIntervalUs)
}
public static var settingsLoadUs: UInt32 {
readUInt32("settingsLoadUs", default: TimingConstants.settingsLoadUs)
}
public static var appSwitcherCardOffset: Double {
readDouble("appSwitcherCardOffset", default: TimingConstants.appSwitcherCardOffset)
}
public static var appSwitcherCardXFraction: Double {
readDouble("appSwitcherCardXFraction", default: TimingConstants.appSwitcherCardXFraction)
}
public static var appSwitcherCardYFraction: Double {
readDouble("appSwitcherCardYFraction", default: TimingConstants.appSwitcherCardYFraction)
}
public static var appSwitcherSwipeDistance: Double {
readDouble("appSwitcherSwipeDistance", default: TimingConstants.appSwitcherSwipeDistance)
}
public static var appSwitcherSwipeDurationMs: Int {
readInt("appSwitcherSwipeDurationMs", default: TimingConstants.appSwitcherSwipeDurationMs)
}
public static var appSwitcherSwipeTopMarginPt: Double {
readDouble("appSwitcherSwipeTopMarginPt", default: TimingConstants.appSwitcherSwipeTopMarginPt)
}
public static var appSwitcherOpenSettleUs: UInt32 {
readUInt32("appSwitcherOpenSettleUs", default: TimingConstants.appSwitcherOpenSettleUs)
}
public static var appForegroundReadyMinElements: Int {
readInt("appForegroundReadyMinElements", default: TimingConstants.appForegroundReadyMinElements)
}
public static var appForegroundReadyRetries: Int {
readInt("appForegroundReadyRetries", default: TimingConstants.appForegroundReadyRetries)
}
public static var appSwitcherMaxSwipes: Int {
readInt("appSwitcherMaxSwipes", default: TimingConstants.appSwitcherMaxSwipes)
}
public static var toolSettlingDelayUs: UInt32 {
readUInt32("toolSettlingDelayUs", default: TimingConstants.toolSettlingDelayUs)
}
// MARK: - Focus Recovery
/// Y coordinate in window-relative points for the status bar engagement tap.
/// After a macOS Space switch, a click at this position makes the window
/// the key window so scroll events are accepted.
public static var statusBarTapY: Double {
readDouble("statusBarTapY", default: TimingConstants.statusBarTapY)
}
// MARK: - Swipe & Scroll Defaults
public static var swipeDistanceFraction: Double {
readDouble("swipeDistanceFraction", default: TimingConstants.swipeDistanceFraction)
}
/// Scroll-swipe start Y as a fraction of window height.
public static var scrollSwipeFromYFraction: Double {
readDouble("scrollSwipeFromYFraction", default: TimingConstants.scrollSwipeFromYFraction)
}
/// Scroll-swipe end Y as a fraction of window height.
public static var scrollSwipeToYFraction: Double {
readDouble("scrollSwipeToYFraction", default: TimingConstants.scrollSwipeToYFraction)
}
public static var defaultSwipeDurationMs: Int {
readInt("defaultSwipeDurationMs", default: TimingConstants.defaultSwipeDurationMs)
}
public static var defaultScrollMaxAttempts: Int {
readInt("defaultScrollMaxAttempts", default: TimingConstants.defaultScrollMaxAttempts)
}
/// Minimum anchor matches required for anchor-based scroll offset detection.
public static var scrollAnchorMinCount: Int {
readInt("scrollAnchorMinCount", default: TimingConstants.scrollAnchorMinCount)
}
// MARK: - Icon Detection
public static var iconOcrProximityFilter: Double {
readDouble("iconOcrProximityFilter", default: TimingConstants.iconOcrProximityFilter)
}
public static var iconMinZoneHeight: Double {
readDouble("iconMinZoneHeight", default: TimingConstants.iconMinZoneHeight)
}
public static var iconSaliencyMinZone: Double {
readDouble("iconSaliencyMinZone", default: TimingConstants.iconSaliencyMinZone)
}
public static var iconBottomZoneFraction: Double {
readDouble("iconBottomZoneFraction", default: TimingConstants.iconBottomZoneFraction)
}
public static var iconTopZoneFraction: Double {
readDouble("iconTopZoneFraction", default: TimingConstants.iconTopZoneFraction)
}
public static var iconMaxZoneElements: Int {
readInt("iconMaxZoneElements", default: TimingConstants.iconMaxZoneElements)
}
public static var iconNoiseMaxLength: Int {
readInt("iconNoiseMaxLength", default: TimingConstants.iconNoiseMaxLength)
}
public static var iconMaxSaliencySize: Double {
readDouble("iconMaxSaliencySize", default: TimingConstants.iconMaxSaliencySize)
}
public static var iconMinForInterpolation: Int {
readInt("iconMinForInterpolation", default: TimingConstants.iconMinForInterpolation)
}
public static var iconSpacingTolerance: Double {
readDouble("iconSpacingTolerance", default: TimingConstants.iconSpacingTolerance)
}
public static var iconDeduplicationRadius: Double {
readDouble("iconDeduplicationRadius", default: TimingConstants.iconDeduplicationRadius)
}
// MARK: - Tab Anchor Synthesis
public static var tabSynthesisMinZoneEvidence: Int {
readInt("tabSynthesisMinZoneEvidence", default: TimingConstants.tabSynthesisMinZoneEvidence)
}
// MARK: - Paused-Session Recovery
public static var pausedDismissClickAttempts: Int {
readInt("pausedDismissClickAttempts", default: TimingConstants.pausedDismissClickAttempts)
}
// MARK: - Icon Cluster Detection
public static var iconColorThreshold: UInt8 {
if let value = settings["iconColorThreshold"],
let num = value as? Int, num >= 0, num <= 255 {
return UInt8(num)
}
if let str = env[envVarName("iconColorThreshold")],
let num = Int(str), num >= 0, num <= 255 {
return UInt8(num)
}
return TimingConstants.iconColorThreshold
}
public static var iconMinColumnDensity: Int {
readInt("iconMinColumnDensity", default: TimingConstants.iconMinColumnDensity)
}
public static var iconMinClusterWidth: Int {
readInt("iconMinClusterWidth", default: TimingConstants.iconMinClusterWidth)
}
public static var iconMaxClusterWidth: Int {
readInt("iconMaxClusterWidth", default: TimingConstants.iconMaxClusterWidth)
}
public static var iconSmoothingWindow: Int {
readInt("iconSmoothingWindow", default: TimingConstants.iconSmoothingWindow)
}
public static var iconCornerInsetPixels: Int {
readInt("iconCornerInsetPixels", default: TimingConstants.iconCornerInsetPixels)
}
public static var iconBarRowBgFraction: Double {
readDouble("iconBarRowBgFraction", default: TimingConstants.iconBarRowBgFraction)
}
// MARK: - Input Tool Defaults
public static var defaultDragDurationMs: Int {
readInt("defaultDragDurationMs", default: TimingConstants.defaultDragDurationMs)
}
public static var defaultLongPressDurationMs: Int {
readInt("defaultLongPressDurationMs", default: TimingConstants.defaultLongPressDurationMs)
}
public static var defaultMeasureTimeoutSeconds: Double {
readDouble("defaultMeasureTimeoutSeconds", default: TimingConstants.defaultMeasureTimeoutSeconds)
}
// MARK: - OCR Configuration
/// OCR recognition level: "accurate" or "fast".
public static var ocrRecognitionLevel: String {
readString("ocrRecognitionLevel", envVar: "MIRROIR_OCR_RECOGNITION_LEVEL",
default: TimingConstants.ocrRecognitionLevel)
}
/// Whether to enable language correction during OCR text recognition.
public static var ocrLanguageCorrection: Bool {
readBool("ocrLanguageCorrection", envVar: "MIRROIR_OCR_LANGUAGE_CORRECTION",
default: TimingConstants.ocrLanguageCorrection)
}
/// Minimum screenshot pixel width for OCR. Images narrower than this are
/// upscaled before text recognition to improve detection on small zoom modes.
public static var ocrMinImageWidth: Int {
readInt("ocrMinImageWidth", default: TimingConstants.ocrMinImageWidth)
}
/// BCP-47 language codes passed to `VNRecognizeTextRequest.recognitionLanguages`.
///
/// When unset, Apple Vision defaults to English (`["en-US"]`). Override this
/// to recognise additional scripts — e.g. `["ja-JP", "en-US"]` for Japanese UI,
/// `["zh-Hans", "en-US"]` for Simplified Chinese, or any combination of
/// [Vision-supported languages](https://developer.apple.com/documentation/vision/vnrecognizetextrequest/recognitionlanguages-swift.property).
///
/// Configure via `settings.json`:
/// ```json
/// { "ocrLanguages": ["ja-JP", "en-US"] }
/// ```
/// Or via the `MIRROIR_OCR_LANGUAGES` environment variable (comma-separated):
/// ```
/// MIRROIR_OCR_LANGUAGES=ja-JP,en-US
/// ```
public static var ocrLanguages: [String] {
readStringArray("ocrLanguages", envVar: "MIRROIR_OCR_LANGUAGES",
default: ["en-US"])
}
// MARK: - Screen Response
/// When true, describe_screen omits the screenshot image from its response.
/// Use this to prevent context overflow in long automation sessions.
public static var describeScreenOmitScreenshot: Bool {
readBool("describeScreenOmitScreenshot", envVar: "MIRROIR_OMIT_SCREENSHOT", default: false)
}
// MARK: - YOLO Element Detection
/// OCR backend selection: "auto", "vision", "yolo", or "both".
public static var ocrBackend: String {
readString("ocrBackend", envVar: "MIRROIR_OCR_BACKEND",
default: TimingConstants.ocrBackend)
}
/// URL to download a YOLO .mlmodel or .mlmodelc from on first use.
public static var yoloModelURL: String {
readString("yoloModelURL", envVar: "MIRROIR_YOLO_MODEL_URL",
default: TimingConstants.yoloModelURL)
}
/// Local filesystem path to a pre-compiled .mlmodelc directory.
public static var yoloModelPath: String {
readString("yoloModelPath", envVar: "MIRROIR_YOLO_MODEL_PATH",
default: TimingConstants.yoloModelPath)
}
/// Minimum confidence threshold for YOLO element detections.
public static var yoloConfidenceThreshold: Double {
readDouble("yoloConfidenceThreshold", default: TimingConstants.yoloConfidenceThreshold)
}
// MARK: - Scroll Deduplication
/// Dedup strategy for scroll-collected OCR elements.
/// Options: "exact" (default), "levenshtein", "proximity".
public static var scrollDedupStrategy: String {
readString("scrollDedupStrategy", envVar: "MIRROIR_SCROLL_DEDUP_STRATEGY",
default: TimingConstants.scrollDedupStrategy)
}
/// Maximum Levenshtein edit distance for fuzzy text dedup.
public static var scrollDedupLevenshteinMax: Int {
readInt("scrollDedupLevenshteinMax", default: TimingConstants.scrollDedupLevenshteinMax)
}
/// Maximum Euclidean distance in points for coordinate proximity dedup.
public static var scrollDedupProximityPt: Double {
readDouble("scrollDedupProximityPt", default: TimingConstants.scrollDedupProximityPt)
}
// MARK: - Scroll Content Matching
/// Maximum X distance (pt) for matching elements across scroll viewports.
public static var scrollContentMatchXTolerance: Double {
readDouble("scrollContentMatchXTolerance", default: TimingConstants.scrollContentMatchXTolerance)
}
/// Maximum Y distance (pt) for filtering outlier deltas during scroll offset measurement.
public static var scrollContentMatchOutlierThreshold: Double {
readDouble("scrollContentMatchOutlierThreshold", default: TimingConstants.scrollContentMatchOutlierThreshold)
}
/// Minimum matching content elements required for content-based scroll offset.
public static var scrollContentMatchMinCount: Int {
readInt("scrollContentMatchMinCount", default: TimingConstants.scrollContentMatchMinCount)
}
/// X quantization bucket size (pt) for composite dedup key.
public static var scrollDedupXBucketSize: Double {
readDouble("scrollDedupXBucketSize", default: TimingConstants.scrollDedupXBucketSize)
}
/// Maximum absolute Y distance (pt) for duplicate detection in page-absolute space.
public static var scrollDedupPageYTolerance: Double {
readDouble("scrollDedupPageYTolerance", default: TimingConstants.scrollDedupPageYTolerance)
}
/// Minimum scroll offset (pt) to accept from anchor or content detection.
public static var scrollMinOffsetThreshold: Double {
readDouble("scrollMinOffsetThreshold", default: TimingConstants.scrollMinOffsetThreshold)
}
// MARK: - Keyboard Layout
/// iPhone keyboard layout name for character substitution.
/// Empty string means US QWERTY (no substitution needed).
/// Set via `mirroir-mcp configure` which saves to settings.json.
public static var keyboardLayout: String {
readString("keyboardLayout", envVar: "IPHONE_KEYBOARD_LAYOUT", default: "")
}
// MARK: - Exploration Budget
public static var explorationMaxDepth: Int {
readInt("explorationMaxDepth", default: TimingConstants.explorationMaxDepth)
}
public static var explorationMaxScreens: Int {
readInt("explorationMaxScreens", default: TimingConstants.explorationMaxScreens)
}
public static var explorationMaxTimeSeconds: Int {
readInt("explorationMaxTimeSeconds", default: TimingConstants.explorationMaxTimeSeconds)
}
// MARK: - Compiled Safety
/// Minimum confidence threshold for compiled taps. Below this, fall back to live OCR.
public static var compiledTapMinConfidence: Double {
readDouble("compiledTapMinConfidence", default: TimingConstants.compiledTapMinConfidence)
}
/// When true, compiled taps are followed by a verification OCR call.
public static var verifyTaps: Bool {
readBool("verifyTaps", envVar: "MIRROIR_VERIFY_TAPS", default: false)
}
// MARK: - Calibration Validation
/// When true, exploration fails if too many elements are unclassified after calibration.
public static var calibrationStrict: Bool {
readBool("calibrationStrict", envVar: "MIRROIR_CALIBRATION_STRICT", default: TimingConstants.calibrationStrict)
}
/// Maximum fraction of content-zone elements that can be unclassified (0.0–1.0).
public static var calibrationUnclassifiedThreshold: Double {
readDouble("calibrationUnclassifiedThreshold", default: TimingConstants.calibrationUnclassifiedThreshold)
}
// MARK: - App Identity
public static var mirroringBundleID: String {
readString("mirroringBundleID", envVar: "MIRROIR_BUNDLE_ID",
default: "com.apple.ScreenContinuity")
}
public static var mirroringProcessName: String {
readString("mirroringProcessName", envVar: "MIRROIR_PROCESS_NAME",
default: "iPhone Mirroring")
}
/// Post mouse events directly to the target PID via `postToPid` instead
/// of global HID posting. Works for regular macOS apps (e.g. FakeMirroring)
/// but NOT for iPhone Mirroring — per-process injection does not register
/// taps; only global HID events (`event.post(tap: .cghidEventTap)`) work,
/// and those inherently move the system cursor. Enables local integration
/// tests without cursor interference.
public static var cursorFreeInput: Bool {
readBool("cursorFreeInput", envVar: "MIRROIR_CURSOR_FREE", default: false)
}
}