Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions Barik/Widgets/Spaces/Aerospace/AerospaceProvider.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import Foundation
import Combine

class AerospaceSpacesProvider: SpacesProvider, SwitchableSpacesProvider {
class AerospaceSpacesProvider: SpacesProvider, SwitchableSpacesProvider, EventBasedSpacesProvider {
typealias SpaceType = AeroSpace
let executablePath = ConfigManager.shared.config.aerospace.path

var spacesPublisher: AnyPublisher<SpaceEvent, Never> = PassthroughSubject<SpaceEvent, Never>().eraseToAnyPublisher()

func getSpacesWithWindows() -> [AeroSpace]? {
guard var spaces = fetchSpaces(), let windows = fetchWindows() else {
return nil
Expand Down Expand Up @@ -133,4 +135,12 @@ class AerospaceSpacesProvider: SpacesProvider, SwitchableSpacesProvider {
return nil
}
}

func startObserving() {
<#code#>
}

func stopObserving() {
<#code#>
}
}
49 changes: 49 additions & 0 deletions Barik/Widgets/Spaces/SpacesModels.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import AppKit
import Combine

protocol SpaceModel: Identifiable, Equatable, Codable {
associatedtype WindowType: WindowModel
Expand All @@ -19,6 +20,19 @@ protocol SpacesProvider {
func getSpacesWithWindows() -> [SpaceType]?
}

enum SpaceEvent {
case initialState([AnySpace])
case focusChanged(String)
case windowsUpdated(String, [AnyWindow])
}

protocol EventBasedSpacesProvider {
var spacesPublisher: AnyPublisher<SpaceEvent, Never> { get }

func startObserving()
func stopObserving()
}

protocol SwitchableSpacesProvider: SpacesProvider {
func focusSpace(spaceId: String, needWindowFocus: Bool)
func focusWindow(windowId: String)
Expand Down Expand Up @@ -61,6 +75,12 @@ struct AnySpace: Identifiable, Equatable {
self.isFocused = space.isFocused
self.windows = space.windows.map { AnyWindow($0) }
}

init(id: String, isFocused: Bool, windows: [AnyWindow]) {
self.id = id
self.isFocused = isFocused
self.windows = windows
}

static func == (lhs: AnySpace, rhs: AnySpace) -> Bool {
return lhs.id == rhs.id && lhs.isFocused == rhs.isFocused
Expand All @@ -72,11 +92,20 @@ class AnySpacesProvider {
private let _getSpacesWithWindows: () -> [AnySpace]?
private let _focusSpace: ((String, Bool) -> Void)?
private let _focusWindow: ((String) -> Void)?

private let _isEventBased: Bool
private let _startObserving: (() -> Void)?
private let _stopObserving: (() -> Void)?
private let _spacesPublisher: AnyPublisher<SpaceEvent, Never>?

var isEventBased: Bool { _isEventBased }
var spacesPublisher: AnyPublisher<SpaceEvent, Never>? { _spacesPublisher }

init<P: SpacesProvider>(_ provider: P) {
_getSpacesWithWindows = {
provider.getSpacesWithWindows()?.map { AnySpace($0) }
}

if let switchable = provider as? any SwitchableSpacesProvider {
_focusSpace = { spaceId, needWindowFocus in
switchable.focusSpace(
Expand All @@ -89,6 +118,18 @@ class AnySpacesProvider {
_focusSpace = nil
_focusWindow = nil
}

if let eventBased = provider as? any EventBasedSpacesProvider {
_isEventBased = true
_startObserving = eventBased.startObserving
_stopObserving = eventBased.stopObserving
_spacesPublisher = eventBased.spacesPublisher
} else {
_isEventBased = false
_startObserving = nil
_stopObserving = nil
_spacesPublisher = nil
}
}

func getSpacesWithWindows() -> [AnySpace]? {
Expand All @@ -102,4 +143,12 @@ class AnySpacesProvider {
func focusWindow(windowId: String) {
_focusWindow?(windowId)
}

func startObserving() {
_startObserving?()
}

func stopObserving() {
_stopObserving?()
}
}
72 changes: 70 additions & 2 deletions Barik/Widgets/Spaces/SpacesViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ class SpacesViewModel: ObservableObject {
@Published var spaces: [AnySpace] = []
private var timer: Timer?
private var provider: AnySpacesProvider?
private var cancellables: Set<AnyCancellable> = []
private var spacesById: [String: AnySpace] = [:]

init() {
let runningApps = NSWorkspace.shared.runningApplications.compactMap {
Expand All @@ -26,17 +28,83 @@ class SpacesViewModel: ObservableObject {
}

private func startMonitoring() {
if let provider = provider {
if provider.isEventBased {
startMonitoringEventBasedProvider()
} else {
startMonitoringPollingBasedProvider()
}
}
}

private func stopMonitoring() {
if let provider = provider {
if provider.isEventBased {
stopMonitoringEventBasedProvider()
} else {
stopMonitoringPollingBasedProvider()
}
}
}

private func startMonitoringPollingBasedProvider() {
timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) {
[weak self] _ in
self?.loadSpaces()
}
loadSpaces()
}

private func stopMonitoring() {
private func stopMonitoringPollingBasedProvider() {
timer?.invalidate()
timer = nil
}

private func startMonitoringEventBasedProvider() {
guard let provider = provider else { return }
provider.spacesPublisher?
.receive(on: DispatchQueue.main)
.sink { [weak self] event in
self?.handleSpaceEvent(event)
}
.store(in: &cancellables)
provider.startObserving()
}

private func stopMonitoringEventBasedProvider() {
provider?.stopObserving()
cancellables.removeAll()
}

private func handleSpaceEvent(_ event: SpaceEvent) {
switch event {
case .initialState(let spaces):
spacesById = Dictionary(uniqueKeysWithValues: spaces.map { ($0.id, $0) })
updatePublishedSpaces()
case .focusChanged(let spaceId):
for (id, space) in spacesById {
let newFocused = id == spaceId
if space.isFocused != newFocused {
spacesById[id] = AnySpace(
id: space.id, isFocused: newFocused, windows: space.windows)
}
}
updatePublishedSpaces()
case .windowsUpdated(let spaceId, let windows):
if let space = spacesById[spaceId] {
spacesById[spaceId] = AnySpace(
id: space.id, isFocused: space.isFocused, windows: windows)
}
updatePublishedSpaces()
}
}

private func updatePublishedSpaces() {
let sortedSpaces = spacesById.values.sorted { $0.id < $1.id }
if sortedSpaces != spaces {
spaces = sortedSpaces
}
}

private func loadSpaces() {
DispatchQueue.global(qos: .background).async {
Expand Down