From bcdcc080fbab0dfbabd76ee775b436ebbb897495 Mon Sep 17 00:00:00 2001 From: Eilionoir Tunnicliff Date: Tue, 21 Mar 2023 10:52:16 -0400 Subject: [PATCH] Controller configurator! Same keycode names as the on-screen editor to make it easier to identify the possible values. Xbox and PlayStation button names for familiarity. Saving configs handled by the Save and Exit buttons on the navigation bar. Currently known issues: - may be jumpy when cells reload or load onto the screen - loading a new configuration file does not update the value of the cells - loading a new configuration file does not update the "Controller configurator" item in the main Settings window - both of these are solved by closing and reopening the view controller for now --- Natives/CMakeLists.txt | 1 + Natives/LauncherPrefContCfgViewController.h | 6 + Natives/LauncherPrefContCfgViewController.m | 454 +++++++++++++++++++ Natives/LauncherPreferences.m | 1 + Natives/LauncherPreferencesViewController.m | 14 +- Natives/customcontrols/CustomControlsUtils.h | 2 +- Natives/customcontrols/CustomControlsUtils.m | 14 +- Natives/en.lproj/Localizable.strings | 47 ++ Natives/input/ControllerInput.m | 2 +- Natives/ios_uikit_bridge.h | 1 + Natives/ios_uikit_bridge.m | 20 + Natives/main_hook.m | 5 +- Natives/resources/Assets.car | Bin 9232352 -> 9232352 bytes Natives/resources/glfw_keycodes.plist | 268 +++++++++++ 14 files changed, 828 insertions(+), 7 deletions(-) create mode 100644 Natives/LauncherPrefContCfgViewController.h create mode 100644 Natives/LauncherPrefContCfgViewController.m create mode 100644 Natives/resources/glfw_keycodes.plist diff --git a/Natives/CMakeLists.txt b/Natives/CMakeLists.txt index 419cca93..38bd0d21 100644 --- a/Natives/CMakeLists.txt +++ b/Natives/CMakeLists.txt @@ -127,6 +127,7 @@ add_executable(PojavLauncher LauncherNewsViewController.m LauncherPreferences.m LauncherPreferencesViewController.m + LauncherPrefContCfgViewController.m LauncherPrefGameDirViewController.m LauncherSplitViewController.m MinecraftResourceUtils.m diff --git a/Natives/LauncherPrefContCfgViewController.h b/Natives/LauncherPrefContCfgViewController.h new file mode 100644 index 00000000..427a122c --- /dev/null +++ b/Natives/LauncherPrefContCfgViewController.h @@ -0,0 +1,6 @@ +#import + +@interface LauncherPrefContCfgViewController : UITableViewController + +@end + diff --git a/Natives/LauncherPrefContCfgViewController.m b/Natives/LauncherPrefContCfgViewController.m new file mode 100644 index 00000000..526dab10 --- /dev/null +++ b/Natives/LauncherPrefContCfgViewController.m @@ -0,0 +1,454 @@ +#import +#import + +#import "input/ControllerInput.h" +#import "customcontrols/CustomControlsUtils.h" +#import "FileListViewController.h" +#import "LauncherNavigationController.h" +#import "LauncherPreferences.h" +#import "LauncherPrefContCfgViewController.h" +#import "TOInsetGroupedTableView.h" +#import "ios_uikit_bridge.h" +#import "utils.h" + +#include "glfw_keycodes.h" + +#define contentNavigationController (LauncherNavigationController *)UIApplication.sharedApplication.keyWindow.rootViewController.splitViewController.viewControllers[1] + +typedef void(^CreateView)(UITableViewCell *, NSString *, NSDictionary *); + +@interface LauncherPrefContCfgViewController () +@property(nonatomic) NSString *currentFileName; +@property(nonatomic) NSMutableDictionary *allMappings; +@property(nonatomic) NSDictionary *keycodePlist; +@property(nonatomic) UITextField *activeTextField; +@property(nonatomic) NSArray* prefSections; +@property(nonatomic) NSMutableArray* prefSectionsVisibility; +@property(nonatomic) NSArray*>* prefContents; +@property(nonatomic) NSArray* prefMappings, *prefMappingsTrimmed; +@property(nonatomic) NSArray* prefConfigFiles, *prefControllerTypes; +@property(nonatomic) NSMutableArray *keyCodeMap, *keyValueMap; + +@end + +@implementation LauncherPrefContCfgViewController + +- (void)viewDidLoad +{ + [super viewDidLoad]; + [self setTitle:localize(@"preference.title.default_gamepad_ctrl", nil)]; + + self.keycodePlist = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"glfw_keycodes" ofType:@"plist"]]; + + self.keyCodeMap = [[NSMutableArray alloc] init]; + self.keyValueMap = [[NSMutableArray alloc] init]; + initKeycodeTable(self.keyCodeMap, self.keyValueMap); + + self.tableView = [[TOInsetGroupedTableView alloc] init]; + self.tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeInteractive; + self.tableView.sectionFooterHeight = 50; + + self.prefMappings = [self loadGamepadConfigurationFile:YES shouldSkipImmutables:NO]; + self.prefMappingsTrimmed = [self loadGamepadConfigurationFile:NO shouldSkipImmutables:YES]; + self.prefConfigFiles = [self loadGamepadControlFolder]; + self.prefControllerTypes = [self loadControllerTypeOptions]; + + self.prefSections = @[@"config_files", @"game_mappings", @"menu_mappings", @"controller_style"]; + self.prefContents = @[self.prefConfigFiles, self.prefMappingsTrimmed[0], self.prefMappingsTrimmed[1], self.prefControllerTypes]; + + self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:localize(@"Save", nil) style:UIBarButtonItemStyleDone target:self action:@selector(actionMenuSave)]; + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:localize(@"Exit", nil) style:UIBarButtonItemStyleDone target:self action:@selector(exitButtonSelector)]; + +} + +- (NSArray*>*) loadGamepadConfigurationFile:(BOOL)isUpdated shouldSkipImmutables:(BOOL)isSkipped { + if(isUpdated) { + NSString *gamepadPath = [NSString stringWithFormat:@"%s/controlmap/gamepads/%@", getenv("POJAV_HOME"), getPreference(@"default_gamepad_ctrl")]; + self.allMappings = parseJSONFromFile(gamepadPath); + } + + self.currentFileName = [getPreference(@"default_ctrl") stringByDeletingPathExtension]; + + NSMutableArray* tempGameMap = [[NSMutableArray alloc] init]; + NSMutableArray* tempMenuMap = [[NSMutableArray alloc] init]; + + __block NSArray *immutables = @[@"mouse_primary", @"mouse_middle", @"mouse_secondary"]; + __block NSArray *maps = @[@"mGameMappingList", @"mMenuMappingList"]; + + for(NSString *mapName in maps) { + for(NSDictionary* mapping in self.allMappings[mapName]) { + __block BOOL isImmutable = NO; + __block int isThird = 0; + for(NSString* name in immutables) { + if([name isEqualToString:mapping[@"name"]]) { if(isSkipped) { isImmutable = YES; } } + if(isThird == 2 && !isImmutable) { + if([mapName isEqualToString:maps[0]]) { + [tempGameMap addObject:mapping]; + } else if([mapName isEqualToString:maps[1]]) { + [tempMenuMap addObject:mapping]; + } + } + isThird++; + } + } + } + + return @[[tempGameMap copy], [tempMenuMap copy]]; +} + +- (NSArray*) loadGamepadControlFolder { + NSArray* gamepadCtrl = @[getPreference(@"default_gamepad_ctrl")]; + NSMutableArray* finalArray = [[NSMutableArray alloc] init]; + NSMutableDictionary *holder = [[NSMutableDictionary alloc] init]; + holder[@"name"] = gamepadCtrl; + [finalArray addObject:holder]; + + return [finalArray copy]; +} + + +- (NSArray*) loadControllerTypeOptions { + NSArray* types = @[@"xbox", @"playstation"]; + NSMutableArray* finalArray = [[NSMutableArray alloc] init]; + for(NSString *type in types) { + NSMutableDictionary *controllerDict = [[NSMutableDictionary alloc] init]; + controllerDict[@"name"] = type; + [finalArray addObject:controllerDict]; + } + + return [finalArray copy]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + NSMutableArray *indexPaths = [[NSMutableArray alloc] init]; + [self.tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone]; +} + +#pragma mark External UITableView functions + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return self.prefSections.count; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return self.prefContents[section].count; +} + +- (UITableViewCell *)tableView:(nonnull UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + __weak LauncherPrefContCfgViewController *weakSelf = self; + + NSDictionary *item = self.prefContents[indexPath.section][indexPath.row]; + NSString *cellID = @"cellValue1"; + UITableViewCellStyle cellStyle = UITableViewCellStyleValue1; + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID]; + if (cell == nil) { + cell = [[UITableViewCell alloc] initWithStyle:cellStyle reuseIdentifier:cellID]; + cell.detailTextLabel.numberOfLines = 0; + cell.detailTextLabel.lineBreakMode = NSLineBreakByWordWrapping; + } + + // Reset cell properties, as it could be reused + cell.accessoryType = UITableViewCellAccessoryNone; + cell.accessoryView = nil; + cell.selectionStyle = UITableViewCellSelectionStyleNone; + cell.textLabel.textColor = nil; + cell.detailTextLabel.text = nil; + + NSString *name = nil; + NSString *value = nil; + NSNumber *keycode = 0; + + if(indexPath.section == 0) { + name = item[@"name"]; + value = getPreference(@"default_gamepad_ctrl"); + cell.textLabel.text = localize(@"controller_configurator.title.current", nil); + cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + cell.detailTextLabel.text = value; + } else if(indexPath.section == 1 || indexPath.section == 2) { + name = item[@"name"]; + NSNumber *keycode = (NSNumber *)item[@"keycode"]; + value = [self.keyCodeMap objectAtIndex:[self.keyValueMap indexOfObject:keycode]]; + cell.textLabel.text = localize(([NSString stringWithFormat:@"controller_configurator.%@.title.%@", getPreference(@"controller_type"), name]), nil); + + CGFloat shortest = MIN(self.view.frame.size.width, self.view.frame.size.height); + CGFloat tempW = MIN(self.view.frame.size.width * 0.75, shortest); + CGFloat tempH = MIN(self.view.frame.size.height * 0.6, shortest); + + UITextField *view = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, cell.bounds.size.width / 2.1, cell.bounds.size.height)]; + [view addTarget:view action:@selector(resignFirstResponder) forControlEvents:UIControlEventEditingDidEndOnExit]; + view.autocorrectionType = UITextAutocorrectionTypeNo; + view.autocapitalizationType = UITextAutocapitalizationTypeNone; + view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin; + view.delegate = weakSelf; + view.returnKeyType = UIReturnKeyDone; + view.text = value; + view.tag = indexPath.section; + view.textAlignment = NSTextAlignmentRight; + view.tintColor = UIColor.clearColor; + view.adjustsFontSizeToFitWidth = YES; + + UIPickerView *pickerMapping = [[UIPickerView alloc] init]; + pickerMapping.delegate = self; + pickerMapping.dataSource = self; + [pickerMapping reloadAllComponents]; + [pickerMapping selectRow:[self.keyValueMap indexOfObject:keycode] inComponent:0 animated:NO]; + [self pickerView:pickerMapping didSelectRow:0 inComponent:0]; + + + UIBlurEffectStyle blurStyle; + UIVisualEffectView *blurView; + if (@available(iOS 13.0, *)) { + blurStyle = UIBlurEffectStyleSystemMaterial; + } else { + blurStyle = UIBlurEffectStyleExtraLight; + } + blurView = [[UIVisualEffectView alloc] initWithEffect:[UIBlurEffect effectWithStyle:blurStyle]]; + blurView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin; + blurView.frame = CGRectMake( + (self.view.frame.size.width - MAX(tempW, tempH))/2, + (self.view.frame.size.height - MIN(tempW, tempH))/2, + MAX(tempW, tempH), MIN(tempW, tempH)); + blurView.layer.cornerRadius = 10.0; + blurView.clipsToBounds = YES; + + UIToolbar *editPickToolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0.0, 0.0, blurView.frame.size.width, 44.0)]; + UIBarButtonItem *btnFlexibleSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:self action:nil]; + + UIBarButtonItem *editDoneButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(closeTextField:)]; + editPickToolbar.items = @[btnFlexibleSpace, editDoneButton]; + + view.inputAccessoryView = editPickToolbar; + view.inputView = pickerMapping; + + cell.accessoryView = view; + } else if(indexPath.section == 3) { + name = item[@"name"]; + value = getPreference(@"default_gamepad_ctrl"); + cell.textLabel.text = localize([NSString stringWithFormat:@"controller_configurator.title.type.%@", name], nil); + if ([getPreference(@"controller_type") isEqualToString:name]) { + cell.accessoryType = UITableViewCellAccessoryCheckmark; + } else { + cell.accessoryType = UITableViewCellAccessoryNone; + } + } + cell.selectionStyle = UITableViewCellSelectionStyleGray; + + if (cell.accessoryView) { + objc_setAssociatedObject(cell.accessoryView, @"gamepad_button", name, OBJC_ASSOCIATION_ASSIGN); + objc_setAssociatedObject(cell.accessoryView, @"item", item, OBJC_ASSOCIATION_ASSIGN); + } + + [(id)cell.accessoryView setEnabled:YES]; + + return cell; +} + +- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { + NSString *header = NSLocalizedStringWithDefaultValue(([NSString stringWithFormat:@"controller_configurator.section.%@", self.prefSections[section]]), @"Localizable", NSBundle.mainBundle, @" ", nil); + if ([header isEqualToString:@" "]) { + return nil; + } + return header; +} + +- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section { + NSString *footer = NSLocalizedStringWithDefaultValue(([NSString stringWithFormat:@"controller_configurator.section.footer.%@", self.prefSections[section]]), @"Localizable", NSBundle.mainBundle, @" ", nil); + if ([footer isEqualToString:@" "]) { + return nil; + } + return footer; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [tableView deselectRowAtIndexPath:indexPath animated:NO]; + + NSDictionary *item = self.prefContents[indexPath.section][indexPath.row]; + + if (indexPath.section == 0 || indexPath.section == 3) { + [self tableView:tableView invokeActionAtIndexPath:indexPath]; + return; + } + // 1 and 2 handle themselves with picker views. + + return; +} + +- (void)tableView:(UITableView *)tableView invokeActionAtIndexPath:(NSIndexPath *)indexPath { + UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath]; + if(indexPath.section == 0) { + [self actionMenuLoad]; + [self.tableView reloadData]; + } else if(indexPath.section == 3) { + if(indexPath.row == 0) { + setPreference(@"controller_type", @"xbox"); + } else if(indexPath.row == 1) { + setPreference(@"controller_type", @"playstation"); + } + NSIndexSet *section = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, 3)]; + [self.tableView reloadSections:section withRowAnimation:UITableViewRowAnimationNone]; + } +} + +#pragma mark UITextField + UIPickerView + +- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view +{ + UILabel *label = (UILabel *)view; + if (label == nil) { + label = [UILabel new]; + label.adjustsFontSizeToFitWidth = YES; + label.minimumScaleFactor = 0.5; + label.textAlignment = NSTextAlignmentCenter; + } + label.text = [self pickerView:pickerView titleForRow:row forComponent:component]; + + return label; +} + +- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component { + self.activeTextField.text = [self.keyCodeMap objectAtIndex:row]; +} + +- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)thePickerView { + return 1; +} + +- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component { + return self.keyCodeMap.count; +} + +- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component { + return [self.keyCodeMap objectAtIndex:row]; +} + +- (void)textFieldDidBeginEditing:(UITextField *)textField +{ + self.activeTextField = textField; +} +- (void)textFieldDidEndEditing:(UITextField *)textField +{ + UITableViewCell *cell = (UITableViewCell *)textField.superview; + __block NSDictionary *item = objc_getAssociatedObject(cell.accessoryView, @"item"); + __block NSString *fullString = @""; + __block NSArray *arrayToSave = nil; + __block NSUInteger btnIndex = 0; + __block NSString *index = @""; + + if(textField.tag == 1) { + arrayToSave = self.prefMappings[0]; + index = @"mGameMappingList"; + } else if(textField.tag == 2) { + arrayToSave = self.prefMappings[1]; + index = @"mMenuMappingList"; + } + + for(NSDictionary* mapping in arrayToSave) { + if(item[@"gamepad_button"] == mapping[@"gamepad_button"]) { + btnIndex = [arrayToSave indexOfObject:mapping]; + break; + } + } + + if(![textField.text hasPrefix:@"SPECIALBTN"]) { + fullString = [@"GLFW_KEY_" stringByAppendingString:textField.text]; + } else { + fullString = textField.text; + } + + self.allMappings[index][btnIndex][@"keycode"] = [self.keycodePlist objectForKey:fullString]; + + self.activeTextField = nil; +} + +- (void)closeTextField:(UIBarButtonItem *)sender { + [self.activeTextField endEditing:YES]; +} + +#pragma mark UI + +- (void) dismissModalViewController { + [self.navigationController dismissViewControllerAnimated:YES completion:nil]; +} + +- (void)actionOpenFilePicker:(void (^)(NSString *name))handler { + FileListViewController *vc = [[FileListViewController alloc] init]; + vc.listPath = [NSString stringWithFormat:@"%s/controlmap/gamepads", getenv("POJAV_HOME")]; + + vc.whenItemSelected = handler; + vc.modalPresentationStyle = UIModalPresentationPopover; + vc.preferredContentSize = CGSizeMake(350, 250); + + UIPopoverPresentationController *popoverController = [vc popoverPresentationController]; + popoverController.sourceView = self.tableView; + popoverController.sourceRect = [self.tableView rectForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]]; + popoverController.delegate = self; + [self presentViewController:vc animated:YES completion:nil]; +} + +- (void)actionMenuLoad { + [self actionOpenFilePicker:^void(NSString* name) { + NSString *currentFile = [NSString stringWithFormat:@"%@.json", getPreference(@"default_gamepad_ctrl")]; + if(![currentFile isEqualToString:name]) { + setPreference(@"default_gamepad_ctrl", [NSString stringWithFormat:@"%@.json", name]); + self.prefMappings = [self loadGamepadConfigurationFile:YES shouldSkipImmutables:NO]; + self.prefMappingsTrimmed = [self loadGamepadConfigurationFile:NO shouldSkipImmutables:YES]; + [self.tableView reloadData]; + } + }]; +} + +- (void)actionMenuSaveWithExit:(BOOL)exit { + UIAlertController *controller = [UIAlertController alertControllerWithTitle:localize(@"custom_controls.control_menu.save", nil) + message:exit?localize(@"custom_controls.control_menu.exit.warn", nil):@"" + preferredStyle:UIAlertControllerStyleAlert]; + [controller addTextFieldWithConfigurationHandler:^(UITextField *textField) { + textField.placeholder = @"Name"; + textField.text = self.currentFileName; + textField.clearButtonMode = UITextFieldViewModeWhileEditing; + textField.borderStyle = UITextBorderStyleRoundedRect; + }]; + [controller addAction:[UIAlertAction actionWithTitle:localize(@"OK", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + NSArray *textFields = controller.textFields; + UITextField *field = textFields[0]; + NSError *error; + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:self.allMappings options:NSJSONWritingPrettyPrinted error:&error]; + if (jsonData == nil) { + showDialog(self, localize(@"custom_controls.control_menu.save.error.json", nil), error.localizedDescription); + return; + } + BOOL success = [jsonData writeToFile:[NSString stringWithFormat:@"%s/controlmap/gamepads/%@.json", getenv("POJAV_HOME"), field.text] options:NSDataWritingAtomic error:&error]; + if (!success) { + showDialog(self, localize(@"custom_controls.control_menu.save.error.write", nil), error.localizedDescription); + return; + } + + if (exit) { + [self dismissModalViewController]; + } + + setPreference(@"default_gamepad_ctrl", [NSString stringWithFormat:@"%@.json", field.text]); + }]]; + if (exit) { + [controller addAction:[UIAlertAction actionWithTitle:localize(@"custom_controls.control_menu.discard_changes", nil) style:UIAlertActionStyleDestructive handler:^(UIAlertAction *action) { + [self dismissModalViewController]; + }]]; + } + [controller addAction:[UIAlertAction actionWithTitle:localize(@"Cancel", nil) style:UIAlertActionStyleCancel handler:nil]]; + [self presentViewController:controller animated:YES completion:nil]; +} + +- (void)actionMenuSave { + [self actionMenuSaveWithExit:NO]; +} + +- (void)exitButtonSelector { + NSString *gamepadPath = [NSString stringWithFormat:@"%s/controlmap/gamepads/%@", getenv("POJAV_HOME"), getPreference(@"default_gamepad_ctrl")]; + if([self.allMappings isEqualToDictionary:parseJSONFromFile(gamepadPath)]) { + [self dismissModalViewController]; + } else { + [self actionMenuSaveWithExit:YES]; + } +} + +@end diff --git a/Natives/LauncherPreferences.m b/Natives/LauncherPreferences.m index 085d6a42..8dee4ae6 100644 --- a/Natives/LauncherPreferences.m +++ b/Natives/LauncherPreferences.m @@ -140,6 +140,7 @@ void loadPreferences(BOOL reset) { setDefaultValueForPref(prefDict, @"appicon", @"AppIcon-Light"); setDefaultValueForPref(prefDict, @"debug_logging", @(CONFIG_RELEASE != 1)); setDefaultValueForPref(prefDict, @"cosmetica", @YES); + setDefaultValueForPref(prefDict, @"controller_type", @"xbox"); setDefaultValueForPref(envPrefDict, @"java_home", @"java-8-openjdk"); setDefaultValueForPref(envPrefDict, @"renderer", @"auto"); setDefaultValueForPref(envPrefDict, @"fullscreen_airplay", @YES); diff --git a/Natives/LauncherPreferencesViewController.m b/Natives/LauncherPreferencesViewController.m index 50ec3f5d..7a0b0faf 100644 --- a/Natives/LauncherPreferencesViewController.m +++ b/Natives/LauncherPreferencesViewController.m @@ -6,6 +6,7 @@ #import "LauncherMenuViewController.h" #import "LauncherPreferences.h" #import "LauncherPreferencesViewController.h" +#import "LauncherPrefContCfgViewController.h" #import "LauncherPrefGameDirViewController.h" #import "TOInsetGroupedTableView.h" #import "UIKit+hook.h" @@ -70,6 +71,7 @@ typedef void(^CreateView)(UITableViewCell *, NSString *, NSDictionary *); @"enableCondition": ^BOOL(){ return whenNotInGame() && !getenv("DEMO_LOCK"); }, + @"canDismissWithSwipe": @YES, @"class": LauncherPrefGameDirViewController.class, }, @{@"key": @"check_sha", @@ -238,9 +240,16 @@ typedef void(^CreateView)(UITableViewCell *, NSString *, NSDictionary *); ], @[ // Control settings @{@"icon": @"gamecontroller"}, + @{@"key": @"default_gamepad_ctrl", + @"icon": @"hammer", + @"type": self.typeChildPane, + @"enableCondition": whenNotInGame, + @"canDismissWithSwipe": @NO, + @"class": LauncherPrefContCfgViewController.class + }, @{@"key": @"hardware_hide", @"icon": @"eye.slash", - @"hasDetail": @"YES", + @"hasDetail": @YES, @"type": self.typeSwitch, }, @{@"key": @"gesture_mouse", @@ -712,6 +721,9 @@ typedef void(^CreateView)(UITableViewCell *, NSString *, NSDictionary *); NSDictionary *item = self.prefContents[indexPath.section][indexPath.row]; UINavigationController *vc = [[UINavigationController alloc] initWithRootViewController:[[item[@"class"] alloc] init]]; vc.navigationBar.prefersLargeTitles = YES; + if(@available(iOS 13.0, *)) { + vc.modalInPresentation = ![item[@"canDismissWithSwipe"] boolValue]; + } [self.navigationController presentViewController:vc animated:YES completion:nil]; } diff --git a/Natives/customcontrols/CustomControlsUtils.h b/Natives/customcontrols/CustomControlsUtils.h index 3dcc2b66..a553e4fb 100644 --- a/Natives/customcontrols/CustomControlsUtils.h +++ b/Natives/customcontrols/CustomControlsUtils.h @@ -5,7 +5,7 @@ #define BTN_SQUARE 50.0, 50.0 NSMutableDictionary* createButton(NSString* name, int* keycodes, NSString* dynamicX, NSString* dynamicY, CGFloat width, CGFloat height); - +NSMutableDictionary* createGamepadButton(NSString* name, int gamepad_button, int keycode); UIColor* convertARGB2UIColor(int argb); int convertUIColor2ARGB(UIColor* color); int convertUIColor2RGB(UIColor* color); diff --git a/Natives/customcontrols/CustomControlsUtils.m b/Natives/customcontrols/CustomControlsUtils.m index 6ef609cc..65cb9974 100644 --- a/Natives/customcontrols/CustomControlsUtils.m +++ b/Natives/customcontrols/CustomControlsUtils.m @@ -311,7 +311,7 @@ void generateAndSaveDefaultControl() { } void generateAndSaveDefaultControlForGamepad() { - NSString *gamepadPath = [NSString stringWithFormat:@"%s/controlmap/gamepads/gamepad-default.json", getenv("POJAV_HOME")]; + NSString *gamepadPath = [NSString stringWithFormat:@"%s/controlmap/gamepads/default.json", getenv("POJAV_HOME")]; if ([NSFileManager.defaultManager fileExistsAtPath:gamepadPath]) { return; } @@ -345,7 +345,7 @@ void generateAndSaveDefaultControlForGamepad() { [dict[@"mGameMappingList"] addObject:createGamepadButton(@"dpad_right", GLFW_GAMEPAD_BUTTON_DPAD_RIGHT, GLFW_KEY_K)]; [dict[@"mGameMappingList"] addObject:createGamepadButton(@"thumb_left", GLFW_GAMEPAD_BUTTON_LEFT_THUMB, GLFW_KEY_LEFT_CONTROL)]; - [dict[@"mGameMappingList"] addObject:createGamepadButton(@"thumb_right", GLFW_GAMEPAD_BUTTON_RIGHT_THUMB, -GLFW_KEY_LEFT_SHIFT)]; + [dict[@"mGameMappingList"] addObject:createGamepadButton(@"thumb_right", GLFW_GAMEPAD_BUTTON_RIGHT_THUMB, GLFW_KEY_LEFT_SHIFT)]; dict[@"mMenuMappingList"] = [[NSMutableArray alloc] init]; @@ -356,15 +356,25 @@ void generateAndSaveDefaultControlForGamepad() { [dict[@"mMenuMappingList"] addObject:createGamepadButton(@"bumper_left", GLFW_GAMEPAD_BUTTON_LEFT_BUMPER, SPECIALBTN_SCROLLUP)]; [dict[@"mMenuMappingList"] addObject:createGamepadButton(@"bumper_right", GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER, SPECIALBTN_SCROLLDOWN)]; + [dict[@"mMenuMappingList"] addObject:createGamepadButton(@"trigger_left", GLFW_GAMEPAD_BUTTON_LEFT_TRIGGER, GLFW_KEY_UNKNOWN)]; + [dict[@"mMenuMappingList"] addObject:createGamepadButton(@"trigger_right", GLFW_GAMEPAD_BUTTON_RIGHT_TRIGGER, GLFW_KEY_UNKNOWN)]; + + [dict[@"mMenuMappingList"] addObject:createGamepadButton(@"named_back", GLFW_GAMEPAD_BUTTON_BACK, GLFW_KEY_UNKNOWN)]; + [dict[@"mMenuMappingList"] addObject:createGamepadButton(@"named_start", GLFW_GAMEPAD_BUTTON_START, GLFW_KEY_UNKNOWN)]; + [dict[@"mMenuMappingList"] addObject:createGamepadButton(@"named_a", GLFW_GAMEPAD_BUTTON_A, SPECIALBTN_MOUSEPRI)]; [dict[@"mMenuMappingList"] addObject:createGamepadButton(@"named_b", GLFW_GAMEPAD_BUTTON_B, GLFW_KEY_ESCAPE)]; [dict[@"mMenuMappingList"] addObject:createGamepadButton(@"named_x", GLFW_GAMEPAD_BUTTON_X, SPECIALBTN_MOUSESEC)]; [dict[@"mMenuMappingList"] addObject:createGamepadButton(@"named_y", GLFW_GAMEPAD_BUTTON_Y, GLFW_KEY_LEFT_SHIFT)]; + [dict[@"mMenuMappingList"] addObject:createGamepadButton(@"dpad_up", GLFW_GAMEPAD_BUTTON_DPAD_UP, GLFW_KEY_UNKNOWN)]; [dict[@"mMenuMappingList"] addObject:createGamepadButton(@"dpad_down", GLFW_GAMEPAD_BUTTON_DPAD_DOWN, GLFW_KEY_O)]; [dict[@"mMenuMappingList"] addObject:createGamepadButton(@"dpad_left", GLFW_GAMEPAD_BUTTON_DPAD_LEFT, GLFW_KEY_J)]; [dict[@"mMenuMappingList"] addObject:createGamepadButton(@"dpad_right", GLFW_GAMEPAD_BUTTON_DPAD_RIGHT, GLFW_KEY_K)]; + [dict[@"mMenuMappingList"] addObject:createGamepadButton(@"thumb_left", GLFW_GAMEPAD_BUTTON_LEFT_THUMB, GLFW_KEY_UNKNOWN)]; + [dict[@"mMenuMappingList"] addObject:createGamepadButton(@"thumb_right", GLFW_GAMEPAD_BUTTON_RIGHT_THUMB, GLFW_KEY_UNKNOWN)]; + NSOutputStream *os = [[NSOutputStream alloc] initToFileAtPath:gamepadPath append:NO]; [os open]; [NSJSONSerialization writeJSONObject:dict toStream:os options:NSJSONWritingPrettyPrinted error:nil]; diff --git a/Natives/en.lproj/Localizable.strings b/Natives/en.lproj/Localizable.strings index b3fd0392..cda3ffd1 100644 --- a/Natives/en.lproj/Localizable.strings +++ b/Natives/en.lproj/Localizable.strings @@ -180,6 +180,7 @@ "preference.title.fullscreen_airplay" = "Fullscreen AirPlay"; "preference.detail.fullscreen_airplay" = "Allows presenting fullscreen game window on the external display rather than mirroring everything on screen."; +"preference.title.default_gamepad_ctrl" = "Controller configuration"; "preference.title.hardware_hide" = "Hide controls when hardware is connected"; "preference.detail.hardware_hide" = "Enable the behaviour of automatically disabling the on-screen control overlay when a mouse or game controller is connected."; "preference.title.gesture_mouse" = "Mouse gestures"; @@ -232,3 +233,49 @@ "preference.detail.debug_show_layout_bounds" = "Show clip bounds, margins"; "preference.title.debug_show_layout_overlap" = "Show layout overlapping"; "preference.detail.debug_show_layout_overlap" = "Overlay every view with a transparent color to reveal overlapping views."; + +"controller_configurator.title.current" = "Gamepad configuration file"; +"controller_configurator.title.type.xbox" = "Xbox"; +"controller_configurator.title.type.playstation" = "PlayStation"; + +"controller_configurator.xbox.title.bumper_left" = "Left bumper"; +"controller_configurator.xbox.title.bumper_right" = "Right bumper"; +"controller_configurator.xbox.title.trigger_left" = "Left trigger"; +"controller_configurator.xbox.title.trigger_right" = "Right trigger"; +"controller_configurator.xbox.title.named_back" = "Guide"; +"controller_configurator.xbox.title.named_start" = "Menu"; +"controller_configurator.xbox.title.named_a" = "A"; +"controller_configurator.xbox.title.named_b" = "B"; +"controller_configurator.xbox.title.named_x" = "X"; +"controller_configurator.xbox.title.named_y" = "Y"; +"controller_configurator.xbox.title.dpad_up" = "Up"; +"controller_configurator.xbox.title.dpad_down" = "Down"; +"controller_configurator.xbox.title.dpad_left" = "Left"; +"controller_configurator.xbox.title.dpad_right" = "Right"; +"controller_configurator.xbox.title.thumb_left" = "Left thumbstick click"; +"controller_configurator.xbox.title.thumb_right" = "Right thumbstick click"; + +"controller_configurator.playstation.title.bumper_left" = "L1"; +"controller_configurator.playstation.title.bumper_right" = "R1"; +"controller_configurator.playstation.title.trigger_left" = "L2"; +"controller_configurator.playstation.title.trigger_right" = "R2"; +"controller_configurator.playstation.title.named_back" = "Select"; +"controller_configurator.playstation.title.named_start" = "Start"; +"controller_configurator.playstation.title.named_a" = "Cross"; +"controller_configurator.playstation.title.named_b" = "Circle"; +"controller_configurator.playstation.title.named_x" = "Square"; +"controller_configurator.playstation.title.named_y" = "Triangle"; +"controller_configurator.playstation.title.dpad_up" = "Up"; +"controller_configurator.playstation.title.dpad_down" = "Down"; +"controller_configurator.playstation.title.dpad_left" = "Left"; +"controller_configurator.playstation.title.dpad_right" = "Right"; +"controller_configurator.playstation.title.thumb_left" = "L3"; +"controller_configurator.playstation.title.thumb_right" = "R3"; + +"controller_configurator.section.config_files" = "Current configuration file"; +"controller_configurator.section.game_mappings" = "Game mappings"; +"controller_configurator.section.footer.game_mappings" = "Bindings when the mouse is locked in game."; +"controller_configurator.section.menu_mappings" = "Menu mappings"; +"controller_configurator.section.footer.menu_mappings" = "Bindings when the mouse is not locked, such as on the title screen or in the pause menu."; +"controller_configurator.section.controller_style" = "Controller type"; +"controller_configurator.section.footer.controller_style" = "Show different controller button names in this configurator."; diff --git a/Natives/input/ControllerInput.m b/Natives/input/ControllerInput.m index 3ec6ef08..26b2286a 100644 --- a/Natives/input/ControllerInput.m +++ b/Natives/input/ControllerInput.m @@ -30,7 +30,7 @@ BOOL leftShiftHeld; return; } - NSString *gamepadPath = [NSString stringWithFormat:@"%s/controlmap/gamepads/gamepad-%@", getenv("POJAV_HOME"), getPreference(@"default_gamepad_ctrl")]; + NSString *gamepadPath = [NSString stringWithFormat:@"%s/controlmap/gamepads/%@", getenv("POJAV_HOME"), getPreference(@"default_gamepad_ctrl")]; NSMutableDictionary *gamepadJSON = parseJSONFromFile(gamepadPath); gameMap = gamepadJSON[@"mGameMappingList"]; diff --git a/Natives/ios_uikit_bridge.h b/Natives/ios_uikit_bridge.h index 869dc808..5f0884a1 100644 --- a/Natives/ios_uikit_bridge.h +++ b/Natives/ios_uikit_bridge.h @@ -7,4 +7,5 @@ UIAlertController* createLoadingAlert(NSString* message); jstring UIKit_accessClipboard(JNIEnv* env, jint action, jstring copySrc); void UIKit_updateProgress(float progress, const char* message); void UIKit_launchMinecraftSurfaceVC(); +void UIKit_returnToSplitView(); void launchInitialViewController(UIWindow *window); diff --git a/Natives/ios_uikit_bridge.m b/Natives/ios_uikit_bridge.m index 9f0c3b86..8ef8d420 100644 --- a/Natives/ios_uikit_bridge.m +++ b/Natives/ios_uikit_bridge.m @@ -124,6 +124,26 @@ void UIKit_launchMinecraftSurfaceVC() { }); } +void UIKit_returnToSplitView() { + // Researching memory-safe ways to return from SurfaceViewController to the split view + // so that the app doesn't close when quitting the game (similar behaviour to Android) + dispatch_async(dispatch_get_main_queue(), ^{ + UIWindow *window = currentWindow(); + [UIView animateWithDuration:0.2 animations:^{ + window.alpha = 0; + } completion:^(BOOL b){ + [window resignKeyWindow]; + window.alpha = 1; + if (@available(iOS 14.0, tvOS 14.0, *)) { + window.rootViewController = [[LauncherSplitViewController alloc] initWithStyle:UISplitViewControllerStyleDoubleColumn]; + } else { + window.rootViewController = [[LauncherSplitViewController alloc] init]; + } + [window makeKeyAndVisible]; + }]; + }); +} + void launchInitialViewController(UIWindow *window) { if ([getPreference(@"internal_launch_on_boot") boolValue]) { window.rootViewController = [[SurfaceViewController alloc] init]; diff --git a/Natives/main_hook.m b/Natives/main_hook.m index 2b73a019..65fe28b2 100644 --- a/Natives/main_hook.m +++ b/Natives/main_hook.m @@ -1,5 +1,6 @@ #import #import "SurfaceViewController.h" +#import "ios_uikit_bridge.h" #import "utils.h" #include @@ -50,7 +51,7 @@ void* hooked_dlopen(const char* path, int mode) { void hooked_exit(int code) { NSLog(@"exit(%d) called", code); if (code == 0) { - orig_exit(code); + UIKit_returnToSplitView(); return; } handle_fatal_exit(code); @@ -59,7 +60,7 @@ void hooked_exit(int code) { (*runtimeJavaVMPtr)->DestroyJavaVM(runtimeJavaVMPtr); } - orig_exit(code); + UIKit_returnToSplitView(); } int hooked_open(const char *path, int oflag, ...) { diff --git a/Natives/resources/Assets.car b/Natives/resources/Assets.car index 50349590d11b405189f95df08b8d6649102a5e2c..f5194c89b73c143ce8a01e81b13c10ee9d25aed8 100644 GIT binary patch delta 740 zcma*l$4*oM6oBD77nETDVN}G<9V;p-h*;(-s8~@HEB3C~doNf}QJjs!LzuXMK*Cbr zK$h<00bKY9euTYo@#W7+&gLX%GB}|}{GSWfMcTQ!k6dN)KlZAB<^~J?0`FALYXATM delta 745 zcma*l$4*oM6oBD77Zeyk7)22qcdV$Rj#$vSDvH<}R_q;n@4ca5nT;6|U%|u;1{0P( zg5%ERJb()y!OyriF24LZ$=RIbl)es?ml%x0ur>KU-CY<6Q)St$&n2UO&xIc;$gp^Uv2u4ytB~>J;riNNZQAa%~Ml*&6 z#xjmZ#?!GG*g+zbY?JlIm{))Jm#~2EDLF65sO*EQrcL?a&r8+ zbpER4#ImdY}aFI(~<_cH2#&vFRlUv;84tKf7eID?TM?B^UPkF|3UeL=+UeQNC uuX)2;-VH}_{?kA@6$H7WcMG+ob^7b?97|9?{PSDBx$-~uGC%TPDt-eZ?$MwC diff --git a/Natives/resources/glfw_keycodes.plist b/Natives/resources/glfw_keycodes.plist new file mode 100644 index 00000000..df83bb19 --- /dev/null +++ b/Natives/resources/glfw_keycodes.plist @@ -0,0 +1,268 @@ + + + + + GLFW_KEY_UNKNOWN + 0 + GLFW_KEY_SPACE + 32 + GLFW_KEY_APOSTROPHE + 39 + GLFW_KEY_COMMA + 44 + GLFW_KEY_MINUS + 45 + GLFW_KEY_PERIOD + 46 + GLFW_KEY_SLASH + 47 + GLFW_KEY_0 + 48 + GLFW_KEY_1 + 49 + GLFW_KEY_2 + 50 + GLFW_KEY_3 + 51 + GLFW_KEY_4 + 52 + GLFW_KEY_5 + 53 + GLFW_KEY_6 + 54 + GLFW_KEY_7 + 55 + GLFW_KEY_8 + 56 + GLFW_KEY_9 + 57 + GLFW_KEY_SEMICOLON + 59 + GLFW_KEY_EQUAL + 61 + GLFW_KEY_A + 65 + GLFW_KEY_B + 66 + GLFW_KEY_C + 67 + GLFW_KEY_D + 68 + GLFW_KEY_E + 69 + GLFW_KEY_F + 70 + GLFW_KEY_G + 71 + GLFW_KEY_H + 72 + GLFW_KEY_I + 73 + GLFW_KEY_J + 74 + GLFW_KEY_K + 75 + GLFW_KEY_L + 76 + GLFW_KEY_M + 77 + GLFW_KEY_N + 78 + GLFW_KEY_O + 79 + GLFW_KEY_P + 80 + GLFW_KEY_Q + 81 + GLFW_KEY_R + 82 + GLFW_KEY_S + 83 + GLFW_KEY_T + 84 + GLFW_KEY_U + 85 + GLFW_KEY_V + 86 + GLFW_KEY_W + 87 + GLFW_KEY_X + 88 + GLFW_KEY_Y + 89 + GLFW_KEY_Z + 90 + GLFW_KEY_LEFT_BRACKET + 91 + GLFW_KEY_BACKSLASH + 92 + GLFW_KEY_RIGHT_BRACKET + 93 + GLFW_KEY_GRAVE_ACCENT + 96 + GLFW_KEY_WORLD_1 + 161 + GLFW_KEY_WORLD_2 + 162 + GLFW_KEY_ESCAPE + 256 + GLFW_KEY_ENTER + 257 + GLFW_KEY_TAB + 258 + GLFW_KEY_BACKSPACE + 259 + GLFW_KEY_INSERT + 260 + GLFW_KEY_DELETE + 261 + GLFW_KEY_DPAD_RIGHT + 262 + GLFW_KEY_DPAD_LEFT + 263 + GLFW_KEY_DPAD_DOWN + 264 + GLFW_KEY_DPAD_UP + 265 + GLFW_KEY_PAGE_UP + 266 + GLFW_KEY_PAGE_DOWN + 267 + GLFW_KEY_HOME + 268 + GLFW_KEY_END + 269 + GLFW_KEY_CAPS_LOCK + 280 + GLFW_KEY_SCROLL_LOCK + 281 + GLFW_KEY_NUM_LOCK + 282 + GLFW_KEY_PRINT_SCREEN + 283 + GLFW_KEY_PAUSE + 284 + GLFW_KEY_F1 + 290 + GLFW_KEY_F2 + 291 + GLFW_KEY_F3 + 292 + GLFW_KEY_F4 + 293 + GLFW_KEY_F5 + 294 + GLFW_KEY_F6 + 295 + GLFW_KEY_F7 + 296 + GLFW_KEY_F8 + 297 + GLFW_KEY_F9 + 298 + GLFW_KEY_F10 + 299 + GLFW_KEY_F11 + 300 + GLFW_KEY_F12 + 301 + GLFW_KEY_F13 + 302 + GLFW_KEY_F14 + 303 + GLFW_KEY_F15 + 304 + GLFW_KEY_F16 + 305 + GLFW_KEY_F17 + 306 + GLFW_KEY_F18 + 307 + GLFW_KEY_F19 + 308 + GLFW_KEY_F20 + 309 + GLFW_KEY_F21 + 310 + GLFW_KEY_F22 + 311 + GLFW_KEY_F23 + 312 + GLFW_KEY_F24 + 313 + GLFW_KEY_F25 + 314 + GLFW_KEY_NUMPAD_0 + 320 + GLFW_KEY_NUMPAD_1 + 321 + GLFW_KEY_NUMPAD_2 + 322 + GLFW_KEY_NUMPAD_3 + 323 + GLFW_KEY_NUMPAD_4 + 324 + GLFW_KEY_NUMPAD_5 + 325 + GLFW_KEY_NUMPAD_6 + 326 + GLFW_KEY_NUMPAD_7 + 327 + GLFW_KEY_NUMPAD_8 + 328 + GLFW_KEY_NUMPAD_9 + 329 + GLFW_KEY_NUMPAD_DECIMAL + 330 + GLFW_KEY_NUMPAD_DIVIDE + 331 + GLFW_KEY_NUMPAD_MULTIPLY + 332 + GLFW_KEY_NUMPAD_SUBTRACT + 333 + GLFW_KEY_NUMPAD_ADD + 334 + GLFW_KEY_NUMPAD_ENTER + 335 + GLFW_KEY_NUMPAD_EQUAL + 336 + GLFW_KEY_LEFT_SHIFT + 340 + GLFW_KEY_LEFT_CONTROL + 341 + GLFW_KEY_LEFT_ALT + 342 + GLFW_KEY_LEFT_SUPER + 343 + GLFW_KEY_RIGHT_SHIFT + 344 + GLFW_KEY_RIGHT_CONTROL + 345 + GLFW_KEY_RIGHT_ALT + 346 + GLFW_KEY_RIGHT_SUPER + 347 + GLFW_KEY_MENU + 348 + GLFW_KEY_LAST + 348 + SPECIALBTN_KEYBOARD + -1 + SPECIALBTN_TOGGLECTRL + -2 + SPECIALBTN_MOUSEPRI + -3 + SPECIALBTN_MOUSESEC + -4 + SPECIALBTN_VIRTUALMOUSE + -5 + SPECIALBTN_MOUSEMID + -6 + SPECIALBTN_SCROLLUP + -7 + SPECIALBTN_SCROLLDOWN + -8 + SPECIALBTN_MENU + -9 + +