From bfc16765cffd6a68b650f8d90c59cf0bd5ceee3f Mon Sep 17 00:00:00 2001 From: TellowKrinkle Date: Sat, 20 Apr 2024 23:55:26 -0500 Subject: [PATCH] Input:Quartz: Release cursor during modal popups --- .../Quartz/QuartzKeyboardAndMouse.h | 3 ++ .../Quartz/QuartzKeyboardAndMouse.mm | 42 +++++++++++++++- Source/Core/InputCommon/QuartzInputMouse.h | 9 ++-- Source/Core/InputCommon/QuartzInputMouse.mm | 50 ++++++++++++++----- 4 files changed, 87 insertions(+), 17 deletions(-) diff --git a/Source/Core/InputCommon/ControllerInterface/Quartz/QuartzKeyboardAndMouse.h b/Source/Core/InputCommon/ControllerInterface/Quartz/QuartzKeyboardAndMouse.h index 31ab29aa9aa3..b919bb24ff5c 100644 --- a/Source/Core/InputCommon/ControllerInterface/Quartz/QuartzKeyboardAndMouse.h +++ b/Source/Core/InputCommon/ControllerInterface/Quartz/QuartzKeyboardAndMouse.h @@ -13,7 +13,10 @@ @interface DolWindowPositionObserver : NSObject - (instancetype)initWithView:(NSView*)view; +- (void)addKeyWindowChangeCallback:(void(*)(void*, bool))callback ctx:(void*)ctx; +- (void)removeKeyWindowChangeCallback:(void*)ctx; @property(readonly) NSRect frame; +@property(readonly, getter=isKeyWindow) bool keyWindow; @end #else diff --git a/Source/Core/InputCommon/ControllerInterface/Quartz/QuartzKeyboardAndMouse.mm b/Source/Core/InputCommon/ControllerInterface/Quartz/QuartzKeyboardAndMouse.mm index d3b322284884..b5574075c174 100644 --- a/Source/Core/InputCommon/ControllerInterface/Quartz/QuartzKeyboardAndMouse.mm +++ b/Source/Core/InputCommon/ControllerInterface/Quartz/QuartzKeyboardAndMouse.mm @@ -19,7 +19,9 @@ @implementation DolWindowPositionObserver NSView* _view; NSWindow* _window; NSRect _frame; + std::atomic _is_key; std::mutex _mtx; + std::vector> _key_callbacks; } - (NSRect)calcFrame @@ -35,7 +37,12 @@ - (instancetype)initWithView:(NSView*)view _view = view; _window = [view window]; _frame = [self calcFrame]; + _is_key.store([_window isKeyWindow], std::memory_order_relaxed); [_window addObserver:self forKeyPath:@"frame" options:0 context:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(keyWindowDidChange:) + name:NSWindowDidBecomeKeyNotification + object:nil]; } return self; } @@ -46,6 +53,38 @@ - (NSRect)frame return _frame; } +- (bool)isKeyWindow +{ + return _is_key.load(std::memory_order_relaxed); +} + +- (void)addKeyWindowChangeCallback:(void(*)(void*, bool))callback ctx:(void*)ctx; +{ + std::lock_guard guard(_mtx); + _key_callbacks.push_back(std::make_pair(callback, ctx)); +} + +- (void)removeKeyWindowChangeCallback:(void*)ctx +{ + std::lock_guard guard(_mtx); + _key_callbacks.erase(std::remove_if(_key_callbacks.begin(), _key_callbacks.end(), + [ctx](auto& entry){ return entry.second == ctx; }), + _key_callbacks.end()); +} + +- (void)keyWindowDidChange:(id)window +{ + bool key = [_window isKeyWindow]; + bool changed = key != _is_key.load(std::memory_order_relaxed); + _is_key.store(key, std::memory_order_relaxed); + if (changed) + { + std::lock_guard guard(_mtx); + for (const auto& callback : _key_callbacks) + callback.first(callback.second, key); + } +} + - (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change @@ -62,6 +101,7 @@ - (void)observeValueForKeyPath:(NSString*)keyPath - (void)dealloc { [_window removeObserver:self forKeyPath:@"frame"]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; } @end @@ -209,7 +249,7 @@ - (void)dealloc MainThreadInitialization(view); else dispatch_sync(dispatch_get_main_queue(), [this, view] { MainThreadInitialization(view); }); - prime::InitQuartzInputMouse(); + prime::InitQuartzInputMouse(m_window_pos_observer); // cursor, with a hax for-loop for (unsigned int i = 0; i < 4; ++i) diff --git a/Source/Core/InputCommon/QuartzInputMouse.h b/Source/Core/InputCommon/QuartzInputMouse.h index b559bc0c8af4..b2d0b31291e8 100644 --- a/Source/Core/InputCommon/QuartzInputMouse.h +++ b/Source/Core/InputCommon/QuartzInputMouse.h @@ -12,22 +12,25 @@ typedef void* id; namespace prime { -bool InitQuartzInputMouse(); +bool InitQuartzInputMouse(DolWindowPositionObserver* window); class QuartzInputMouse: public GenericMouse { public: - explicit QuartzInputMouse(); + explicit QuartzInputMouse(DolWindowPositionObserver* window); ~QuartzInputMouse(); void InputCallback(NSEvent* event); void UpdateInput() override; void LockCursorToGameWindow() override; + void UnlockCursor(); private: NSEvent*(^m_event_callback)(NSEvent*); - id m_monitor = nullptr; + std::atomic m_monitor{nullptr}; + DolWindowPositionObserver* m_window; std::atomic thread_dx; std::atomic thread_dy; + std::mutex m_mtx; }; } diff --git a/Source/Core/InputCommon/QuartzInputMouse.mm b/Source/Core/InputCommon/QuartzInputMouse.mm index 591df8db08ec..204b8a118dff 100644 --- a/Source/Core/InputCommon/QuartzInputMouse.mm +++ b/Source/Core/InputCommon/QuartzInputMouse.mm @@ -15,24 +15,34 @@ namespace prime { -bool InitQuartzInputMouse() +bool InitQuartzInputMouse(DolWindowPositionObserver* window) { - g_mouse_input.reset(new QuartzInputMouse()); + g_mouse_input.reset(new QuartzInputMouse(window)); return true; } -QuartzInputMouse::QuartzInputMouse() +QuartzInputMouse::QuartzInputMouse(DolWindowPositionObserver* window) { + m_window = window; m_event_callback = ^NSEvent*(NSEvent* event) { InputCallback(event); return event; }; + void (*key_callback)(void*, bool) = [](void* ctx, bool key){ + if (key) + return; + QuartzInputMouse* me = static_cast(ctx); + std::lock_guard lock(me->m_mtx); + me->UnlockCursor(); + }; + [m_window addKeyWindowChangeCallback:key_callback ctx:this]; } QuartzInputMouse::~QuartzInputMouse() { - if (m_monitor) - [NSEvent removeMonitor:m_monitor]; + [m_window removeKeyWindowChangeCallback:this]; + if (void* monitor = m_monitor.load(std::memory_order_relaxed)) + [NSEvent removeMonitor:(__bridge id)monitor]; } void QuartzInputMouse::InputCallback(NSEvent* event) @@ -50,8 +60,13 @@ bool InitQuartzInputMouse() void QuartzInputMouse::LockCursorToGameWindow() { - bool wants_locked = Host_RendererHasFocus() && cursor_locked; - bool is_locked = m_monitor; + bool wants_locked = Host_RendererHasFocus() && cursor_locked && [m_window isKeyWindow]; + bool is_locked = m_monitor.load(std::memory_order_relaxed); + if (wants_locked == is_locked) + return; + std::lock_guard lock(m_mtx); + wants_locked = Host_RendererHasFocus() && cursor_locked && [m_window isKeyWindow]; + is_locked = m_monitor.load(std::memory_order_relaxed); if (wants_locked == is_locked) return; if (wants_locked) @@ -62,17 +77,26 @@ bool InitQuartzInputMouse() // Clear any accumulated movement thread_dx.store(0, std::memory_order_relaxed); thread_dy.store(0, std::memory_order_relaxed); - m_monitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskMouseMoved - handler:m_event_callback]; + id monitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskMouseMoved + handler:m_event_callback]; + m_monitor.store((__bridge void*)monitor, std::memory_order_relaxed); } else { - CGAssociateMouseAndMouseCursorPosition(true); - [NSCursor unhide]; - [NSEvent removeMonitor:m_monitor]; - m_monitor = nullptr; + UnlockCursor(); cursor_locked = false; } } +void QuartzInputMouse::UnlockCursor() +{ + void* monitor = m_monitor.load(std::memory_order_relaxed); + if (!monitor) + return; + CGAssociateMouseAndMouseCursorPosition(true); + [NSCursor unhide]; + [NSEvent removeMonitor:(__bridge id)monitor]; + m_monitor.store(nullptr, std::memory_order_relaxed); +} + }