#5901

fix(macos): handle non-mouse events in clickCount to prevent system tray crash

Openeduardomozartwants to merge intofrom

Description

On macOS 15+ (Sequoia / Darwin 24+), status bar item tracking routed through FrontBoardServices and NSSceneStatusItem delivers NSEventTypeAppKitDefined (type 13, printed as KitDefined) events when interacting with the menu bar icon.

When Qt's Cocoa platform plugin (libqcocoa.dylib in QCocoaSystemTrayIcon) receives the internal notification from NSMenuTrackingSession, it directly invokes [NSApp.currentEvent clickCount] without verifying whether the event is actually a mouse event. In AppKit, invoking -[NSEvent clickCount] on a non-mouse event raises an NSInternalInconsistencyException, terminating Sunshine with an unhandled exception abort:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid message sent to event "NSEvent: type=KitDefined loc=(0,900) time=555614.8 flags=0x40 win=0x0 winNum=0 ctxt=0x0 subtype=2 data1=0 data2=64"'
*** First throw call stack:
(
	0   CoreFoundation                      0x000000018fb3a8c0 __exceptionPreprocess + 176
	1   libobjc.A.dylib                     0x000000018f59860c objc_exception_throw + 88
	2   Foundation                          0x00000001912814d8 -[NSCalendarDate initWithCoder:] + 0
	3   AppKit                              0x0000000194218f80 -[NSEvent clickCount] + 152
	4   libqcocoa.dylib                     0x00000001076bb49c qt_plugin_instance + 186712
	5   CoreFoundation                      0x000000018fabf1a0 __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 148
	6   CoreFoundation                      0x000000018fb4e394 ___CFXRegistrationPost_block_invoke + 92
	7   CoreFoundation                      0x000000018fb4e2d8 _CFXRegistrationPost + 440
	8   CoreFoundation                      0x000000018fa8b3d0 _CFXNotificationPost + 688
	9   Foundation                          0x000000019117c38c -[NSNotificationCenter postNotificationName:object:userInfo:] + 88
	10  AppKit                              0x000000019462d154 -[NSMenuTrackingSession sendBeginTrackingNotifications] + 188
	11  AppKit                              0x0000000194634b98 -[NSMenuTrackingSession beginTrackingSession:] + 644
	12  AppKit                              0x00000001945e1bac +[NSContextMenuImpl presentPopup:fromView:withContext:animated:async:completionBlock:] + 432
	13  AppKit                              0x0000000194926880 _NSPopUpMenu + 1992
	14  AppKit                              0x0000000194925fe0 -[NSCocoaMenuImpl popUpMenu:atLocation:width:view:selectedItemIndexProvider:font:positioningRect:flags:options:] + 432
	15  AppKit                              0x0000000194925e08 -[NSCocoaMenuImpl popUpMenu:atLocation:width:forView:withSelectedItem:withFont:withFlags:withOptions:] + 96
	16  AppKit                              0x00000001948e682c +[NSStatusBarButtonCell popUpStatusBarMenu:ofItem:ofBar:inRect:ofView:withEvent:] + 744
	17  AppKit                              0x00000001948eed1c -[NSStatusItem popUpStatusItemMenu:] + 108
	18  AppKit                              0x00000001948ef348 -[NSStatusItem _beginExpandedInterfaceSession:] + 600
	19  AppKit                              0x00000001946ba60c -[NSSceneStatusItem _beginExpandedInterfaceSession:] + 192
	20  AppKit                              0x00000001946b91b0 __33-[NSSceneStatusItem _setupScene:]_block_invoke.25 + 320
	21  AppKit                              0x0000000194f06618 __swift_closure_destructor.106 + 28764
	22  AppKit                              0x0000000194f06794 __swift_closure_destructor.106 + 29144
	23  FrontBoardServices                  0x00000001ac1a7cc8 -[FBSSceneObserver scene:handlePrivateActions:] + 80
	24  FrontBoardServices                  0x00000001ac16b864 -[FBSScene updater:didReceiveActions:forExtension:] + 736
	25  FrontBoardServices                  0x00000001ac193e28 __71-[FBSWorkspaceScenesClient sceneID:sendActions:toExtension:completion:]_block_invoke + 204
	26  FrontBoardServices                  0x00000001ac14ebbc -[FBSWorkspace _calloutQueue_executeCalloutFromSource:withBlock:] + 180
	27  libdispatch.dylib                   0x000000018f8625a0 _dispatch_client_callout + 16
	28  libdispatch.dylib                   0x000000018f84d700 _dispatch_block_invoke_direct + 284
	29  BoardServices                       0x00000001ac236ca4 __BSSERVICEMAINRUNLOOPQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 48
	30  BoardServices                       0x00000001ac235ac8 BSServiceMainRunLoopSourceHandler + 232
	31  CoreFoundation                      0x000000018faca944 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 28
	32  CoreFoundation                      0x000000018faca8d8 __CFRunLoopDoSource0 + 224
	33  CoreFoundation                      0x000000018faca610 __CFRunLoopDoSources0 + 232
	34  CoreFoundation                      0x000000018fac91e0 __CFRunLoopRun + 832
	35  CoreFoundation                      0x000000018fba382c _CFRunLoopRunSpecificWithOptions + 536
	36  HIToolbox                           0x000000019cefc590 RunCurrentEventLoopInMode + 320
	37  HIToolbox                           0x000000019ceff91c ReceiveNextEventCommon + 492
	38  HIToolbox                           0x000000019d082380 _BlockUntilNextEventMatchingListInMode + 48
	39  AppKit                              0x00000001943c329c _DPSBlockUntilNextEventMatchingListInMode + 292
	40  AppKit                              0x00000001940804fc _DPSNextEvent + 580
	41  AppKit                              0x00000001948f8748 -[NSApplication(NSEventRouting) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 780
	42  AppKit                              0x00000001948f826c -[NSApplication(NSEventRouting) nextEventMatchingMask:untilDate:inMode:dequeue:] + 292
	43  AppKit                              0x000000019407390c -[NSApplication run] + 396
	44  libqcocoa.dylib                     0x000000010769b99c qt_plugin_instance + 56920
	45  QtCore                              0x00000001071a6ba0 _ZN10QEventLoop4execE6QFlagsINS_17ProcessEventsFlagEE + 588
	46  QtCore                              0x000000010719d1a8 _ZN16QCoreApplication4execEv + 228
	47  Sunshine                            0x0000000105028110 _ZN10QtTrayMenu4loopEi + 232
	48  Sunshine                            0x000000010456f53c _Z14mainThreadLoopRKNSt3__110shared_ptrIN4safe7event_tIbEEEE + 212
	49  Sunshine                            0x0000000104570d90 main + 5668
	50  dyld                                0x000000018f633e80 start + 6688
)
libc++abi: terminating due to uncaught exception of type NSException

This PR resolves the crash by:

  1. Adding platf::install_event_compatibility_fixes() in src/platform/macos/misc.mm, which swizzles -[NSEvent clickCount] using an Objective-C category.
  2. For mouse event types (LeftMouseDown/Up, RightMouseDown/Up, OtherMouseDown/Up), the original implementation is called with @try / @catch fallback.
  3. For non-mouse events (such as NSEventTypeAppKitDefined), it immediately returns 1 instead of throwing.
  4. Calling platf::install_event_compatibility_fixes() early in src/main.cpp and prior to tray_init() in src/system_tray.cpp.

Screenshot

Issues Fixed or Closed

Roadmap Issues

Testing

Added automated unit regression tests in tests/unit/platform/macos/test_event_compat.mm (MacosEventCompatTest):

  • IdempotentInstallation: Verifies safe idempotent execution across multiple setup calls.
  • NonMouseEventClickCountReturnsFallbackWithoutThrowing: Verifies that querying clickCount on NSEventTypeAppKitDefined and NSEventTypeSystemDefined events returns 1 without throwing NSInternalInconsistencyException.
  • MouseEventClickCountPreserved: Verifies that single, double, and right-click counts are correctly preserved for mouse events.

Commands run:

cmake --build cmake-build-debug --target test_sunshine -j8
./cmake-build-debug/tests/test_sunshine --gtest_filter="Macos*:SystemTrayTest.*"
uv run --locked lb-update-clang-format

All 16 tests in MacosPermissionsTest, SystemTrayTest, MacosAudioPermissionTest, and MacosEventCompatTest passed.

Type of Change

  • feat: New feature (non-breaking change which adds functionality)
  • fix: Bug fix (non-breaking change which fixes an issue)
  • docs: Documentation only changes
  • style: Changes that do not affect the meaning of the code (white-space, formatting, missing semicolons, etc.)
  • refactor: Code change that neither fixes a bug nor adds a feature
  • perf: Code change that improves performance
  • test: Adding missing tests or correcting existing tests
  • build: Changes that affect the build system or external dependencies
  • ci: Changes to CI configuration files and scripts
  • chore: Other changes that don't modify src or test files
  • revert: Reverts a previous commit
  • BREAKING CHANGE: Introduces a breaking change (can be combined with any type above)

Checklist

  • Code follows the style guidelines of this project
  • Code has been self-reviewed
  • Code has been commented, particularly in hard-to-understand areas
  • Code docstring/documentation-blocks for new or existing methods/components have been added or updated
  • Tests have been added or updated for new or modified functionality, including regression tests for bug fixes

AI Usage

See our AI usage policy.

  • AI tools were used while preparing this PR

AI assisted in analyzing the macOS NSInternalInconsistencyException crash call stack, implementing the AppKit swizzle compatibility shim, and developing unit tests.

This pull request was generated by Antigravity, Gemini 3.8 Flash, high thinking level.