diff --git a/Coder-Desktop/Coder-Desktop/Coder_DesktopApp.swift b/Coder-Desktop/Coder-Desktop/Coder_DesktopApp.swift index 30ea7e7e..307e0797 100644 --- a/Coder-Desktop/Coder-Desktop/Coder_DesktopApp.swift +++ b/Coder-Desktop/Coder-Desktop/Coder_DesktopApp.swift @@ -1,6 +1,10 @@ import FluidMenuBarExtra import NetworkExtension +import os +import SDWebImageSVGCoder +import SDWebImageSwiftUI import SwiftUI +import UserNotifications import VPNLib @main @@ -15,8 +19,8 @@ struct DesktopApp: App { Window("Sign In", id: Windows.login.rawValue) { LoginForm() .environmentObject(appDelegate.state) - } - .windowResizability(.contentSize) + }.handlesExternalEvents(matching: Set()) // Don't handle deep links + .windowResizability(.contentSize) SwiftUI.Settings { SettingsView() .environmentObject(appDelegate.vpn) @@ -28,23 +32,32 @@ struct DesktopApp: App { .environmentObject(appDelegate.state) .environmentObject(appDelegate.fileSyncDaemon) .environmentObject(appDelegate.vpn) - } + }.handlesExternalEvents(matching: Set()) // Don't handle deep links } } @MainActor class AppDelegate: NSObject, NSApplicationDelegate { + private var logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "app-delegate") private var menuBar: MenuBarController? let vpn: CoderVPNService let state: AppState let fileSyncDaemon: MutagenDaemon + let urlHandler: URLHandler + let notifDelegate: NotifDelegate override init() { + notifDelegate = NotifDelegate() vpn = CoderVPNService() - state = AppState(onChange: vpn.configureTunnelProviderProtocol) + let state = AppState(onChange: vpn.configureTunnelProviderProtocol) + vpn.onStart = { + // We don't need this to have finished before the VPN actually starts + Task { await state.refreshDeploymentConfig() } + } if state.startVPNOnLaunch { vpn.startWhenReady = true } + self.state = state vpn.installSystemExtension() #if arch(arm64) let mutagenBinary = "mutagen-darwin-arm64" @@ -58,15 +71,21 @@ class AppDelegate: NSObject, NSApplicationDelegate { await fileSyncDaemon.tryStart() } self.fileSyncDaemon = fileSyncDaemon + urlHandler = URLHandler(state: state, vpn: vpn) + // `delegate` is weak + UNUserNotificationCenter.current().delegate = notifDelegate } func applicationDidFinishLaunching(_: Notification) { + // Init SVG loader + SDImageCodersManager.shared.addCoder(SDImageSVGCoder.shared) + menuBar = .init(menuBarExtra: FluidMenuBarExtra( title: "Coder Desktop", image: "MenuBarIcon", onAppear: { // If the VPN is enabled, it's likely the token isn't expired - guard case .disabled = self.vpn.state, self.state.hasSession else { return } + guard self.vpn.state != .connected, self.state.hasSession else { return } Task { @MainActor in await self.state.handleTokenExpiry() } @@ -116,6 +135,45 @@ class AppDelegate: NSObject, NSApplicationDelegate { func applicationShouldTerminateAfterLastWindowClosed(_: NSApplication) -> Bool { false } + + func applicationShouldHandleReopen(_: NSApplication, hasVisibleWindows _: Bool) -> Bool { + if !state.skipHiddenIconAlert, let menuBar, !menuBar.menuBarExtra.isVisible { + displayIconHiddenAlert() + } + return true + } + + func application(_: NSApplication, open urls: [URL]) { + guard let url = urls.first else { + // We only accept one at time, for now + return + } + do { try urlHandler.handle(url) } catch let handleError { + Task { + do { + try await sendNotification(title: "Failed to handle link", body: handleError.description) + } catch let notifError { + logger.error("Failed to send notification (\(handleError.description)): \(notifError)") + } + } + } + } + + private func displayIconHiddenAlert() { + let alert = NSAlert() + alert.alertStyle = .informational + alert.messageText = "Coder Desktop is hidden!" + alert.informativeText = """ + Coder Desktop is running, but there's no space in the menu bar for it's icon. + You can rearrange icons by holding command. + """ + alert.addButton(withTitle: "OK") + alert.addButton(withTitle: "Don't show again") + let resp = alert.runModal() + if resp == .alertSecondButtonReturn { + state.skipHiddenIconAlert = true + } + } } extension AppDelegate { diff --git a/Coder-Desktop/Coder-Desktop/Info.plist b/Coder-Desktop/Coder-Desktop/Info.plist index 5e59b253..4712604f 100644 --- a/Coder-Desktop/Coder-Desktop/Info.plist +++ b/Coder-Desktop/Coder-Desktop/Info.plist @@ -2,6 +2,21 @@ + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLIconFile + 1024Icon + CFBundleURLName + com.coder.Coder-Desktop + CFBundleURLSchemes + + coder + + + NSAppTransportSecurity