From 3e41e2150be5cc3a2d0748648115d0eaff9904c5 Mon Sep 17 00:00:00 2001 From: treeform Date: Sat, 27 Jun 2026 18:45:58 -0700 Subject: [PATCH] fix slow macos startup activation --- examples/slow_window_start.nim | 87 ++++++++++++++++++++++++++ src/windy/platforms/macos/macdefs.nim | 11 ++++ src/windy/platforms/macos/platform.nim | 38 +++++++++-- tests/run_all.nim | 1 + 4 files changed, 132 insertions(+), 5 deletions(-) create mode 100644 examples/slow_window_start.nim diff --git a/examples/slow_window_start.nim b/examples/slow_window_start.nim new file mode 100644 index 0000000..dacb130 --- /dev/null +++ b/examples/slow_window_start.nim @@ -0,0 +1,87 @@ +import + std/[os, strformat], + windy, vmath, boxy, opengl + +const + SlowStartMs = 3500 + +var + window: Window + bxy: Boxy + squareSize = 28.0 + clicked = false + moveCount = 0 + +proc drawFrame() = + ## Draws the cursor-following square and crosshair. + bxy.beginFrame(window.size) + bxy.saveTransform() + + let + scale = window.contentScale + logicalSize = window.size.vec2 / scale + cursorPos = window.mousePos.vec2 / scale + halfSize = squareSize / 2.0 + squareColor = + if clicked: + color(0.2, 0.7, 1.0, 0.9) + else: + color(1.0, 0.3, 0.3, 0.8) + + bxy.scale(scale) + bxy.drawRect( + rect(0, 0, logicalSize.x, logicalSize.y), + color(0.1, 0.1, 0.1, 1.0) + ) + bxy.drawRect( + rect( + cursorPos.x - halfSize, + cursorPos.y - halfSize, + squareSize, + squareSize + ), + squareColor + ) + bxy.drawRect( + rect(cursorPos.x - 1, 0, 2, logicalSize.y), + color(0.3, 1.0, 0.3, 0.5) + ) + bxy.drawRect( + rect(0, cursorPos.y - 1, logicalSize.x, 2), + color(0.3, 1.0, 0.3, 0.5) + ) + + bxy.restoreTransform() + bxy.endFrame() + window.swapBuffers() + +window = newWindow("Slow Window Start", ivec2(1280, 800)) +window.makeContextCurrent() +loadExtensions() +bxy = newBoxy() + +window.onMouseMove = proc() = + moveCount += 1 + +window.onButtonPress = proc(button: Button) = + case button: + of MouseLeft: + clicked = not clicked + echo &"Left click toggled color. Mouse moves seen: {moveCount}" + else: + discard + +window.onFrame = proc() = + drawFrame() + +echo "Slow window start repro." +echo "The window is visible before the event loop is allowed to run." +echo "After the delay, move the mouse and click the square." + +drawFrame() +echo "Delaying for 3,500 ms." +sleep(SlowStartMs) +echo "Delay done." + +while not window.closeRequested: + pollEvents() diff --git a/src/windy/platforms/macos/macdefs.nim b/src/windy/platforms/macos/macdefs.nim index 6ae3085..9d588c3 100644 --- a/src/windy/platforms/macos/macdefs.nim +++ b/src/windy/platforms/macos/macdefs.nim @@ -49,6 +49,7 @@ type NSNotification* = distinct NSObject NSEvent* = distinct NSObject NSDate* = distinct NSObject + NSRunLoop* = distinct NSObject NSRunLoopMode* = distinct NSString NSMenu* = distinct NSObject NSMenuItem* = distinct NSObject @@ -190,6 +191,16 @@ objc: ): NSEvent proc sendEvent*(self: NSApplication, x: NSEvent) proc distantPast*(class: typedesc[NSDate]): NSDate + proc dateWithTimeIntervalSinceNow*( + class: typedesc[NSDate], + x: float64 + ): NSDate + proc currentRunLoop*(class: typedesc[NSRunLoop]): NSRunLoop + proc runMode*( + self: NSRunLoop, + x: NSRunLoopMode, + beforeDate: NSDate + ): bool proc addItem*(self: NSMenu, x: NSMenuItem) proc initWithTitle*( self: NSMenuItem, diff --git a/src/windy/platforms/macos/platform.nim b/src/windy/platforms/macos/platform.nim index bb060a9..5e4a312 100644 --- a/src/windy/platforms/macos/platform.nim +++ b/src/windy/platforms/macos/platform.nim @@ -37,8 +37,11 @@ type # AppKit applies this state asynchronously via delegate callbacks. fullscreenState: bool minimizedState: bool + activationSettlePolls: int const + ActivationSettlePolls = 60 + ActivationSettleSeconds = 0.001 decoratedResizableWindowMask = NSWindowStyleMaskTitled or NSWindowStyleMaskClosable or NSWindowStyleMaskMiniaturizable or NSWindowStyleMaskResizable @@ -160,12 +163,25 @@ proc `title=`*(window: Window, title: string) = proc `icon=`*(window: Window, icon: Image) = window.state.icon = icon +proc requestActivation(window: Window) = + ## Shows the window and asks AppKit to activate this app. + window.inner.makeKeyAndOrderFront(0.ID) + NSApp.activateIgnoringOtherApps(true) + +proc settleRunLoop() = + ## Gives AppKit a short turn to deliver activation notifications. + discard NSRunLoop.currentRunLoop.runMode( + NSDefaultRunLoopMode, + NSDate.dateWithTimeIntervalSinceNow(ActivationSettleSeconds) + ) + proc `visible=`*(window: Window, visible: bool) = autoreleasepool: if visible: - window.inner.makeKeyAndOrderFront(0.ID) - NSApp.activateIgnoringOtherApps(true) + window.activationSettlePolls = ActivationSettlePolls + window.requestActivation() else: + window.activationSettlePolls = 0 window.inner.orderOut(0.ID) proc `style=`*(window: Window, windowStyle: WindowStyle) = @@ -320,7 +336,6 @@ proc applicationDidFinishLaunching( notification: NSNotification ): ID {.cdecl.} = NSApp.setPresentationOptions(NSApplicationPresentationDefault) - NSApp.activateIgnoringOtherApps(true) proc windowDidResize( self: ID, @@ -888,7 +903,22 @@ proc processFlagsChanged(event: NSEvent) = else: window.handleButtonPress(button) +proc settleActivationRequests() = + ## Replays activation while AppKit catches up after delayed startup. + var pending = false + for window in windows: + if window.activationSettlePolls == 0: + continue + dec window.activationSettlePolls + pending = true + window.requestActivation() + if pending: + settleRunLoop() + proc pollEvents*() = + autoreleasepool: + settleActivationRequests() + # Draw first (in case a message closes a window or similar) for window in windows: if window.onFrame != nil: @@ -1077,8 +1107,6 @@ proc newWindow*( result.fullscreenState = (result.inner.styleMask and NSWindowStyleMaskFullScreen) != 0 - pollEvents() # This can cause lots of issues, potential workaround needed - proc title*(window: Window): string = window.state.title diff --git a/tests/run_all.nim b/tests/run_all.nim index 7f9d292..fe782d4 100644 --- a/tests/run_all.nim +++ b/tests/run_all.nim @@ -21,6 +21,7 @@ const Examples = [ "property_changes", "screens", "scrollwheel", + "slow_window_start", "system_cursors", "tray", "websocket",