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 50349590..f5194c89 100644 Binary files a/Natives/resources/Assets.car and b/Natives/resources/Assets.car differ 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 + +