InputCommon:QuarzKB&M: Use KVO to watch window position

CGWindowListCreateDescriptionFromArray would block for up to ~1ms, which isn't a great thing to do on the main emulation thread
This commit is contained in:
TellowKrinkle 2022-08-12 03:02:47 -05:00
parent b96bc4267e
commit 798b241832
2 changed files with 82 additions and 29 deletions

View File

@ -8,6 +8,12 @@
#include "Common/Matrix.h" #include "Common/Matrix.h"
#include "InputCommon/ControllerInterface/CoreDevice.h" #include "InputCommon/ControllerInterface/CoreDevice.h"
#ifdef __OBJC__
@class DolWindowPositionObserver;
#else
class DolWindowPositionObserver;
#endif
namespace ciface::Quartz namespace ciface::Quartz
{ {
std::string KeycodeToName(const CGKeyCode keycode); std::string KeycodeToName(const CGKeyCode keycode);
@ -59,13 +65,16 @@ public:
void UpdateInput() override; void UpdateInput() override;
explicit KeyboardAndMouse(void* view); explicit KeyboardAndMouse(void* view);
~KeyboardAndMouse() override;
std::string GetName() const override; std::string GetName() const override;
std::string GetSource() const override; std::string GetSource() const override;
private: private:
void MainThreadInitialization(void* view);
Common::Vec2 m_cursor; Common::Vec2 m_cursor;
uint32_t m_windowid; DolWindowPositionObserver* m_window_pos_observer;
}; };
} // namespace ciface::Quartz } // namespace ciface::Quartz

View File

@ -4,6 +4,7 @@
#include "InputCommon/ControllerInterface/Quartz/QuartzKeyboardAndMouse.h" #include "InputCommon/ControllerInterface/Quartz/QuartzKeyboardAndMouse.h"
#include <map> #include <map>
#include <mutex>
#include <Carbon/Carbon.h> #include <Carbon/Carbon.h>
#include <Cocoa/Cocoa.h> #include <Cocoa/Cocoa.h>
@ -12,6 +13,64 @@
#include "InputCommon/ControllerInterface/ControllerInterface.h" #include "InputCommon/ControllerInterface/ControllerInterface.h"
/// Helper class to get window position data from threads other than the main thread
@interface DolWindowPositionObserver : NSObject
- (instancetype)initWithView:(NSView*)view;
@property(readonly) NSRect frame;
@end
@implementation DolWindowPositionObserver
{
NSWindow* _window;
NSRect _frame;
std::mutex _mtx;
}
- (NSRect)calcFrame
{
return [_window frame];
}
- (instancetype)initWithView:(NSView*)view
{
self = [super init];
if (self)
{
_window = [view window];
_frame = [self calcFrame];
[_window addObserver:self forKeyPath:@"frame" options:0 context:nil];
}
return self;
}
- (NSRect)frame
{
std::lock_guard<std::mutex> guard(_mtx);
return _frame;
}
- (void)observeValueForKeyPath:(NSString*)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey, id>*)change
context:(void*)context
{
if (object == _window)
{
NSRect new_frame = [self calcFrame];
std::lock_guard<std::mutex> guard(_mtx);
_frame = new_frame;
}
}
- (void)dealloc
{
[_window removeObserver:self forKeyPath:@"frame"];
}
@end
namespace ciface::Quartz namespace ciface::Quartz
{ {
std::string KeycodeToName(const CGKeyCode keycode) std::string KeycodeToName(const CGKeyCode keycode)
@ -149,20 +208,12 @@ KeyboardAndMouse::KeyboardAndMouse(void* view)
AddCombinedInput("Shift", {"Left Shift", "Right Shift"}); AddCombinedInput("Shift", {"Left Shift", "Right Shift"});
AddCombinedInput("Ctrl", {"Left Control", "Right Control"}); AddCombinedInput("Ctrl", {"Left Control", "Right Control"});
NSView* cocoa_view = (__bridge NSView*)view;
// PopulateDevices may be called on the Emuthread, so we need to ensure that // PopulateDevices may be called on the Emuthread, so we need to ensure that
// these UI APIs are only ever called on the main thread. // these UI APIs are only ever called on the main thread.
if ([NSThread isMainThread]) if ([NSThread isMainThread])
{ MainThreadInitialization(view);
m_windowid = [[cocoa_view window] windowNumber];
}
else else
{ dispatch_sync(dispatch_get_main_queue(), [this, view] { MainThreadInitialization(view); });
dispatch_sync(dispatch_get_main_queue(), ^{
m_windowid = [[cocoa_view window] windowNumber];
});
}
// cursor, with a hax for-loop // cursor, with a hax for-loop
for (unsigned int i = 0; i < 4; ++i) for (unsigned int i = 0; i < 4; ++i)
@ -173,26 +224,19 @@ KeyboardAndMouse::KeyboardAndMouse(void* view)
AddInput(new Button(kCGMouseButtonCenter)); AddInput(new Button(kCGMouseButtonCenter));
} }
// Very important that this is here
// C++ and ObjC++ have different views of the header, and only ObjC++'s will deallocate properly
KeyboardAndMouse::~KeyboardAndMouse() = default;
void KeyboardAndMouse::MainThreadInitialization(void* view)
{
NSView* cocoa_view = (__bridge NSView*)view;
m_window_pos_observer = [[DolWindowPositionObserver alloc] initWithView:cocoa_view];
}
void KeyboardAndMouse::UpdateInput() void KeyboardAndMouse::UpdateInput()
{ {
CGRect bounds = CGRectZero; NSRect bounds = [m_window_pos_observer frame];
CGWindowID windowid[1] = {m_windowid};
CFArrayRef windowArray = CFArrayCreate(nullptr, (const void**)windowid, 1, nullptr);
CFArrayRef windowDescriptions = CGWindowListCreateDescriptionFromArray(windowArray);
CFDictionaryRef windowDescription =
static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(windowDescriptions, 0));
if (CFDictionaryContainsKey(windowDescription, kCGWindowBounds))
{
CFDictionaryRef boundsDictionary =
static_cast<CFDictionaryRef>(CFDictionaryGetValue(windowDescription, kCGWindowBounds));
if (boundsDictionary != nullptr)
CGRectMakeWithDictionaryRepresentation(boundsDictionary, &bounds);
}
CFRelease(windowDescriptions);
CFRelease(windowArray);
const double window_width = std::max(bounds.size.width, 1.0); const double window_width = std::max(bounds.size.width, 1.0);
const double window_height = std::max(bounds.size.height, 1.0); const double window_height = std::max(bounds.size.height, 1.0);