Add a basic mouse grab mode when running on macOS

Adds a "Grab Mouse" command to the "Window" menu which toggles
SDL_SetRelativeMouseMode. That "traps" the mouse to the current window
(and hides it) which makes it less annoying to deal with the separate
acceleration curves of the host and guest OS.

This needs to be done via AppKit code since SDL does not have a
cross-platform way to add menu commands.

I also looked into the extended ADB mouse protocol but it does not
appear to do that we want. If we use it to report a device class of
classAbsolute (0) the acceleration curve changes to a flat response
(we end up using 'accl' 0 from 9dd3c4bef8/base/SuperMarioProj.1994-02-09/Resources/MiscROMRsrcs.r (L64-L76))
but the coordinates are still interpreted as being deltas. If I use a
device type 4 (Absolute pointing devices) then the mouse position is
never queried. Per https://68kmla.org/bb/index.php?threads/anyone-have-an-adb-graphics-tablet-or-joystick.39128/
all tablet devices end up using custom drivers.
This commit is contained in:
Mihai Parparita 2023-12-15 15:38:32 -08:00
parent 0e0de638d4
commit 587eb48f61
1 changed files with 57 additions and 0 deletions

View File

@ -20,6 +20,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <Cocoa/Cocoa.h>
#include <SDL.h>
@interface DPPCResponder : NSResponder
@end
static DPPCResponder *dppcResponder;
/** @file SDL-specific main function additions for macOS hosts. */
@ -37,4 +43,55 @@ void remap_appkit_menu_shortcuts(void) {
}
}
}
// Add a "Grab Mouse" commamand to the Window menu.
NSMenuItem *windowMenuItem = [[NSApp mainMenu] itemWithTitle:@"Window"];
if (windowMenuItem) {
NSMenu *windowMenu = [windowMenuItem submenu];
if (windowMenu) {
NSMenuItem *grabMouseMenuItem = [[NSMenuItem alloc] initWithTitle:@"Grab Mouse"
action:@selector(grabMouse:)
keyEquivalent:@"g"];
[grabMouseMenuItem setKeyEquivalentModifierMask:NSEventModifierFlagControl];
[windowMenu addItem:grabMouseMenuItem];
}
}
// Insert a responder in the chain to handle the command (SDL does not
// expose its NSAppDelegate implementation so we can't subclass it).
dppcResponder = [DPPCResponder new];
[dppcResponder setNextResponder:[NSApp nextResponder]];
[NSApp setNextResponder:dppcResponder];
}
@implementation DPPCResponder
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
if ([menuItem action] == @selector(grabMouse:)) {
[menuItem setState:SDL_GetRelativeMouseMode() ? NSControlStateValueOn : NSControlStateValueOff];
return YES;
}
return [super validateMenuItem:menuItem];
}
- (void)grabMouse:(id)sender {
NSWindow *mainWindow = NSApp.mainWindow;
if (mainWindow) {
// If we're about to grab the mouse, move the mouse to the middle of the
// window so that SDL knows that this window is what we want coordinates
// to be relative to.
if (!SDL_GetRelativeMouseMode()) {
NSRect frame = mainWindow.frame;
NSPoint center = NSMakePoint(frame.size.width/2, frame.size.height/2);
center = [mainWindow convertPointToScreen:center];
CGWarpMouseCursorPosition(center);
mainWindow.subtitle = @"Mouse grabbed";
} else {
mainWindow.subtitle = @"";
}
}
SDL_SetRelativeMouseMode(!SDL_GetRelativeMouseMode());
}
@end