@lattices/cli 0.4.2 → 0.4.6
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 +3 -0
- package/app/Info.plist +2 -2
- package/app/Lattices.app/Contents/Info.plist +2 -2
- package/app/Lattices.app/Contents/MacOS/Lattices +0 -0
- package/app/Package.swift +6 -0
- package/app/Sources/AppShell/App.swift +20 -0
- package/app/Sources/{AppDelegate.swift → AppShell/AppDelegate.swift} +94 -34
- package/app/Sources/{AppShellView.swift → AppShell/AppShellView.swift} +12 -1
- package/app/Sources/AppShell/AppUpdater.swift +92 -0
- package/app/Sources/AppShell/CliActionLauncher.swift +50 -0
- package/app/Sources/{HomeDashboardView.swift → AppShell/HomeDashboardView.swift} +18 -10
- package/app/Sources/AppShell/LatticesRuntime.swift +61 -0
- package/app/Sources/{MainView.swift → AppShell/MainView.swift} +351 -191
- package/app/Sources/{OnboardingView.swift → AppShell/OnboardingView.swift} +30 -16
- package/app/Sources/{Preferences.swift → AppShell/Preferences.swift} +78 -0
- package/app/Sources/{SettingsView.swift → AppShell/SettingsView.swift} +869 -152
- package/app/Sources/{HotkeyStore.swift → Core/Actions/HotkeyStore.swift} +9 -5
- package/app/Sources/{IntentEngine.swift → Core/Actions/IntentEngine.swift} +51 -27
- package/app/Sources/Core/Actions/IntentSchema.swift +94 -0
- package/app/Sources/{Intents → Core/Actions/Intents}/LatticeIntent.swift +0 -25
- package/app/Sources/{PaletteCommand.swift → Core/Actions/PaletteCommand.swift} +26 -6
- package/app/Sources/{VoiceIntentResolver.swift → Core/Actions/VoiceIntentResolver.swift} +46 -4
- package/app/Sources/Core/Companion/CompanionActivityLog.swift +70 -0
- package/app/Sources/Core/Companion/CompanionKeyboardController.swift +141 -0
- package/app/Sources/Core/Companion/LatticesCompanionBridgeServer.swift +438 -0
- package/app/Sources/Core/Companion/LatticesCompanionCockpit.swift +555 -0
- package/app/Sources/Core/Companion/LatticesCompanionSecurityCoordinator.swift +594 -0
- package/app/Sources/Core/Companion/LatticesCompanionTrackpadController.swift +204 -0
- package/app/Sources/Core/Companion/LatticesDeckHost.swift +1463 -0
- package/app/Sources/{LatticesApi.swift → Core/Daemon/LatticesApi.swift} +125 -4
- package/app/Sources/{AppTypeClassifier.swift → Core/Desktop/AppTypeClassifier.swift} +36 -0
- package/app/Sources/{DesktopModel.swift → Core/Desktop/DesktopModel.swift} +6 -8
- package/app/Sources/Core/Desktop/MouseFinder.swift +527 -0
- package/app/Sources/Core/Desktop/SessionWindowLocator.swift +139 -0
- package/app/Sources/Core/Desktop/WindowDragSnapController.swift +628 -0
- package/app/Sources/Core/Desktop/WindowPreviewCard.swift +100 -0
- package/app/Sources/Core/Desktop/WindowPreviewStore.swift +113 -0
- package/app/Sources/Core/Desktop/WindowSelectionStore.swift +76 -0
- package/app/Sources/{WindowTiler.swift → Core/Desktop/WindowTiler.swift} +351 -172
- package/app/Sources/Core/Input/MouseGestureConfig.swift +364 -0
- package/app/Sources/Core/Input/MouseGestureController.swift +1203 -0
- package/app/Sources/Core/Input/MouseInputDeviceStore.swift +98 -0
- package/app/Sources/Core/Input/MouseInputEventViewer.swift +272 -0
- package/app/Sources/Core/Input/MouseShortcutStore.swift +107 -0
- package/app/Sources/{CommandModeState.swift → Core/Overlays/CommandMode/CommandModeState.swift} +127 -24
- package/app/Sources/{CommandModeView.swift → Core/Overlays/CommandMode/CommandModeView.swift} +492 -79
- package/app/Sources/Core/Overlays/CommandPalette/CommandPaletteWindow.swift +67 -0
- package/app/Sources/{CheatSheetHUD.swift → Core/Overlays/HUD/CheatSheetHUD.swift} +1 -0
- package/app/Sources/{HUDRightBar.swift → Core/Overlays/HUD/HUDRightBar.swift} +23 -201
- package/app/Sources/{LauncherHUD.swift → Core/Overlays/HUD/LauncherHUD.swift} +12 -26
- package/app/Sources/{OmniSearchView.swift → Core/Overlays/OmniSearch/OmniSearchView.swift} +136 -2
- package/app/Sources/{OmniSearchWindow.swift → Core/Overlays/OmniSearch/OmniSearchWindow.swift} +21 -32
- package/app/Sources/Core/Overlays/OverlayPanelShell.swift +241 -0
- package/app/Sources/{ScreenMapState.swift → Core/Overlays/ScreenMap/ScreenMapState.swift} +116 -32
- package/app/Sources/{ScreenMapView.swift → Core/Overlays/ScreenMap/ScreenMapView.swift} +510 -524
- package/app/Sources/{ScreenMapWindowController.swift → Core/Overlays/ScreenMap/ScreenMapWindowController.swift} +12 -4
- package/app/Sources/{VoiceCommandWindow.swift → Core/Overlays/Voice/VoiceCommandWindow.swift} +46 -53
- package/app/Sources/Core/Pi/PiAuthNextStepCard.swift +148 -0
- package/app/Sources/Core/Pi/PiAuthPromptCard.swift +90 -0
- package/app/Sources/{PiChatDock.swift → Core/Pi/PiChatDock.swift} +137 -74
- package/app/Sources/{PiChatSession.swift → Core/Pi/PiChatSession.swift} +608 -108
- package/app/Sources/Core/Pi/PiInstallCallout.swift +86 -0
- package/app/Sources/Core/Pi/PiProviderSetupCallout.swift +99 -0
- package/app/Sources/{PiWorkspaceView.swift → Core/Pi/PiWorkspaceView.swift} +174 -77
- package/app/Sources/{PermissionChecker.swift → Core/System/PermissionChecker.swift} +76 -2
- package/app/Sources/Core/System/SystemTelemetryMonitor.swift +273 -0
- package/app/Sources/{HandsOffSession.swift → Core/Voice/HandsOffSession.swift} +15 -4
- package/app/Sources/{WorkspaceManager.swift → Core/Workspace/WorkspaceManager.swift} +288 -0
- package/bin/assistant-intelligence.ts +874 -0
- package/bin/handsoff-infer.ts +16 -209
- package/bin/handsoff-worker.ts +45 -258
- package/bin/lattices-app.ts +62 -0
- package/bin/lattices-dev +4 -0
- package/bin/lattices.ts +125 -14
- package/docs/agents.md +14 -0
- package/docs/api.md +55 -0
- package/docs/app.md +3 -0
- package/docs/companion-deck.md +180 -0
- package/docs/component-extraction-roadmap.md +392 -0
- package/docs/config.md +25 -0
- package/docs/tiling-reference.md +55 -0
- package/docs/voice-error-model.md +73 -0
- package/package.json +4 -1
- package/app/Sources/App.swift +0 -10
- package/app/Sources/CommandPaletteWindow.swift +0 -134
- package/app/Sources/MouseFinder.swift +0 -222
- /package/app/Sources/{KeyRecorderView.swift → AppShell/KeyRecorderView.swift} +0 -0
- /package/app/Sources/{MainWindow.swift → AppShell/MainWindow.swift} +0 -0
- /package/app/Sources/{SettingsWindow.swift → AppShell/SettingsWindow.swift} +0 -0
- /package/app/Sources/{HotkeyManager.swift → Core/Actions/HotkeyManager.swift} +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/CreateLayerIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/DistributeIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/FocusIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/HelpIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/KillIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/LaunchIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/ListSessionsIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/ListWindowsIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/ScanIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/SearchIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/SwitchLayerIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/TileIntent.swift +0 -0
- /package/app/Sources/{DaemonProtocol.swift → Core/Daemon/DaemonProtocol.swift} +0 -0
- /package/app/Sources/{DaemonServer.swift → Core/Daemon/DaemonServer.swift} +0 -0
- /package/app/Sources/{AccessibilityTextExtractor.swift → Core/Desktop/AccessibilityTextExtractor.swift} +0 -0
- /package/app/Sources/{DesktopModelTypes.swift → Core/Desktop/DesktopModelTypes.swift} +0 -0
- /package/app/Sources/{InventoryManager.swift → Core/Desktop/InventoryManager.swift} +0 -0
- /package/app/Sources/{InventoryPath.swift → Core/Desktop/InventoryPath.swift} +0 -0
- /package/app/Sources/{OcrModel.swift → Core/Desktop/OcrModel.swift} +0 -0
- /package/app/Sources/{OcrStore.swift → Core/Desktop/OcrStore.swift} +0 -0
- /package/app/Sources/{PlacementSpec.swift → Core/Desktop/PlacementSpec.swift} +0 -0
- /package/app/Sources/{TilePickerView.swift → Core/Desktop/TilePickerView.swift} +0 -0
- /package/app/Sources/{AppWindowShell.swift → Core/Overlays/AppWindowShell.swift} +0 -0
- /package/app/Sources/{CommandModeWindow.swift → Core/Overlays/CommandMode/CommandModeWindow.swift} +0 -0
- /package/app/Sources/{CommandPaletteView.swift → Core/Overlays/CommandPalette/CommandPaletteView.swift} +0 -0
- /package/app/Sources/{HUDBottomBar.swift → Core/Overlays/HUD/HUDBottomBar.swift} +0 -0
- /package/app/Sources/{HUDController.swift → Core/Overlays/HUD/HUDController.swift} +0 -0
- /package/app/Sources/{HUDLeftBar.swift → Core/Overlays/HUD/HUDLeftBar.swift} +0 -0
- /package/app/Sources/{HUDMinimap.swift → Core/Overlays/HUD/HUDMinimap.swift} +0 -0
- /package/app/Sources/{HUDState.swift → Core/Overlays/HUD/HUDState.swift} +0 -0
- /package/app/Sources/{HUDTopBar.swift → Core/Overlays/HUD/HUDTopBar.swift} +0 -0
- /package/app/Sources/{LayerBezel.swift → Core/Overlays/HUD/LayerBezel.swift} +0 -0
- /package/app/Sources/{OmniSearchState.swift → Core/Overlays/OmniSearch/OmniSearchState.swift} +0 -0
- /package/app/Sources/{DiagnosticLog.swift → Core/System/DiagnosticLog.swift} +0 -0
- /package/app/Sources/{EventBus.swift → Core/System/EventBus.swift} +0 -0
- /package/app/Sources/{ProcessModel.swift → Core/System/ProcessModel.swift} +0 -0
- /package/app/Sources/{ProcessQuery.swift → Core/System/ProcessQuery.swift} +0 -0
- /package/app/Sources/{AdvisorLearningStore.swift → Core/Voice/AdvisorLearningStore.swift} +0 -0
- /package/app/Sources/{AgentSession.swift → Core/Voice/AgentSession.swift} +0 -0
- /package/app/Sources/{AudioProvider.swift → Core/Voice/AudioProvider.swift} +0 -0
- /package/app/Sources/{VoiceChatView.swift → Core/Voice/VoiceChatView.swift} +0 -0
- /package/app/Sources/{VoxClient.swift → Core/Voice/VoxClient.swift} +0 -0
- /package/app/Sources/{Project.swift → Core/Workspace/Project.swift} +0 -0
- /package/app/Sources/{ProjectScanner.swift → Core/Workspace/ProjectScanner.swift} +0 -0
- /package/app/Sources/{SessionLayerStore.swift → Core/Workspace/SessionLayerStore.swift} +0 -0
- /package/app/Sources/{SessionManager.swift → Core/Workspace/SessionManager.swift} +0 -0
- /package/app/Sources/{Terminal.swift → Core/Workspace/Terminal/Terminal.swift} +0 -0
- /package/app/Sources/{TerminalQuery.swift → Core/Workspace/Terminal/TerminalQuery.swift} +0 -0
- /package/app/Sources/{TerminalSynthesizer.swift → Core/Workspace/Terminal/TerminalSynthesizer.swift} +0 -0
- /package/app/Sources/{TmuxModel.swift → Core/Workspace/Tmux/TmuxModel.swift} +0 -0
- /package/app/Sources/{TmuxQuery.swift → Core/Workspace/Tmux/TmuxQuery.swift} +0 -0
- /package/app/Sources/{ActionRow.swift → UI/ActionRow.swift} +0 -0
- /package/app/Sources/{OrphanRow.swift → UI/OrphanRow.swift} +0 -0
- /package/app/Sources/{ProjectRow.swift → UI/ProjectRow.swift} +0 -0
- /package/app/Sources/{TabGroupRow.swift → UI/TabGroupRow.swift} +0 -0
- /package/app/Sources/{Theme.swift → UI/Theme.swift} +0 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
import SwiftUI
|
|
3
|
+
|
|
4
|
+
struct WindowPreviewCardStyle {
|
|
5
|
+
var containerCornerRadius: CGFloat = 10
|
|
6
|
+
var imageCornerRadius: CGFloat = 8
|
|
7
|
+
var imagePadding: CGFloat = 8
|
|
8
|
+
var background: Color = Palette.surface.opacity(0.8)
|
|
9
|
+
var border: Color = Palette.border
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
struct WindowPreviewCard<Overlay: View>: View {
|
|
13
|
+
let image: NSImage?
|
|
14
|
+
let isLoading: Bool
|
|
15
|
+
let appName: String
|
|
16
|
+
var loadingTitle: String = "Capturing preview"
|
|
17
|
+
var unavailableTitle: String = "Preview unavailable"
|
|
18
|
+
var style: WindowPreviewCardStyle = WindowPreviewCardStyle()
|
|
19
|
+
var holdingPreviousPreview: Bool = false
|
|
20
|
+
@ViewBuilder let overlay: () -> Overlay
|
|
21
|
+
|
|
22
|
+
var body: some View {
|
|
23
|
+
ZStack {
|
|
24
|
+
RoundedRectangle(cornerRadius: style.containerCornerRadius)
|
|
25
|
+
.fill(style.background)
|
|
26
|
+
.overlay(
|
|
27
|
+
RoundedRectangle(cornerRadius: style.containerCornerRadius)
|
|
28
|
+
.strokeBorder(style.border, lineWidth: 0.5)
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
if let image {
|
|
32
|
+
Image(nsImage: image)
|
|
33
|
+
.resizable()
|
|
34
|
+
.aspectRatio(contentMode: .fit)
|
|
35
|
+
.clipShape(RoundedRectangle(cornerRadius: style.imageCornerRadius))
|
|
36
|
+
.padding(style.imagePadding)
|
|
37
|
+
.opacity(holdingPreviousPreview ? 0.88 : 1)
|
|
38
|
+
} else if isLoading {
|
|
39
|
+
WindowPreviewPlaceholder(
|
|
40
|
+
icon: "photo",
|
|
41
|
+
title: loadingTitle,
|
|
42
|
+
subtitle: appName
|
|
43
|
+
)
|
|
44
|
+
} else {
|
|
45
|
+
WindowPreviewPlaceholder(
|
|
46
|
+
icon: "eye.slash",
|
|
47
|
+
title: unavailableTitle,
|
|
48
|
+
subtitle: appName
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
overlay()
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
extension WindowPreviewCard where Overlay == EmptyView {
|
|
58
|
+
init(
|
|
59
|
+
image: NSImage?,
|
|
60
|
+
isLoading: Bool,
|
|
61
|
+
appName: String,
|
|
62
|
+
loadingTitle: String = "Capturing preview",
|
|
63
|
+
unavailableTitle: String = "Preview unavailable",
|
|
64
|
+
style: WindowPreviewCardStyle = WindowPreviewCardStyle(),
|
|
65
|
+
holdingPreviousPreview: Bool = false
|
|
66
|
+
) {
|
|
67
|
+
self.init(
|
|
68
|
+
image: image,
|
|
69
|
+
isLoading: isLoading,
|
|
70
|
+
appName: appName,
|
|
71
|
+
loadingTitle: loadingTitle,
|
|
72
|
+
unavailableTitle: unavailableTitle,
|
|
73
|
+
style: style,
|
|
74
|
+
holdingPreviousPreview: holdingPreviousPreview,
|
|
75
|
+
overlay: { EmptyView() }
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private struct WindowPreviewPlaceholder: View {
|
|
81
|
+
let icon: String
|
|
82
|
+
let title: String
|
|
83
|
+
let subtitle: String
|
|
84
|
+
|
|
85
|
+
var body: some View {
|
|
86
|
+
VStack(spacing: 8) {
|
|
87
|
+
Image(systemName: icon)
|
|
88
|
+
.font(.system(size: 18, weight: .medium))
|
|
89
|
+
.foregroundColor(Palette.textMuted.opacity(0.7))
|
|
90
|
+
Text(title)
|
|
91
|
+
.font(Typo.monoBold(10))
|
|
92
|
+
.foregroundColor(Palette.textMuted)
|
|
93
|
+
Text(subtitle)
|
|
94
|
+
.font(Typo.mono(9))
|
|
95
|
+
.foregroundColor(Palette.textDim)
|
|
96
|
+
.lineLimit(1)
|
|
97
|
+
}
|
|
98
|
+
.padding(16)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
import SwiftUI
|
|
3
|
+
|
|
4
|
+
final class WindowPreviewStore: ObservableObject {
|
|
5
|
+
static let shared = WindowPreviewStore()
|
|
6
|
+
|
|
7
|
+
@Published private var images: [UInt32: NSImage] = [:]
|
|
8
|
+
@Published private var loading: Set<UInt32> = []
|
|
9
|
+
|
|
10
|
+
private var lastAttemptAt: [UInt32: Date] = [:]
|
|
11
|
+
private var accessOrder: [UInt32] = []
|
|
12
|
+
private let maxCached = 15
|
|
13
|
+
private let queue = DispatchQueue(label: "com.arach.lattices.window-preview", qos: .userInitiated)
|
|
14
|
+
private let previewMaxSize = NSSize(width: 360, height: 190)
|
|
15
|
+
|
|
16
|
+
func image(for wid: UInt32) -> NSImage? {
|
|
17
|
+
if images[wid] != nil {
|
|
18
|
+
touchLRU(wid)
|
|
19
|
+
}
|
|
20
|
+
return images[wid]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
func hasSettled(_ wid: UInt32) -> Bool {
|
|
24
|
+
images[wid] != nil || (lastAttemptAt[wid] != nil && !loading.contains(wid))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
func isLoading(_ wid: UInt32) -> Bool {
|
|
28
|
+
loading.contains(wid)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
func prewarm(windows: [WindowEntry], limit: Int = 4) {
|
|
32
|
+
for window in windows.prefix(limit) {
|
|
33
|
+
load(window: window)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
func load(window: WindowEntry) {
|
|
38
|
+
if images[window.wid] != nil || loading.contains(window.wid) {
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let now = Date()
|
|
43
|
+
if let lastAttemptAt = lastAttemptAt[window.wid], now.timeIntervalSince(lastAttemptAt) < 1.0 {
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
lastAttemptAt[window.wid] = now
|
|
47
|
+
|
|
48
|
+
loading.insert(window.wid)
|
|
49
|
+
let wid = window.wid
|
|
50
|
+
let frame = window.frame
|
|
51
|
+
let startedAt = Date()
|
|
52
|
+
|
|
53
|
+
queue.async { [weak self] in
|
|
54
|
+
guard let self else { return }
|
|
55
|
+
|
|
56
|
+
let cgImage = CGWindowListCreateImage(
|
|
57
|
+
.null,
|
|
58
|
+
.optionIncludingWindow,
|
|
59
|
+
CGWindowID(wid),
|
|
60
|
+
[.boundsIgnoreFraming, .nominalResolution]
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
let image = cgImage.map {
|
|
64
|
+
NSImage(
|
|
65
|
+
cgImage: $0,
|
|
66
|
+
size: self.previewSize(for: frame)
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
DispatchQueue.main.async {
|
|
71
|
+
self.loading.remove(wid)
|
|
72
|
+
let elapsedMs = Int(Date().timeIntervalSince(startedAt) * 1000)
|
|
73
|
+
if let image {
|
|
74
|
+
self.images[wid] = image
|
|
75
|
+
self.touchLRU(wid)
|
|
76
|
+
self.evictIfNeeded()
|
|
77
|
+
if elapsedMs >= 80 {
|
|
78
|
+
DiagnosticLog.shared.info("HUDPreview: captured wid=\(wid) in \(elapsedMs)ms")
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
DiagnosticLog.shared.info("HUDPreview: capture unavailable wid=\(wid) after \(elapsedMs)ms")
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private func touchLRU(_ wid: UInt32) {
|
|
88
|
+
accessOrder.removeAll { $0 == wid }
|
|
89
|
+
accessOrder.append(wid)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private func evictIfNeeded() {
|
|
93
|
+
while images.count > maxCached, let oldest = accessOrder.first {
|
|
94
|
+
accessOrder.removeFirst()
|
|
95
|
+
images.removeValue(forKey: oldest)
|
|
96
|
+
lastAttemptAt.removeValue(forKey: oldest)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private func previewSize(for frame: WindowFrame) -> NSSize {
|
|
101
|
+
let width = max(CGFloat(frame.w), CGFloat(1))
|
|
102
|
+
let height = max(CGFloat(frame.h), CGFloat(1))
|
|
103
|
+
let scale = min(
|
|
104
|
+
previewMaxSize.width / width,
|
|
105
|
+
previewMaxSize.height / height,
|
|
106
|
+
CGFloat(1)
|
|
107
|
+
)
|
|
108
|
+
return NSSize(
|
|
109
|
+
width: max(CGFloat(1), width * scale),
|
|
110
|
+
height: max(CGFloat(1), height * scale)
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import Combine
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
struct SelectedWindowSummary: Identifiable, Equatable {
|
|
5
|
+
let wid: UInt32
|
|
6
|
+
let app: String
|
|
7
|
+
let title: String
|
|
8
|
+
let latticesSession: String?
|
|
9
|
+
|
|
10
|
+
var id: UInt32 { wid }
|
|
11
|
+
|
|
12
|
+
var displayTitle: String {
|
|
13
|
+
if !title.isEmpty { return title }
|
|
14
|
+
if let latticesSession, !latticesSession.isEmpty { return latticesSession }
|
|
15
|
+
return app
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
final class WindowSelectionStore: ObservableObject {
|
|
20
|
+
static let shared = WindowSelectionStore()
|
|
21
|
+
|
|
22
|
+
@Published private(set) var windows: [SelectedWindowSummary] = []
|
|
23
|
+
@Published private(set) var source: String?
|
|
24
|
+
@Published private(set) var updatedAt: Date?
|
|
25
|
+
|
|
26
|
+
private init() {}
|
|
27
|
+
|
|
28
|
+
var isActive: Bool { !windows.isEmpty }
|
|
29
|
+
var windowIds: [UInt32] { windows.map(\.wid) }
|
|
30
|
+
var count: Int { windows.count }
|
|
31
|
+
var sourceLabel: String? {
|
|
32
|
+
switch source {
|
|
33
|
+
case "desktop-inventory": return "window selector"
|
|
34
|
+
case "screen-map": return "screen map"
|
|
35
|
+
case let value?: return value
|
|
36
|
+
case nil: return nil
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
func setSelection(_ windows: [SelectedWindowSummary], source: String) {
|
|
41
|
+
let unique = Array(
|
|
42
|
+
Dictionary(uniqueKeysWithValues: windows.map { ($0.wid, $0) }).values
|
|
43
|
+
).sorted { lhs, rhs in
|
|
44
|
+
if lhs.app == rhs.app { return lhs.wid < rhs.wid }
|
|
45
|
+
return lhs.app.localizedCaseInsensitiveCompare(rhs.app) == .orderedAscending
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
DispatchQueue.main.async {
|
|
49
|
+
self.windows = unique
|
|
50
|
+
self.source = source
|
|
51
|
+
self.updatedAt = Date()
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
func clear(source: String? = nil) {
|
|
56
|
+
DispatchQueue.main.async {
|
|
57
|
+
if let source, let current = self.source, current != source {
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
self.windows = []
|
|
61
|
+
self.source = source ?? self.source
|
|
62
|
+
self.updatedAt = Date()
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
func summary(maxItems: Int = 3) -> String {
|
|
67
|
+
guard !windows.isEmpty else { return "No selection" }
|
|
68
|
+
let titles = windows.prefix(maxItems).map { item in
|
|
69
|
+
item.app == item.displayTitle ? item.app : "\(item.app): \(item.displayTitle)"
|
|
70
|
+
}
|
|
71
|
+
if windows.count > maxItems {
|
|
72
|
+
return titles.joined(separator: " • ") + " +\(windows.count - maxItems)"
|
|
73
|
+
}
|
|
74
|
+
return titles.joined(separator: " • ")
|
|
75
|
+
}
|
|
76
|
+
}
|