diff --git a/Overview/Layout/LayoutManager.swift b/Overview/Layout/LayoutManager.swift index 8f407867..bb310851 100644 --- a/Overview/Layout/LayoutManager.swift +++ b/Overview/Layout/LayoutManager.swift @@ -30,13 +30,13 @@ final class LayoutManager: ObservableObject { // MARK: - Public Methods - func createLayout(name: String) -> Layout? { + func createLayout(name: String, windows: [Window]? = nil) -> Layout? { guard isLayoutNameUnique(name) else { logger.warning("Attempted to create layout with non-unique name: \(name)") return nil } - let currentWindows = windowServices.windowStorage.collectWindows() + let currentWindows = windows ?? windowServices.windowStorage.collectWindows() let layout = Layout(name: name, windows: currentWindows) layouts.append(layout) @@ -45,7 +45,7 @@ final class LayoutManager: ObservableObject { return layout } - func updateLayout(id: UUID, name: String? = nil) { + func updateLayout(id: UUID, name: String? = nil, windows: [Window]? = nil) { guard let index = layouts.firstIndex(where: { $0.id == id }) else { logger.warning("Attempted to update non-existent layout: \(id)") return @@ -59,7 +59,12 @@ final class LayoutManager: ObservableObject { return } layout.update(name: name) - } else { + } + + if let windows = windows { + layout.update(windows: windows) + logger.info("Updated layout '\(layout.name)' with \(windows.count) windows") + } else if name == nil { let currentWindows = windowServices.windowStorage.collectWindows() layout.update(windows: currentWindows) logger.info("Updated layout '\(layout.name)' with \(currentWindows.count) windows") diff --git a/Overview/Layout/Settings/LayoutSettingsKeys.swift b/Overview/Layout/Settings/LayoutSettingsKeys.swift index 0dcc8f05..f5dcf314 100644 --- a/Overview/Layout/Settings/LayoutSettingsKeys.swift +++ b/Overview/Layout/Settings/LayoutSettingsKeys.swift @@ -12,4 +12,5 @@ extension Defaults.Keys { static let storedLayouts = Key("storedLayouts", default: nil) static let launchLayoutUUID = Key("launchLayoutUUID", default: nil) static let closeWindowsOnApply = Key("closeWindowsOnApply", default: true) + static let includeWindowNames = Key("includeWindowNames", default: false) } diff --git a/Overview/Layout/Settings/LayoutSettingsTab.swift b/Overview/Layout/Settings/LayoutSettingsTab.swift index f273701c..4facdcfa 100644 --- a/Overview/Layout/Settings/LayoutSettingsTab.swift +++ b/Overview/Layout/Settings/LayoutSettingsTab.swift @@ -37,6 +37,7 @@ struct LayoutSettingsTab: View { // Layout Settings @Default(.launchLayoutUUID) private var launchLayoutUUID + @Default(.includeWindowNames) private var includeWindowNames init(windowManager: WindowManager, layoutManager: LayoutManager) { self.layoutManager = layoutManager @@ -151,6 +152,8 @@ struct LayoutSettingsTab: View { Defaults.Toggle( "Close all windows when applying layouts", key: .closeWindowsOnApply) + Defaults.Toggle( + "Save window names with layout", key: .includeWindowNames) } } .formStyle(.grouped) @@ -179,7 +182,7 @@ struct LayoutSettingsTab: View { Button("Cancel", role: .cancel) {} Button("Update") { if let layout = layoutToModify { - layoutManager.updateLayout(id: layout.id) + windowManager.updateLayout(id: layout.id, includeWindowNames: includeWindowNames) } layoutToModify = nil } @@ -271,7 +274,8 @@ struct LayoutSettingsTab: View { isJSONEditorVisible = true } private func createLayout() { - guard !newLayoutName.isEmpty, layoutManager.createLayout(name: newLayoutName) != nil + guard !newLayoutName.isEmpty, + windowManager.saveLayout(name: newLayoutName, includeWindowNames: includeWindowNames) != nil else { logger.warning("Attempted to create layout with empty or non-unique name") return @@ -349,7 +353,7 @@ struct LayoutSettingsTab: View { } for layout in layoutsFromJSON { - _ = windowManager.saveLayout(name: layout.name) + _ = windowManager.saveLayout(name: layout.name, includeWindowNames: includeWindowNames) if let newLayout = layoutManager.layouts.last { layoutManager.updateLayout(id: newLayout.id, name: layout.name) diff --git a/Overview/Window/Services/WindowStorageService.swift b/Overview/Window/Services/WindowStorageService.swift index 6be1f8e3..366798a5 100644 --- a/Overview/Window/Services/WindowStorageService.swift +++ b/Overview/Window/Services/WindowStorageService.swift @@ -48,9 +48,9 @@ final class WindowStorageService { } } - func applyWindows(_ windows: [Window], using handler: (NSRect) -> Void) { + func applyWindows(_ windows: [Window], using handler: (Window) -> Void) { windows.forEach { window in - handler(window.frame) + handler(window) } logger.info("Successfully applied \(windows.count) windows") } diff --git a/Overview/Window/Window.swift b/Overview/Window/Window.swift index 005c03f2..b43f2960 100644 --- a/Overview/Window/Window.swift +++ b/Overview/Window/Window.swift @@ -12,15 +12,17 @@ struct Window: Codable, Equatable { let y: Double let width: Double let height: Double + let windowName: String? var frame: NSRect { NSRect(x: x, y: y, width: width, height: height) } - init(frame: NSRect) { + init(frame: NSRect, windowName: String? = nil) { self.x = frame.origin.x self.y = frame.origin.y self.width = frame.width self.height = frame.height + self.windowName = windowName } } diff --git a/Overview/Window/WindowManager.swift b/Overview/Window/WindowManager.swift index ce873ecd..18bebef0 100644 --- a/Overview/Window/WindowManager.swift +++ b/Overview/Window/WindowManager.swift @@ -23,6 +23,7 @@ final class WindowManager: ObservableObject { // Private State private var activeWindows: Set = [] private var windowDelegates: [NSWindow: WindowDelegate] = [:] + private var captureCoordinators: [NSWindow: CaptureCoordinator] = [:] private var sessionWindowCounter: Int init( @@ -39,7 +40,7 @@ final class WindowManager: ObservableObject { logger.debug("Window manager initialized") } - func createWindow(at frame: NSRect? = nil) throws { + func createWindow(at frame: NSRect? = nil, windowName: String? = nil) throws { do { let defaultSize = CGSize( width: Defaults[.defaultWindowWidth], height: Defaults[.defaultWindowHeight]) @@ -48,7 +49,7 @@ final class WindowManager: ObservableObject { windowCount: sessionWindowCounter, providedFrame: frame) - configureWindow(window) + configureWindow(window, windowName: windowName) activeWindows.insert(window) sessionWindowCounter += 1 @@ -66,6 +67,7 @@ final class WindowManager: ObservableObject { window.orderOut(self) activeWindows.remove(window) windowDelegates.removeValue(forKey: window) + captureCoordinators.removeValue(forKey: window) logger.debug("Window closed successfully") } } @@ -102,20 +104,26 @@ final class WindowManager: ObservableObject { } } - func saveLayout(name: String) -> Layout? { - let layout = layoutManager.createLayout(name: name) + func saveLayout(name: String, includeWindowNames: Bool = false) -> Layout? { + let windows = collectWindows(includeWindowNames: includeWindowNames) + let layout = layoutManager.createLayout(name: name, windows: windows) return layout } + func updateLayout(id: UUID, includeWindowNames: Bool = false) { + let windows = collectWindows(includeWindowNames: includeWindowNames) + layoutManager.updateLayout(id: id, windows: windows) + } + func applyLayout(_ layout: Layout) { if Defaults[.closeWindowsOnApply] { closeAllWindows() } - windowServices.windowStorage.applyWindows(layout.windows) { [weak self] frame in + windowServices.windowStorage.applyWindows(layout.windows) { [weak self] windowState in guard let self = self else { return } do { - try createWindow(at: frame) + try createWindow(at: windowState.frame, windowName: windowState.windowName) } catch { logger.logError( error, context: "Failed to create window from layout '\(layout.name)'") @@ -134,11 +142,11 @@ final class WindowManager: ObservableObject { } } - private func configureWindow(_ window: NSWindow) { + private func configureWindow(_ window: NSWindow, windowName: String?) { windowServices.windowConfiguration.applyConfiguration( to: window, hasShadow: Defaults[.windowShadowEnabled]) setupWindowDelegate(for: window) - setupWindowContent(window) + setupWindowContent(window, windowName: windowName) } private func setupWindowDelegate(for window: NSWindow) { @@ -148,12 +156,14 @@ final class WindowManager: ObservableObject { logger.debug("Window delegate configured: id=\(sessionWindowCounter)") } - private func setupWindowContent(_ window: NSWindow) { + private func setupWindowContent(_ window: NSWindow, windowName: String?) { let captureCoordinator = CaptureCoordinator( sourceManager: sourceManager, permissionManager: permissionManager ) + captureCoordinators[window] = captureCoordinator + let contentView = PreviewView( previewManager: previewManager, sourceManager: sourceManager, @@ -165,6 +175,10 @@ final class WindowManager: ObservableObject { } ) window.contentView = NSHostingView(rootView: contentView) + + if let title = windowName { + selectSource(named: title, for: captureCoordinator) + } } private func closeAllWindows() { @@ -174,6 +188,29 @@ final class WindowManager: ObservableObject { } } + private func collectWindows(includeWindowNames: Bool) -> [Window] { + activeWindows.compactMap { window in + let title: String? = includeWindowNames ? captureCoordinators[window]?.sourceWindowTitle : nil + return Window(frame: window.frame, windowName: title) + } + } + + private func selectSource(named title: String, for coordinator: CaptureCoordinator) { + Task { @MainActor in + do { + let sources = try await sourceManager.getFilteredSources() + if let match = sources.first(where: { $0.title == title }) { + previewManager.startSourcePreview( + captureCoordinator: coordinator, + source: match + ) + } + } catch { + logger.logError(error, context: "Failed to select source '\(title)'") + } + } + } + private func handleRestoreCompletion(_ restoredCount: Int) { if restoredCount == 0 && Defaults[.createOnLaunch] { logger.info("No windows restored, creating default window")