Skip to content
Open
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
20 changes: 20 additions & 0 deletions NotchBeat.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@
72522930F2F1674F82C61598 /* VolumeController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EBE1A010C9C568DF7EF30B9 /* VolumeController.swift */; };
7CE2042F64E912316794D314 /* ExpandedPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0A326FFF2D90FCF3CA1C1C /* ExpandedPlayerView.swift */; };
852BCE6356651B307827CFA9 /* LiquidGlassUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C001B28DA5319FDF1493DCE6 /* LiquidGlassUI.swift */; };
8CECBA1806341C3ACDF9666D /* GlobalHotKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = E114462CC31BFC8BFDB6571D /* GlobalHotKey.swift */; };
935A65F30FA58FFC1F3F9D3D /* NotchUIState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 797E3149A2DA6244346D8F0A /* NotchUIState.swift */; };
A879C2CBF374A2AA1F4532C1 /* SpotifyAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 709D63BE29CA0D22B1F331FB /* SpotifyAPIClient.swift */; };
AA4978CC7D080FB667E9CFA7 /* NotchMetrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D3213245D2618045EA009B9 /* NotchMetrics.swift */; };
B03F55117440084F1A9EEC47 /* LockScreenBackdropView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9404B9EDA37A59B491EF92E1 /* LockScreenBackdropView.swift */; };
B2D670FE2164BE0E5F5971D3 /* PseudoLockScreenController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68EB8D95060ED9D416B0AD35 /* PseudoLockScreenController.swift */; };
B543D6063513F9830EC5CE7A /* SpotifyAuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A3D07D6AF6BC0759ABB9C7 /* SpotifyAuthManager.swift */; };
B68F48D70CDCD0AA188976C7 /* CompactNotchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC64A71BE0F2587FD91FC2F8 /* CompactNotchView.swift */; };
BD741B1B99AB90F0372BE471 /* LyricsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E371951F75508989A077CFC5 /* LyricsService.swift */; };
Expand All @@ -35,24 +38,37 @@
5CE226CBE5A055D67B715329 /* MenuBarContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarContentView.swift; sourceTree = "<group>"; };
5DDE8CB4F434738E3DA92CE4 /* PKCE.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PKCE.swift; sourceTree = "<group>"; };
5EBE1A010C9C568DF7EF30B9 /* VolumeController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VolumeController.swift; sourceTree = "<group>"; };
68EB8D95060ED9D416B0AD35 /* PseudoLockScreenController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PseudoLockScreenController.swift; sourceTree = "<group>"; };
709D63BE29CA0D22B1F331FB /* SpotifyAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpotifyAPIClient.swift; sourceTree = "<group>"; };
797E3149A2DA6244346D8F0A /* NotchUIState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotchUIState.swift; sourceTree = "<group>"; };
7C75977B9D3E98AEEFDDE820 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7D3213245D2618045EA009B9 /* NotchMetrics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotchMetrics.swift; sourceTree = "<group>"; };
7F19FAF1275C541AB173F1F7 /* MediaMetadataService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaMetadataService.swift; sourceTree = "<group>"; };
81D658A3FC6D7C63D43E2149 /* LyricsPanelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LyricsPanelView.swift; sourceTree = "<group>"; };
898EE9859801623F36E78015 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
9404B9EDA37A59B491EF92E1 /* LockScreenBackdropView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockScreenBackdropView.swift; sourceTree = "<group>"; };
A862BCC79D94A263060B84F3 /* NotchRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotchRootView.swift; sourceTree = "<group>"; };
C001B28DA5319FDF1493DCE6 /* LiquidGlassUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiquidGlassUI.swift; sourceTree = "<group>"; };
C8436228A3EE4EBCB67BAE67 /* NotchBeat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NotchBeat.app; sourceTree = BUILT_PRODUCTS_DIR; };
C99A7818B9692BE3A3237E75 /* NotchWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotchWindowController.swift; sourceTree = "<group>"; };
D463E53A2075B4EFF62193C8 /* GestureEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GestureEngine.swift; sourceTree = "<group>"; };
D87D141A7BB7819335674E9B /* NotchLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotchLayout.swift; sourceTree = "<group>"; };
E114462CC31BFC8BFDB6571D /* GlobalHotKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalHotKey.swift; sourceTree = "<group>"; };
E371951F75508989A077CFC5 /* LyricsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LyricsService.swift; sourceTree = "<group>"; };
EC64A71BE0F2587FD91FC2F8 /* CompactNotchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompactNotchView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXGroup section */
30DAA1F3C75615A20920FBC1 /* PseudoLockScreenController */ = {
isa = PBXGroup;
children = (
E114462CC31BFC8BFDB6571D /* GlobalHotKey.swift */,
9404B9EDA37A59B491EF92E1 /* LockScreenBackdropView.swift */,
68EB8D95060ED9D416B0AD35 /* PseudoLockScreenController.swift */,
);
path = PseudoLockScreenController;
sourceTree = "<group>";
};
37C1C34D15B0CD5F0D2E277D /* GestureEngine */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -114,6 +130,7 @@
7FE4B98368DF4DFDFD86788F /* LyricsService */,
4B98F2266DC599916DFF0B2D /* MediaMetadataService */,
F2DD7F4510AC85ED32374B6D /* NotchWindowController */,
30DAA1F3C75615A20920FBC1 /* PseudoLockScreenController */,
611867AB07875D7872A85B4F /* VolumeController */,
);
path = NotchBeat;
Expand Down Expand Up @@ -214,7 +231,9 @@
B68F48D70CDCD0AA188976C7 /* CompactNotchView.swift in Sources */,
7CE2042F64E912316794D314 /* ExpandedPlayerView.swift in Sources */,
0732678AD96EAF7126898805 /* GestureEngine.swift in Sources */,
8CECBA1806341C3ACDF9666D /* GlobalHotKey.swift in Sources */,
852BCE6356651B307827CFA9 /* LiquidGlassUI.swift in Sources */,
B03F55117440084F1A9EEC47 /* LockScreenBackdropView.swift in Sources */,
316A63FA9FCD92E88E0BD9A0 /* LyricsPanelView.swift in Sources */,
BD741B1B99AB90F0372BE471 /* LyricsService.swift in Sources */,
2D36EB8D1A9988C8C2B7B781 /* MediaMetadataService.swift in Sources */,
Expand All @@ -226,6 +245,7 @@
935A65F30FA58FFC1F3F9D3D /* NotchUIState.swift in Sources */,
E7227127738431B9CA49DA60 /* NotchWindowController.swift in Sources */,
1B21A56838FE8FD8C02B0907 /* PKCE.swift in Sources */,
B2D670FE2164BE0E5F5971D3 /* PseudoLockScreenController.swift in Sources */,
A879C2CBF374A2AA1F4532C1 /* SpotifyAPIClient.swift in Sources */,
B543D6063513F9830EC5CE7A /* SpotifyAuthManager.swift in Sources */,
72522930F2F1674F82C61598 /* VolumeController.swift in Sources */,
Expand Down
7 changes: 7 additions & 0 deletions NotchBeat/App/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,16 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
let authManager = SpotifyAuthManager()
private(set) lazy var metadataService = MediaMetadataService(auth: authManager)
private lazy var notchWindowController = NotchWindowController(metadata: metadataService)
let lockScreenController = PseudoLockScreenController()

func applicationDidFinishLaunching(_ notification: Notification) {
notchWindowController.show()
metadataService.startPolling()
// Surface B: register the global ⌃⌘L toggle (also available in the menu).
lockScreenController.installHotKey()
}

func toggleLockScreen() {
lockScreenController.toggle()
}
}
5 changes: 5 additions & 0 deletions NotchBeat/App/MenuBarContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import SwiftUI

struct MenuBarContentView: View {
@ObservedObject var auth: SpotifyAuthManager
let onToggleLockScreen: () -> Void

var body: some View {
if !auth.isConfigured {
Expand All @@ -25,6 +26,10 @@ struct MenuBarContentView: View {
}
}
Divider()
Button("Toggle Lock Screen (⌃⌘L)") {
onToggleLockScreen()
}
Comment on lines +29 to +31

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Instead of hardcoding the shortcut string (⌃⌘L) in the button's title, use SwiftUI's .keyboardShortcut modifier. This allows macOS to automatically render the shortcut beautifully on the right side of the menu item and ensures it works natively when the menu is open.

Suggested change
Button("Toggle Lock Screen (⌃⌘L)") {
onToggleLockScreen()
}
Button("Toggle Lock Screen") {
onToggleLockScreen()
}
.keyboardShortcut("l", modifiers: [.control, .command])

Divider()
Button("Quit NotchBeat") {
NSApplication.shared.terminate(nil)
}
Expand Down
5 changes: 4 additions & 1 deletion NotchBeat/App/NotchBeatApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ struct NotchBeatApp: App {

var body: some Scene {
MenuBarExtra("NotchBeat", systemImage: "music.note") {
MenuBarContentView(auth: appDelegate.authManager)
MenuBarContentView(
auth: appDelegate.authManager,
onToggleLockScreen: { appDelegate.toggleLockScreen() }
)
}
}
}
90 changes: 90 additions & 0 deletions NotchBeat/PseudoLockScreenController/GlobalHotKey.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import AppKit
import Carbon.HIToolbox

/// A process-global hotkey via Carbon's `RegisterEventHotKey` — the standard,
/// permission-free way for a menu-bar (LSUIElement) agent to get a system-wide
/// shortcut. NSEvent global monitors would need Accessibility / Input
/// Monitoring permission and can't consume the event; RegisterEventHotKey
/// needs neither and fires regardless of which app is focused.
final class GlobalHotKey {
struct Combo {
let keyCode: UInt32
let modifiers: UInt32

/// ⌃⌘L — toggle the lock-screen surface (PRD §3.2 trigger).
static let toggleLockScreen = Combo(
keyCode: UInt32(kVK_ANSI_L),
modifiers: UInt32(controlKey | cmdKey)
)
}

private var ref: EventHotKeyRef?
private let id: UInt32
private let callback: () -> Void

// Carbon dispatches to a C function pointer with no captured context, so we
// route hotkey presses back to instances through a shared id → instance map.
private static var registry: [UInt32: GlobalHotKey] = [:]
private static var nextID: UInt32 = 1
private static var handlerInstalled = false
private static let signature: OSType = 0x4E_54_42_54 // 'NTBT'

init(_ combo: Combo, callback: @escaping () -> Void) {
self.callback = callback
self.id = GlobalHotKey.nextID
GlobalHotKey.nextID += 1
GlobalHotKey.registry[id] = self
Comment on lines +25 to +36

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The registry dictionary holds a strong reference to GlobalHotKey instances. Since each instance registers itself in init, this creates a retain cycle where the registry keeps the instance alive indefinitely, preventing deinit from ever being called. This leaks the hotkey and its callback. Use a weak wrapper to store weak references in the registry.

    private struct WeakBox {
        weak var value: GlobalHotKey?
    }

    // Carbon dispatches to a C function pointer with no captured context, so we
    // route hotkey presses back to instances through a shared id → instance map.
    // We use weak references to avoid a retain cycle, as the registry would otherwise
    // keep the hotkey instances alive indefinitely.
    private static var registry: [UInt32: WeakBox] = [:]
    private static var nextID: UInt32 = 1
    private static var handlerInstalled = false
    private static let signature: OSType = 0x4E_54_42_54 // 'NTBT'

    init(_ combo: Combo, callback: @escaping () -> Void) {
        self.callback = callback
        self.id = GlobalHotKey.nextID
        GlobalHotKey.nextID += 1
        GlobalHotKey.registry[id] = WeakBox(value: self)


GlobalHotKey.installDispatchHandlerIfNeeded()
let hotKeyID = EventHotKeyID(signature: GlobalHotKey.signature, id: id)
RegisterEventHotKey(
combo.keyCode,
combo.modifiers,
hotKeyID,
GetApplicationEventTarget(),
0,
&ref
)
Comment on lines +40 to +47

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Check the return status of RegisterEventHotKey to detect and log registration failures (e.g., if another application has already registered the shortcut).

Suggested change
RegisterEventHotKey(
combo.keyCode,
combo.modifiers,
hotKeyID,
GetApplicationEventTarget(),
0,
&ref
)
let status = RegisterEventHotKey(
combo.keyCode,
combo.modifiers,
hotKeyID,
GetApplicationEventTarget(),
0,
&ref
)
if status != noErr {
print("Failed to register global hotkey: \(status)")
}

}

deinit {
if let ref { UnregisterEventHotKey(ref) }
GlobalHotKey.registry[id] = nil
}
Comment on lines +50 to +53

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Update deinit to remove the entry from the registry safely on the main thread.

    deinit {
        if let ref { UnregisterEventHotKey(ref) }
        let targetID = id
        DispatchQueue.main.async {
            GlobalHotKey.registry.removeValue(forKey: targetID)
        }
    }


private static func installDispatchHandlerIfNeeded() {
guard !handlerInstalled else { return }
handlerInstalled = true

var spec = EventTypeSpec(
eventClass: OSType(kEventClassKeyboard),
eventKind: UInt32(kEventHotKeyPressed)
)
InstallEventHandler(
GetApplicationEventTarget(),
{ _, event, _ -> OSStatus in
var hkID = EventHotKeyID()
let status = GetEventParameter(
event,
EventParamName(kEventParamDirectObject),
EventParamType(typeEventHotKeyID),
nil,
MemoryLayout<EventHotKeyID>.size,
nil,
&hkID
)
if status == noErr {
let targetID = hkID.id
DispatchQueue.main.async {
GlobalHotKey.registry[targetID]?.callback()
}
Comment on lines +78 to +80

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Update the event handler dispatch to access the weak reference's value.

Suggested change
DispatchQueue.main.async {
GlobalHotKey.registry[targetID]?.callback()
}
DispatchQueue.main.async {
GlobalHotKey.registry[targetID]?.value?.callback()
}

}
return noErr
},
1,
&spec,
nil,
nil
)
}
}
69 changes: 69 additions & 0 deletions NotchBeat/PseudoLockScreenController/LockScreenBackdropView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import AppKit
import SwiftUI

/// The lock-screen backdrop (PRD §3.2 L1, reference image 1): the desktop
/// wallpaper filling the screen with a large clock + date near the top. The
/// now-playing widget is layered on in phase 6.
struct LockScreenBackdropView: View {
var body: some View {
ZStack(alignment: .top) {
WallpaperView()
ClockView()
.padding(.top, 64)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.ignoresSafeArea()
}
}
Comment on lines +7 to +17

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Update LockScreenBackdropView to accept and pass the target NSScreen to WallpaperView.

Suggested change
struct LockScreenBackdropView: View {
var body: some View {
ZStack(alignment: .top) {
WallpaperView()
ClockView()
.padding(.top, 64)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.ignoresSafeArea()
}
}
struct LockScreenBackdropView: View {
let screen: NSScreen
var body: some View {
ZStack(alignment: .top) {
WallpaperView(screen: screen)
ClockView()
.padding(.top, 64)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.ignoresSafeArea()
}
}


/// Fills the window with the current desktop wallpaper (aspect-fill), falling
/// back to a dark gradient if it can't be read.
private struct WallpaperView: View {
@State private var image: NSImage?

var body: some View {
Comment on lines +21 to +24

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Pass the specific NSScreen instance to WallpaperView instead of relying on NSScreen.main. This ensures the correct wallpaper is loaded on multi-monitor setups where the lock screen window might be shown on a different screen than the active key window.

private struct WallpaperView: View {
    let screen: NSScreen
    @State private var image: NSImage?

    var body: some View {

GeometryReader { geo in
Group {
if let image {
Image(nsImage: image)
.resizable()
.aspectRatio(contentMode: .fill)
} else {
LinearGradient(
colors: [Color(white: 0.18), .black],
startPoint: .top,
endPoint: .bottom
)
}
}
.frame(width: geo.size.width, height: geo.size.height)
.clipped()
}
.ignoresSafeArea()
.task { loadWallpaper() }
}

private func loadWallpaper() {
guard let screen = NSScreen.main,
let url = NSWorkspace.shared.desktopImageURL(for: screen) else { return }
image = NSImage(contentsOf: url)
}
Comment on lines +46 to +50

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

NSImage(contentsOf:) performs synchronous disk I/O to load and decode the wallpaper image. Running this on the main thread (which is the default context for SwiftUI .task blocks) can cause noticeable UI stuttering or freezes when presenting the lock screen. Load the image asynchronously on a background queue.

    private func loadWallpaper() {
        guard let url = NSWorkspace.shared.desktopImageURL(for: screen) else { return }
        
        // Load the image asynchronously on a background queue to avoid blocking the main thread
        DispatchQueue.global(qos: .userInitiated).async {
            if let loadedImage = NSImage(contentsOf: url) {
                DispatchQueue.main.async {
                    self.image = loadedImage
                }
            }
        }
    }

}

/// Big time + date, updating each second, styled like the iOS/macOS lock screen.
private struct ClockView: View {
var body: some View {
TimelineView(.periodic(from: .now, by: 1)) { context in
VStack(spacing: 0) {
Text(context.date, format: .dateTime.weekday(.abbreviated).month(.abbreviated).day())
.font(.system(size: 22, weight: .semibold, design: .rounded))
.foregroundStyle(.white.opacity(0.92))
Text(context.date, format: .dateTime.hour().minute())
.font(.system(size: 96, weight: .bold, design: .rounded))
.foregroundStyle(.white)
.monospacedDigit()
}
.shadow(color: .black.opacity(0.35), radius: 18, y: 4)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import AppKit
import SwiftUI

/// Surface B: a borderless, fullscreen window on the built-in display that
/// recreates a lock-screen backdrop (wallpaper + clock). It hosts the same
/// compact → expand → lyrics flow as the notch (added in phases 6–8); phase 5
/// is just the shell + trigger.
///
/// This is NOT the real macOS lock screen — that can't host third-party UI
/// (PRD §4). It's an ordinary top-level window shown while logged in; it does
/// not overlay the password screen or gate unlock.
@MainActor
final class PseudoLockScreenController {
private var window: LockScreenWindow?
private var hotKey: GlobalHotKey?
private(set) var isVisible = false

/// Registers the global ⌃⌘L toggle. Call once at launch.
func installHotKey() {
hotKey = GlobalHotKey(.toggleLockScreen) { [weak self] in
self?.toggle()
}
}

func toggle() {
isVisible ? hide() : show()
}

func show() {
guard !isVisible else { return }
// The lock screen belongs on the built-in display; fall back to the
// main screen if there's no notched screen (e.g. clamshell).
guard let screen = NotchMetrics.builtInNotchedScreen() ?? NSScreen.main else { return }

let window = LockScreenWindow(
contentRect: screen.frame,
styleMask: [.borderless],
backing: .buffered,
defer: false
)
window.setFrame(screen.frame, display: true)
window.isOpaque = true
window.backgroundColor = .black
// Above the menu bar and normal windows so it reads as a full takeover,
// like a lock screen. Esc / the hotkey / click-away dismiss it.
window.level = .screenSaver
window.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary, .stationary, .ignoresCycle]
window.isReleasedWhenClosed = false
window.onDismiss = { [weak self] in self?.hide() }
window.contentView = NSHostingView(rootView: LockScreenBackdropView())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Pass the target screen to LockScreenBackdropView when initializing the hosting view.

Suggested change
window.contentView = NSHostingView(rootView: LockScreenBackdropView())
window.contentView = NSHostingView(rootView: LockScreenBackdropView(screen: screen))


window.makeKeyAndOrderFront(nil)
NSApp.activate(ignoringOtherApps: true)

self.window = window
isVisible = true
}

func hide() {
window?.orderOut(nil)
window = nil
isVisible = false
}
}

/// Borderless window that can take key focus (so Esc works) and reports
/// dismissal intents (Esc key) back to the controller.
final class LockScreenWindow: NSWindow {
var onDismiss: (() -> Void)?

override var canBecomeKey: Bool { true }
override var canBecomeMain: Bool { true }

/// Esc — AppKit routes it here as the standard "cancel" action.
override func cancelOperation(_ sender: Any?) {
onDismiss?()
}

override func keyDown(with event: NSEvent) {
if event.keyCode == 53 { // Esc, as a backup to cancelOperation
onDismiss?()
} else {
super.keyDown(with: event)
}
}
Comment on lines +79 to +85

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The keyDown override is redundant because cancelOperation(_:) is already overridden and is the standard, system-provided way AppKit handles the Escape key. Bypassing or duplicating this with a hardcoded key code 53 is unnecessary and can be removed.

    // Rely on cancelOperation(_:) to handle the Escape key idiomatic to AppKit.

}