Files
UTM/Platform/iOS/Display/VMDisplayMetalViewController+Gamepad.m
T
osy 17244e6f6c input(iOS): let the controller Options and Home buttons be mapped
Only the Menu button could be mapped to the guest. Options and Home were
left out because the system uses them for its own gestures (screenshots,
recording, opening the game overlay), so a press never did anything useful
for the guest.

Add both to the gamepad settings with the same choices as the other
buttons. They default to Disabled, which leaves the system gestures alone.
Once a button is mapped we ask the system to give up its gesture through
preferredSystemGestureState, which the system honours according to the
user's Home button setting.

Not tested with a physical controller.

Assisted-by: Claude:claude-fable-5-1
2026-09-20 03:45:31 -05:00

176 lines
8.0 KiB
Objective-C

//
// Copyright © 2020 osy. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#import "VMCursor.h"
#import "VMScroll.h"
#import "VMDisplayMetalViewController+Private.h"
#import "VMDisplayMetalViewController+Gamepad.h"
#import "VMDisplayMetalViewController+Touch.h"
#import "CSDisplay.h"
#import "UTMLogging.h"
#import "UTM-Swift.h"
const CGFloat kThumbstickSpeedMultiplier = 1000; // in points per second
@implementation VMDisplayMetalViewController(Gamepad)
- (void)initGamepad {
// notifications for controller (dis)connect
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(controllerWasConnected:) name:GCControllerDidConnectNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(controllerWasDisconnected:) name:GCControllerDidDisconnectNotification object:nil];
for (GCController *controller in [GCController controllers]) {
[self setupController:controller];
}
}
#pragma mark - Gamepad connection
- (void)controllerWasConnected:(NSNotification *)notification {
// a controller was connected
GCController *controller = (GCController *)notification.object;
UTMLog(@"Controller connected: %@", controller.vendorName);
[self setupController:controller];
}
- (void)controllerWasDisconnected:(NSNotification *)notification {
// a controller was disconnected
GCController *controller = (GCController *)notification.object;
UTMLog(@"Controller disconnected: %@", controller.vendorName);
}
- (void)setupController:(GCController *)controller {
GCExtendedGamepad *gamepad = controller.extendedGamepad;
__weak typeof(self) _self = self;
self.controller = controller;
UTMLog(@"active controller switched to: %@", controller.vendorName);
gamepad.leftTrigger.pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) {
[_self gamepadButton:@"GCButtonTriggerLeft" pressed:pressed];
};
gamepad.rightTrigger.pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) {
[_self gamepadButton:@"GCButtonTriggerRight" pressed:pressed];
};
gamepad.leftShoulder.pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) {
[_self gamepadButton:@"GCButtonShoulderLeft" pressed:pressed];
};
gamepad.rightShoulder.pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) {
[_self gamepadButton:@"GCButtonShoulderRight" pressed:pressed];
};
gamepad.buttonA.pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) {
[_self gamepadButton:@"GCButtonA" pressed:pressed];
};
gamepad.buttonB.pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) {
[_self gamepadButton:@"GCButtonB" pressed:pressed];
};
gamepad.buttonX.pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) {
[_self gamepadButton:@"GCButtonX" pressed:pressed];
};
gamepad.buttonY.pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) {
[_self gamepadButton:@"GCButtonY" pressed:pressed];
};
gamepad.dpad.up.pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) {
[_self gamepadButton:@"GCButtonDpadUp" pressed:pressed];
};
gamepad.dpad.left.pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) {
[_self gamepadButton:@"GCButtonDpadLeft" pressed:pressed];
};
gamepad.dpad.down.pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) {
[_self gamepadButton:@"GCButtonDpadDown" pressed:pressed];
};
gamepad.dpad.right.pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) {
[_self gamepadButton:@"GCButtonDpadRight" pressed:pressed];
};
gamepad.leftThumbstick.valueChangedHandler = ^(GCControllerDirectionPad * _Nonnull dpad, float xValue, float yValue) {
VMDisplayMetalViewController *s = _self;
CGPoint velocity = CGPointMake(xValue * kThumbstickSpeedMultiplier, -yValue * kThumbstickSpeedMultiplier);
[s.scroll startMovement:CGPointZero];
[s.scroll updateMovement:CGPointMake(xValue, yValue)];
[s.scroll endMovementWithVelocity:velocity resistance:0];
};
gamepad.rightThumbstick.valueChangedHandler = ^(GCControllerDirectionPad * _Nonnull dpad, float xValue, float yValue) {
NSInteger speed = [_self integerForSetting:@"GCThumbstickRightSpeed"];
VMDisplayMetalViewController *s = _self;
CGPoint center = s.cursor.center;
CGPoint start = CGPointMake(xValue * speed, -yValue * speed);
CGPoint velocity = CGPointMake(xValue * kThumbstickSpeedMultiplier, -yValue * kThumbstickSpeedMultiplier);
[s.cursor startMovement:center];
[s.cursor updateMovement:CGPointMake(center.x + start.x, center.y + start.y)];
[s.cursor endMovementWithVelocity:velocity resistance:0];
};
gamepad.buttonMenu.pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) {
[_self gamepadButton:@"GCButtonMenu" pressed:pressed];
};
gamepad.buttonOptions.pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) {
[_self gamepadButton:@"GCButtonOptions" pressed:pressed];
};
gamepad.buttonHome.pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) {
[_self gamepadButton:@"GCButtonHome" pressed:pressed];
};
[self setSystemGestureButtonsClaimed:YES];
}
- (void)setSystemGestureButtonsClaimed:(BOOL)isClaimed {
// the system uses these buttons for its own gestures, so we only ask for them while they are mapped to the guest
GCExtendedGamepad *gamepad = self.controller.extendedGamepad;
BOOL isOptionsClaimed = isClaimed && [self integerForSetting:@"GCButtonOptions"] != 0;
BOOL isHomeClaimed = isClaimed && [self integerForSetting:@"GCButtonHome"] != 0;
gamepad.buttonOptions.preferredSystemGestureState = isOptionsClaimed ? GCSystemGestureStateDisabled : GCSystemGestureStateEnabled;
gamepad.buttonHome.preferredSystemGestureState = isHomeClaimed ? GCSystemGestureStateDisabled : GCSystemGestureStateEnabled;
}
- (void)gamepadButton:(NSString *)identifier pressed:(BOOL)isPressed {
NSInteger value = [self integerForSetting:identifier];
UTMLog(@"GC button %@ (%ld) pressed:%d", identifier, value, isPressed);
switch (value) {
case 0:
break;
case -1:
[self.vmInput sendMouseButton:kCSInputButtonLeft mask:self.mouseButtonDown pressed:isPressed];
self.mouseLeftDown = isPressed;
break;
case -3:
[self.vmInput sendMouseButton:kCSInputButtonRight mask:self.mouseButtonDown pressed:isPressed];
self.mouseRightDown = isPressed;
break;
case -2:
[self.vmInput sendMouseButton:kCSInputButtonMiddle mask:self.mouseButtonDown pressed:isPressed];
self.mouseMiddleDown = isPressed;
break;
default:
[self sendExtendedKey:isPressed ? kCSInputKeyPress : kCSInputKeyRelease code:(int)value];
break;
}
}
@end