Skip to content

Commit

Permalink
InputCommon:QuarzKB&M: Use KVO to watch window position
Browse files Browse the repository at this point in the history
CGWindowListCreateDescriptionFromArray would block for up to ~1ms, which isn't a great thing to do on the main emulation thread
  • Loading branch information
TellowKrinkle committed Aug 18, 2022
1 parent b96bc42 commit 798b241
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 29 deletions.
Expand Up @@ -8,6 +8,12 @@
#include "Common/Matrix.h"
#include "InputCommon/ControllerInterface/CoreDevice.h"

#ifdef __OBJC__
@class DolWindowPositionObserver;
#else
class DolWindowPositionObserver;
#endif

namespace ciface::Quartz
{
std::string KeycodeToName(const CGKeyCode keycode);
Expand Down Expand Up @@ -59,13 +65,16 @@ class KeyboardAndMouse : public Core::Device
void UpdateInput() override;

explicit KeyboardAndMouse(void* view);
~KeyboardAndMouse() override;

std::string GetName() const override;
std::string GetSource() const override;

private:
void MainThreadInitialization(void* view);

Common::Vec2 m_cursor;

uint32_t m_windowid;
DolWindowPositionObserver* m_window_pos_observer;
};
} // namespace ciface::Quartz
Expand Up @@ -4,6 +4,7 @@
#include "InputCommon/ControllerInterface/Quartz/QuartzKeyboardAndMouse.h"

#include <map>
#include <mutex>

#include <Carbon/Carbon.h>
#include <Cocoa/Cocoa.h>
Expand All @@ -12,6 +13,64 @@

#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
{
std::string KeycodeToName(const CGKeyCode keycode)
Expand Down Expand Up @@ -149,20 +208,12 @@
AddCombinedInput("Shift", {"Left Shift", "Right Shift"});
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
// these UI APIs are only ever called on the main thread.
if ([NSThread isMainThread])
{
m_windowid = [[cocoa_view window] windowNumber];
}
MainThreadInitialization(view);
else
{
dispatch_sync(dispatch_get_main_queue(), ^{
m_windowid = [[cocoa_view window] windowNumber];
});
}
dispatch_sync(dispatch_get_main_queue(), [this, view] { MainThreadInitialization(view); });

// cursor, with a hax for-loop
for (unsigned int i = 0; i < 4; ++i)
Expand All @@ -173,26 +224,19 @@
AddInput(new Button(kCGMouseButtonCenter));
}

void KeyboardAndMouse::UpdateInput()
{
CGRect bounds = CGRectZero;
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));
// 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;

if (boundsDictionary != nullptr)
CGRectMakeWithDictionaryRepresentation(boundsDictionary, &bounds);
}
void KeyboardAndMouse::MainThreadInitialization(void* view)
{
NSView* cocoa_view = (__bridge NSView*)view;
m_window_pos_observer = [[DolWindowPositionObserver alloc] initWithView:cocoa_view];
}

CFRelease(windowDescriptions);
CFRelease(windowArray);
void KeyboardAndMouse::UpdateInput()
{
NSRect bounds = [m_window_pos_observer frame];

const double window_width = std::max(bounds.size.width, 1.0);
const double window_height = std::max(bounds.size.height, 1.0);
Expand Down

0 comments on commit 798b241

Please sign in to comment.