49 lines
2.0 KiB
Swift
49 lines
2.0 KiB
Swift
import AppKit
|
|
import Combine
|
|
import GameController
|
|
|
|
struct DetectedController: Identifiable, Equatable {
|
|
let id: ObjectIdentifier
|
|
let name: String
|
|
let supported: Bool
|
|
}
|
|
|
|
// Observe connections only. Never take input handlers away from a running game.
|
|
@MainActor final class ControllerMonitor: ObservableObject {
|
|
@Published private(set) var controllers: [DetectedController] = []
|
|
private let query: () -> [GCController]
|
|
private let center: NotificationCenter
|
|
private var observers: [NSObjectProtocol] = []
|
|
|
|
init(center:NotificationCenter = .default, query:(() -> [GCController])? = nil) {
|
|
self.center=center
|
|
self.query=query ?? {GCController.controllers()}
|
|
for name in [NSNotification.Name.GCControllerDidConnect, .GCControllerDidDisconnect,
|
|
NSApplication.didBecomeActiveNotification] {
|
|
observers.append(center.addObserver(forName:name,object:nil,queue:.main) { [weak self] _ in
|
|
Task { @MainActor in self?.refresh() }
|
|
})
|
|
}
|
|
refresh()
|
|
}
|
|
deinit {for observer in observers {center.removeObserver(observer)}}
|
|
func refresh() {
|
|
let next=query().map {
|
|
DetectedController(id:ObjectIdentifier($0),name:$0.vendorName ?? "",supported:$0.extendedGamepad != nil)
|
|
}
|
|
if next != controllers {controllers=next}
|
|
}
|
|
}
|
|
|
|
// All launchers share one app identity, even when opened through an old shortcut.
|
|
final class LauncherDelegate: NSObject, NSApplicationDelegate {
|
|
func applicationDidFinishLaunching(_ notification:Notification) {
|
|
guard let identifier=Bundle.main.bundleIdentifier else {return}
|
|
if let existing=NSRunningApplication.runningApplications(withBundleIdentifier:identifier)
|
|
.first(where:{$0.processIdentifier != ProcessInfo.processInfo.processIdentifier && !$0.isTerminated}) {
|
|
existing.activate(options:[.activateAllWindows])
|
|
NSApplication.shared.terminate(nil)
|
|
}
|
|
}
|
|
}
|