From 21599f0b1502dc43a3f8ebdf836d631454aad983 Mon Sep 17 00:00:00 2001 From: James Power Date: Thu, 12 Mar 2026 19:18:51 -0400 Subject: [PATCH 1/3] Add Apple Vision Pro (visionOS) support. --- Project.swift | 4 +- Vendored/CocoaAsyncSocket/Package.swift | 2 +- Vendored/DAKeyboardControl/Package.swift | 2 +- .../Sources/DAKeyboardControl.m | 24 +++- Vendored/FXForms/Package.swift | 2 +- Vendored/FXForms/Sources/FXForms.m | 136 ++++++++---------- Vendored/JSQSystemSoundPlayer/Package.swift | 2 +- Vendored/JTSImageViewController/Package.swift | 2 +- .../Sources/JTSImageViewController.m | 123 +++++++++------- .../Sources/UIImage+JTSImageEffects.m | 8 +- Vendored/KVOController/Package.swift | 2 +- Vendored/Masonry/Package.swift | 2 +- Vendored/MyLilTimer/Package.swift | 2 +- Vendored/OSCache/Package.swift | 2 +- Vendored/QuickDialog/Package.swift | 2 +- .../Sources/DOAutocompleteTextField.m | 14 +- .../Sources/QAutoEntryTableViewCell.m | 2 + .../QuickDialog/Sources/QBadgeTableCell.m | 4 +- .../QuickDialog/Sources/QClassicAppearance.m | 11 +- .../Sources/QDateEntryTableViewCell.m | 2 + .../QuickDialog/Sources/QEntryTableViewCell.m | 8 +- .../QuickDialog/Sources/QFloatTableViewCell.m | 3 +- .../QuickDialog/Sources/QLoadingElement.m | 2 +- Vendored/QuickDialog/Sources/QMailElement.m | 5 +- .../QuickDialog/Sources/QMapViewController.m | 2 +- .../Sources/QPickerTableViewCell.m | 1 - Vendored/QuickDialog/Sources/QRootBuilder.m | 8 +- .../QuickDialog/Sources/QSegmentedElement.m | 1 - Vendored/QuickDialog/Sources/QTableViewCell.m | 6 +- Vendored/QuickDialog/Sources/QTextElement.m | 4 +- Vendored/QuickDialog/Sources/QTextField.m | 6 +- .../Sources/QuickDialogController+Loading.m | 6 +- .../Sources/QuickDialogWebController.m | 4 +- .../Sources/UIColor+ColorUtilities.m | 2 +- Vendored/SAMRateLimit/Package.swift | 2 +- Vendored/SPLCore/Package.swift | 2 +- Vendored/SPLCore/Sources/SPLCore.h | 2 + .../SPLCore/Sources/UIDevice+SSAdditions.m | 6 + .../SPLCore/Sources/UIScreen+SSAdditions.m | 2 + Vendored/SPLCore/Sources/include/SPLCore.h | 2 + Vendored/SPLUserActivity/Package.swift | 2 +- Vendored/SSAccessibility/Package.swift | 2 +- Vendored/SSDataSources/Package.swift | 2 +- Vendored/SSOperations/Package.swift | 2 +- Vendored/TTTAttributedLabel/Package.swift | 2 +- .../Package.swift | 2 +- .../VTAcknowledgementsViewController.m | 21 +-- Vendored/libextobjc/Package.swift | 2 +- Vendored/libtelnet/Package.swift | 2 +- .../Controllers/Client/ClientContainer.swift | 14 +- .../Client/SSClientViewController.m | 10 +- .../Client/SSWorldDisplayController.m | 2 + .../Controllers/SPLHandoffWebViewController.m | 7 +- src/Mudrammer/SSAppDelegate.m | 2 + src/Mudrammer/Views/SSMUDToolbar.m | 6 + src/Mudrammer/Views/SSMudView.m | 4 +- src/Mudrammer/Views/SSTextTableView.m | 2 + src/Mudrammer/WammerSceneDelegate.swift | 13 ++ 58 files changed, 307 insertions(+), 212 deletions(-) diff --git a/Project.swift b/Project.swift index abd790d..9d39650 100644 --- a/Project.swift +++ b/Project.swift @@ -34,10 +34,10 @@ let project = Project( targets: [ .target( name: "Wammer", - destinations: [.iPhone, .iPad, .macCatalyst], + destinations: [.iPhone, .iPad, .macCatalyst, .appleVision], product: .app, bundleId: "org.ncmud.wammer", - deploymentTargets: .iOS("26.0"), + deploymentTargets: .multiplatform(iOS: "26.0", visionOS: "26.0"), infoPlist: .extendingDefault(with: [ "LSApplicationCategoryType": .string("public.app-category.games"), "CFBundleDisplayName": .string("MUDWammer"), diff --git a/Vendored/CocoaAsyncSocket/Package.swift b/Vendored/CocoaAsyncSocket/Package.swift index 2c328bc..fad421c 100644 --- a/Vendored/CocoaAsyncSocket/Package.swift +++ b/Vendored/CocoaAsyncSocket/Package.swift @@ -2,7 +2,7 @@ import PackageDescription let package = Package( name: "CocoaAsyncSocket", - platforms: [.iOS(.v26)], + platforms: [.iOS(.v26), .visionOS(.v26)], products: [.library(name: "CocoaAsyncSocket", targets: ["CocoaAsyncSocket"])], targets: [ .target( diff --git a/Vendored/DAKeyboardControl/Package.swift b/Vendored/DAKeyboardControl/Package.swift index 99d463f..e0ecee7 100644 --- a/Vendored/DAKeyboardControl/Package.swift +++ b/Vendored/DAKeyboardControl/Package.swift @@ -2,7 +2,7 @@ import PackageDescription let package = Package( name: "DAKeyboardControl", - platforms: [.iOS(.v26)], + platforms: [.iOS(.v26), .visionOS(.v26)], products: [.library(name: "DAKeyboardControl", targets: ["DAKeyboardControl"])], targets: [ .target( diff --git a/Vendored/DAKeyboardControl/Sources/DAKeyboardControl.m b/Vendored/DAKeyboardControl/Sources/DAKeyboardControl.m index 5ac0d56..7e550e6 100644 --- a/Vendored/DAKeyboardControl/Sources/DAKeyboardControl.m +++ b/Vendored/DAKeyboardControl/Sources/DAKeyboardControl.m @@ -7,6 +7,26 @@ // #import "DAKeyboardControl.h" + +#if TARGET_OS_VISION + +// No-op stubs for visionOS (keyboard tracking APIs unavailable) +@implementation UIView (DAKeyboardControl) +- (CGFloat)keyboardTriggerOffset { return 0; } +- (void)setKeyboardTriggerOffset:(CGFloat)offset {} +- (BOOL)keyboardWillRecede { return NO; } +- (void)addKeyboardPanningWithActionHandler:(DAKeyboardDidMoveBlock)b {} +- (void)addKeyboardPanningWithFrameBasedActionHandler:(DAKeyboardDidMoveBlock)a constraintBasedActionHandler:(DAKeyboardDidMoveBlock)b {} +- (void)addKeyboardNonpanningWithActionHandler:(DAKeyboardDidMoveBlock)b {} +- (void)addKeyboardNonpanningWithFrameBasedActionHandler:(DAKeyboardDidMoveBlock)a constraintBasedActionHandler:(DAKeyboardDidMoveBlock)b {} +- (void)removeKeyboardControl {} +- (CGRect)keyboardFrameInView { return CGRectZero; } +- (BOOL)isKeyboardOpened { return NO; } +- (void)hideKeyboard { [self endEditing:YES]; } +@end + +#else // !TARGET_OS_VISION + #import @@ -707,4 +727,6 @@ - (BOOL)keyboardWillRecede return touchLocationInKeyboardWindow.y >= thresholdHeight && velocity.y >= 0; } -@end \ No newline at end of file +@end + +#endif // !TARGET_OS_VISION \ No newline at end of file diff --git a/Vendored/FXForms/Package.swift b/Vendored/FXForms/Package.swift index 5f0771d..eb5b59f 100644 --- a/Vendored/FXForms/Package.swift +++ b/Vendored/FXForms/Package.swift @@ -2,7 +2,7 @@ import PackageDescription let package = Package( name: "FXForms", - platforms: [.iOS(.v26)], + platforms: [.iOS(.v26), .visionOS(.v26)], products: [.library(name: "FXForms", targets: ["FXForms"])], targets: [ .target( diff --git a/Vendored/FXForms/Sources/FXForms.m b/Vendored/FXForms/Sources/FXForms.m index f6f85d7..66c481f 100755 --- a/Vendored/FXForms/Sources/FXForms.m +++ b/Vendored/FXForms/Sources/FXForms.m @@ -1102,7 +1102,9 @@ - (void)setSegue:(id)segue segue = FXFormClassFromString(segue) ?: [segue copy]; } +#if !TARGET_OS_VISION NSAssert(segue != [UIStoryboardPopoverSegue class], @"Unfortunately displaying subcontrollers using UIStoryboardPopoverSegue is not supported, as doing so would require calling private methods. To display using a popover, create a custom UIStoryboard subclass instead."); +#endif _segue = segue; } @@ -2377,43 +2379,35 @@ - (void)keyboardDidShow:(NSNotification *)notification { self.originalTableContentInset = tableContentInset; tableContentInset.bottom = heightOfTableViewThatIsCoveredByKeyboard; - UIEdgeInsets tableScrollIndicatorInsets = self.tableView.scrollIndicatorInsets; + UIEdgeInsets tableScrollIndicatorInsets = self.tableView.verticalScrollIndicatorInsets; tableScrollIndicatorInsets.bottom += heightOfTableViewThatIsCoveredByKeyboard; - - [UIView beginAnimations:nil context:nil]; - - // adjust the tableview insets by however much the keyboard is overlapping the tableview - self.tableView.contentInset = tableContentInset; - self.tableView.scrollIndicatorInsets = tableScrollIndicatorInsets; - - UIView *firstResponder = FXFormsFirstResponder(self.tableView); - if ([firstResponder isKindOfClass:[UITextView class]]) { - UITextView *textView = (UITextView *)firstResponder; - - // calculate the position of the cursor in the textView - NSRange range = textView.selectedRange; - UITextPosition *beginning = textView.beginningOfDocument; - UITextPosition *start = [textView positionFromPosition:beginning offset:range.location]; - UITextPosition *end = [textView positionFromPosition:start offset:range.length]; - CGRect caretFrame = [textView caretRectForPosition:end]; - - // convert the cursor to the same coordinate system as the tableview - CGRect caretViewFrame = [textView convertRect:caretFrame toView:self.tableView.superview]; - - // padding makes sure that the cursor isn't sitting just above the keyboard and will adjust to 3 lines of text worth above keyboard - CGFloat padding = textView.font.lineHeight * 3; - CGFloat keyboardToCursorDifference = (caretViewFrame.origin.y + caretViewFrame.size.height) - heightOfTableViewThatIsNotCoveredByKeyboard + padding; - - // if there is a difference then we want to adjust the keyboard, otherwise the cursor is fine to stay where it is and the keyboard doesn't need to move - if (keyboardToCursorDifference > 0.0f) { - // adjust offset by this difference - CGPoint contentOffset = self.tableView.contentOffset; - contentOffset.y += keyboardToCursorDifference; - [self.tableView setContentOffset:contentOffset animated:YES]; + + [UIView animateWithDuration:0.25 animations:^{ + // adjust the tableview insets by however much the keyboard is overlapping the tableview + self.tableView.contentInset = tableContentInset; + self.tableView.verticalScrollIndicatorInsets = tableScrollIndicatorInsets; + } completion:^(BOOL finished) { + UIView *firstResponder = FXFormsFirstResponder(self.tableView); + if ([firstResponder isKindOfClass:[UITextView class]]) { + UITextView *textView = (UITextView *)firstResponder; + + NSRange range = textView.selectedRange; + UITextPosition *beginning = textView.beginningOfDocument; + UITextPosition *start = [textView positionFromPosition:beginning offset:range.location]; + UITextPosition *end = [textView positionFromPosition:start offset:range.length]; + CGRect caretFrame = [textView caretRectForPosition:end]; + CGRect caretViewFrame = [textView convertRect:caretFrame toView:self.tableView.superview]; + + CGFloat padding = textView.font.lineHeight * 3; + CGFloat keyboardToCursorDifference = (caretViewFrame.origin.y + caretViewFrame.size.height) - heightOfTableViewThatIsNotCoveredByKeyboard + padding; + + if (keyboardToCursorDifference > 0.0f) { + CGPoint contentOffset = self.tableView.contentOffset; + contentOffset.y += keyboardToCursorDifference; + [self.tableView setContentOffset:contentOffset animated:YES]; + } } - } - - [UIView commitAnimations]; + }]; } - (void)keyboardWillHide:(NSNotification *)note @@ -2422,17 +2416,15 @@ - (void)keyboardWillHide:(NSNotification *)note if (cell && ![self.delegate isKindOfClass:[UITableViewController class]]) { NSDictionary *keyboardInfo = [note userInfo]; - UIEdgeInsets tableScrollIndicatorInsets = self.tableView.scrollIndicatorInsets; - tableScrollIndicatorInsets.bottom = 0; - - //restore insets - [UIView beginAnimations:nil context:nil]; - [UIView setAnimationCurve:(UIViewAnimationCurve)keyboardInfo[UIKeyboardAnimationCurveUserInfoKey]]; - [UIView setAnimationDuration:[keyboardInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]]; - self.tableView.contentInset = self.originalTableContentInset; - self.tableView.scrollIndicatorInsets = tableScrollIndicatorInsets; - self.originalTableContentInset = UIEdgeInsetsZero; - [UIView commitAnimations]; + NSTimeInterval duration = [keyboardInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; + + [UIView animateWithDuration:duration animations:^{ + self.tableView.contentInset = self.originalTableContentInset; + UIEdgeInsets indicatorInsets = self.tableView.verticalScrollIndicatorInsets; + indicatorInsets.bottom = 0; + self.tableView.verticalScrollIndicatorInsets = indicatorInsets; + self.originalTableContentInset = UIEdgeInsetsZero; + }]; } } @@ -2502,7 +2494,7 @@ - (void)viewDidLoad if (!self.tableView) { - self.tableView = [[UITableView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame + self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStyleGrouped]; } if (!self.tableView.superview) @@ -3401,7 +3393,7 @@ - (void)didSelectWithTableView:(UITableView *)tableView controller:(__unused UIV @end -@interface FXFormImagePickerCell () +@interface FXFormImagePickerCell () @property (nonatomic, strong) UIImagePickerController *imagePickerController; @property (nonatomic, weak) UIViewController *controller; @@ -3484,34 +3476,34 @@ - (void)didSelectWithTableView:(UITableView *)tableView controller:(UIViewContro [FXFormsFirstResponder(tableView) resignFirstResponder]; [tableView deselectRowAtIndexPath:tableView.indexPathForSelectedRow animated:YES]; - if (!TARGET_IPHONE_SIMULATOR && ![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) + BOOL hasCamera = NO; +#if !TARGET_OS_VISION + hasCamera = !TARGET_IPHONE_SIMULATOR && [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]; +#endif + + if (!hasCamera) { self.imagePickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; [controller presentViewController:self.imagePickerController animated:YES completion:nil]; } - else if ([UIAlertController class]) + else { - UIAlertControllerStyle style = (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)? UIAlertControllerStyleAlert: UIAlertControllerStyleActionSheet; + UIAlertControllerStyle style = ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad)? UIAlertControllerStyleAlert: UIAlertControllerStyleActionSheet; UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:style]; - + [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Take Photo", nil) style:UIAlertActionStyleDefault handler:^(__unused UIAlertAction *action) { - [self actionSheet:nil didDismissWithButtonIndex:0]; + [self showPickerWithSourceType:0]; }]]; - + [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Photo Library", nil) style:UIAlertActionStyleDefault handler:^(__unused UIAlertAction *action) { - [self actionSheet:nil didDismissWithButtonIndex:1]; + [self showPickerWithSourceType:1]; }]]; - + [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", nil) style:UIAlertActionStyleCancel handler:NULL]]; - + self.controller = controller; [controller presentViewController:alert animated:YES completion:NULL]; } - else - { - self.controller = controller; - [[[UIActionSheet alloc] initWithTitle:nil delegate:self cancelButtonTitle:NSLocalizedString(@"Cancel", nil) destructiveButtonTitle:nil otherButtonTitles:NSLocalizedString(@"Take Photo", nil), NSLocalizedString(@"Photo Library", nil), nil] showInView:controller.view]; - } } - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker @@ -3527,23 +3519,15 @@ - (void)imagePickerController:(UIImagePickerController *)picker didFinishPicking [self update]; } -- (void)actionSheet:(__unused UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex +- (void)showPickerWithSourceType:(NSInteger)buttonIndex { UIImagePickerControllerSourceType sourceType = UIImagePickerControllerSourceTypePhotoLibrary; - switch (buttonIndex) - { - case 0: - { - sourceType = UIImagePickerControllerSourceTypeCamera; - break; - } - case 1: - { - sourceType = UIImagePickerControllerSourceTypePhotoLibrary; - break; - } +#if !TARGET_OS_VISION + if (buttonIndex == 0) { + sourceType = UIImagePickerControllerSourceTypeCamera; } - +#endif + if ([UIImagePickerController isSourceTypeAvailable:sourceType]) { self.imagePickerController.sourceType = sourceType; diff --git a/Vendored/JSQSystemSoundPlayer/Package.swift b/Vendored/JSQSystemSoundPlayer/Package.swift index 40145be..03ec9ba 100644 --- a/Vendored/JSQSystemSoundPlayer/Package.swift +++ b/Vendored/JSQSystemSoundPlayer/Package.swift @@ -2,7 +2,7 @@ import PackageDescription let package = Package( name: "JSQSystemSoundPlayer", - platforms: [.iOS(.v26)], + platforms: [.iOS(.v26), .visionOS(.v26)], products: [.library(name: "JSQSystemSoundPlayer", targets: ["JSQSystemSoundPlayer"])], targets: [ .target( diff --git a/Vendored/JTSImageViewController/Package.swift b/Vendored/JTSImageViewController/Package.swift index 9605975..6a31c80 100644 --- a/Vendored/JTSImageViewController/Package.swift +++ b/Vendored/JTSImageViewController/Package.swift @@ -2,7 +2,7 @@ import PackageDescription let package = Package( name: "JTSImageViewController", - platforms: [.iOS(.v26)], + platforms: [.iOS(.v26), .visionOS(.v26)], products: [.library(name: "JTSImageViewController", targets: ["JTSImageViewController"])], targets: [ .target( diff --git a/Vendored/JTSImageViewController/Sources/JTSImageViewController.m b/Vendored/JTSImageViewController/Sources/JTSImageViewController.m index d1e1dbb..628f0e4 100644 --- a/Vendored/JTSImageViewController/Sources/JTSImageViewController.m +++ b/Vendored/JTSImageViewController/Sources/JTSImageViewController.m @@ -8,10 +8,20 @@ #import "JTSImageViewController.h" +#import + #import "JTSSimpleImageDownloader.h" #import "UIImage+JTSImageEffects.h" #import "UIApplication+JTSImageViewController.h" +static inline UIInterfaceOrientation JTS_CurrentInterfaceOrientation(void) { +#if TARGET_OS_VISION + return UIInterfaceOrientationPortrait; +#else + return JTS_CurrentInterfaceOrientation(); +#endif +} + ///-------------------------------------------------------------------------------------------------------------------- /// Definitions ///-------------------------------------------------------------------------------------------------------------------- @@ -126,7 +136,9 @@ - (instancetype)initWithImageInfo:(JTSImageInfo *)imageInfo self = [super initWithNibName:nil bundle:nil]; if (self) { +#if !TARGET_OS_VISION [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deviceOrientationDidChange:) name:UIDeviceOrientationDidChangeNotification object:nil]; +#endif _imageInfo = imageInfo; _currentSnapshotRotationTransform = CGAffineTransformIdentity; _mode = mode; @@ -143,8 +155,13 @@ - (void)showFromViewController:(UIViewController *)viewController self.transition = transition; +#if TARGET_OS_VISION + _startingInfo.statusBarHiddenPriorToPresentation = NO; + _startingInfo.statusBarStylePriorToPresentation = UIStatusBarStyleDefault; +#else _startingInfo.statusBarHiddenPriorToPresentation = [UIApplication sharedApplication].statusBarHidden; _startingInfo.statusBarStylePriorToPresentation = [UIApplication sharedApplication].statusBarStyle; +#endif if (self.mode == JTSImageViewControllerMode_Image) { if (transition == JTSImageViewControllerTransition_FromOffscreen) { @@ -194,7 +211,9 @@ - (void)dismiss:(BOOL)animated { #pragma mark - NSObject - (void)dealloc { +#if !TARGET_OS_VISION [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil]; +#endif [_imageDownloadDataTask cancel]; [self cancelProgressTimer]; } @@ -221,7 +240,7 @@ - (NSUInteger)supportedInterfaceOrientations { NSUInteger mask; if (self.flags.viewHasAppeared == NO) { - switch ([UIApplication sharedApplication].statusBarOrientation) { + switch (JTS_CurrentInterfaceOrientation()) { case UIInterfaceOrientationLandscapeLeft: mask = UIInterfaceOrientationMaskLandscapeLeft; break; @@ -251,18 +270,20 @@ - (BOOL)shouldAutorotate { return (_flags.isAnimatingAPresentationOrDismissal == NO); } +#if !TARGET_OS_VISION - (BOOL)prefersStatusBarHidden { - + if (_flags.isPresented || _flags.isTransitioningFromInitialModalToInteractiveState) { return YES; } - + return _startingInfo.statusBarHiddenPriorToPresentation; } - (UIStatusBarAnimation)preferredStatusBarUpdateAnimation { return UIStatusBarAnimationFade; } +#endif - (UIModalTransitionStyle)modalTransitionStyle { return UIModalTransitionStyleCrossDissolve; @@ -288,8 +309,8 @@ - (void)viewDidLayoutSubviews { - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - if (self.lastUsedOrientation != [UIApplication sharedApplication].statusBarOrientation) { - self.lastUsedOrientation = [UIApplication sharedApplication].statusBarOrientation; + if (self.lastUsedOrientation != JTS_CurrentInterfaceOrientation()) { + self.lastUsedOrientation = JTS_CurrentInterfaceOrientation(); _flags.rotationTransformIsDirty = YES; [self updateLayoutsForCurrentOrientation]; } @@ -327,7 +348,7 @@ - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id context) { typeof(self) strongSelf = weakSelf; - strongSelf.lastUsedOrientation = [UIApplication sharedApplication].statusBarOrientation; + strongSelf.lastUsedOrientation = JTS_CurrentInterfaceOrientation(); JTSImageViewControllerFlags flags = strongSelf.flags; flags.isRotating = NO; strongSelf.flags = flags; @@ -335,8 +356,9 @@ - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id __FLT_EPSILON__; BOOL hasSaturationChange = fabs(saturationDeltaFactor - 1.) > __FLT_EPSILON__; if (hasBlur || hasSaturationChange) { - UIGraphicsBeginImageContextWithOptions(self.size, NO, [[UIScreen mainScreen] scale]); + UIGraphicsBeginImageContextWithOptions(self.size, NO, 0); CGContextRef effectInContext = UIGraphicsGetCurrentContext(); CGContextScaleCTM(effectInContext, 1.0, -1.0); CGContextTranslateCTM(effectInContext, 0, -self.size.height); @@ -176,7 +176,7 @@ - (UIImage *)JTS_applyBlurWithRadius:(CGFloat)blurRadius effectInBuffer.height = CGBitmapContextGetHeight(effectInContext); effectInBuffer.rowBytes = CGBitmapContextGetBytesPerRow(effectInContext); - UIGraphicsBeginImageContextWithOptions(self.size, NO, [[UIScreen mainScreen] scale]); + UIGraphicsBeginImageContextWithOptions(self.size, NO, 0); CGContextRef effectOutContext = UIGraphicsGetCurrentContext(); vImage_Buffer effectOutBuffer; effectOutBuffer.data = CGBitmapContextGetData(effectOutContext); @@ -197,7 +197,7 @@ - (UIImage *)JTS_applyBlurWithRadius:(CGFloat)blurRadius // // ... if d is odd, use three box-blurs of size 'd', centered on the output pixel. // - CGFloat inputRadius = blurRadius * [[UIScreen mainScreen] scale]; + CGFloat inputRadius = blurRadius * 0; uint32_t radius = floor(inputRadius * 3. * sqrt(2 * M_PI) / 4 + 0.5); if (radius % 2 != 1) { radius += 1; // force radius to be odd so that the three box-blur methodology works. @@ -239,7 +239,7 @@ - (UIImage *)JTS_applyBlurWithRadius:(CGFloat)blurRadius } // Set up output context. - UIGraphicsBeginImageContextWithOptions(self.size, NO, [[UIScreen mainScreen] scale]); + UIGraphicsBeginImageContextWithOptions(self.size, NO, 0); CGContextRef outputContext = UIGraphicsGetCurrentContext(); CGContextScaleCTM(outputContext, 1.0, -1.0); CGContextTranslateCTM(outputContext, 0, -self.size.height); diff --git a/Vendored/KVOController/Package.swift b/Vendored/KVOController/Package.swift index f0e6662..f6bc81e 100644 --- a/Vendored/KVOController/Package.swift +++ b/Vendored/KVOController/Package.swift @@ -2,7 +2,7 @@ import PackageDescription let package = Package( name: "KVOController", - platforms: [.iOS(.v26)], + platforms: [.iOS(.v26), .visionOS(.v26)], products: [.library(name: "KVOController", targets: ["KVOController"])], targets: [ .target( diff --git a/Vendored/Masonry/Package.swift b/Vendored/Masonry/Package.swift index 64f6143..092153f 100644 --- a/Vendored/Masonry/Package.swift +++ b/Vendored/Masonry/Package.swift @@ -2,7 +2,7 @@ import PackageDescription let package = Package( name: "Masonry", - platforms: [.iOS(.v26)], + platforms: [.iOS(.v26), .visionOS(.v26)], products: [.library(name: "Masonry", targets: ["Masonry"])], targets: [ .target( diff --git a/Vendored/MyLilTimer/Package.swift b/Vendored/MyLilTimer/Package.swift index ddac8ca..22b7543 100644 --- a/Vendored/MyLilTimer/Package.swift +++ b/Vendored/MyLilTimer/Package.swift @@ -2,7 +2,7 @@ import PackageDescription let package = Package( name: "MyLilTimer", - platforms: [.iOS(.v26)], + platforms: [.iOS(.v26), .visionOS(.v26)], products: [.library(name: "MyLilTimer", targets: ["MyLilTimer"])], targets: [ .target( diff --git a/Vendored/OSCache/Package.swift b/Vendored/OSCache/Package.swift index 0436117..2c5ed00 100644 --- a/Vendored/OSCache/Package.swift +++ b/Vendored/OSCache/Package.swift @@ -2,7 +2,7 @@ import PackageDescription let package = Package( name: "OSCache", - platforms: [.iOS(.v26)], + platforms: [.iOS(.v26), .visionOS(.v26)], products: [.library(name: "OSCache", targets: ["OSCache"])], targets: [ .target( diff --git a/Vendored/QuickDialog/Package.swift b/Vendored/QuickDialog/Package.swift index 4aa4ef3..b671320 100644 --- a/Vendored/QuickDialog/Package.swift +++ b/Vendored/QuickDialog/Package.swift @@ -2,7 +2,7 @@ import PackageDescription let package = Package( name: "QuickDialog", - platforms: [.iOS(.v26)], + platforms: [.iOS(.v26), .visionOS(.v26)], products: [.library(name: "QuickDialog", targets: ["QuickDialog"])], targets: [ .target( diff --git a/Vendored/QuickDialog/Sources/DOAutocompleteTextField.m b/Vendored/QuickDialog/Sources/DOAutocompleteTextField.m index 1e8e555..659a35e 100644 --- a/Vendored/QuickDialog/Sources/DOAutocompleteTextField.m +++ b/Vendored/QuickDialog/Sources/DOAutocompleteTextField.m @@ -107,14 +107,16 @@ - (CGRect)autocompleteRectForBounds:(CGRect)bounds CGRect textRect = [self textRectForBounds:self.bounds]; // NSLog(@"textRect: %@", NSStringFromCGRect(textRect)); - CGSize prefixTextSize = [self.text sizeWithFont:self.font - constrainedToSize:textRect.size - lineBreakMode:NSLineBreakByCharWrapping]; + CGSize prefixTextSize = [self.text boundingRectWithSize:textRect.size + options:NSStringDrawingUsesLineFragmentOrigin + attributes:@{NSFontAttributeName: self.font} + context:nil].size; // NSLog(@"prefixTextSize: %@", NSStringFromCGSize(prefixTextSize)); - CGSize autocompleteTextSize = [_autoCompleteString sizeWithFont:self.font - constrainedToSize:CGSizeMake(textRect.size.width-prefixTextSize.width, textRect.size.height) - lineBreakMode:NSLineBreakByCharWrapping]; + CGSize autocompleteTextSize = [_autoCompleteString boundingRectWithSize:CGSizeMake(textRect.size.width-prefixTextSize.width, textRect.size.height) + options:NSStringDrawingUsesLineFragmentOrigin + attributes:@{NSFontAttributeName: self.font} + context:nil].size; // NSLog(@"autocompleteTextSize: %@", NSStringFromCGSize(autocompleteTextSize)); diff --git a/Vendored/QuickDialog/Sources/QAutoEntryTableViewCell.m b/Vendored/QuickDialog/Sources/QAutoEntryTableViewCell.m index 7e8e004..7769f0f 100644 --- a/Vendored/QuickDialog/Sources/QAutoEntryTableViewCell.m +++ b/Vendored/QuickDialog/Sources/QAutoEntryTableViewCell.m @@ -71,11 +71,13 @@ - (void)prepareForElement:(QEntryElement *)element inTableView:(QuickDialogTable _autoCompleteField.returnKeyType = _autoEntryElement.returnKeyType; _autoCompleteField.enablesReturnKeyAutomatically = _autoEntryElement.enablesReturnKeyAutomatically; +#if !TARGET_OS_VISION if (_autoEntryElement.hiddenToolbar){ _autoCompleteField.inputAccessoryView = nil; } else { _autoCompleteField.inputAccessoryView = [self createActionBar]; } +#endif _autoCompleteField.userInteractionEnabled = element.enabled; diff --git a/Vendored/QuickDialog/Sources/QBadgeTableCell.m b/Vendored/QuickDialog/Sources/QBadgeTableCell.m index f33d093..f79bcbb 100644 --- a/Vendored/QuickDialog/Sources/QBadgeTableCell.m +++ b/Vendored/QuickDialog/Sources/QBadgeTableCell.m @@ -30,7 +30,7 @@ - (QBadgeTableCell *)init { [self.contentView addSubview:_badgeLabel]; _badgeLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; _badgeLabel.contentMode = UIViewContentModeRedraw; - _badgeLabel.contentStretch = CGRectMake(1., 0., 0., 0.); + // contentStretch removed (deprecated, unavailable on visionOS) self.selectionStyle = UITableViewCellSelectionStyleNone; } return self; @@ -39,7 +39,7 @@ - (QBadgeTableCell *)init { - (void)layoutSubviews { [super layoutSubviews]; CGRect rect = self.contentView.frame; - CGSize badgeTextSize = [_badgeLabel.text sizeWithFont:_badgeLabel.font]; + CGSize badgeTextSize = [_badgeLabel.text boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:0 attributes:@{NSFontAttributeName: _badgeLabel.font} context:nil].size; _badgeLabel.frame = CGRectIntegral(CGRectMake(rect.size.width - badgeTextSize.width - 10, ((rect.size.height - badgeTextSize.height) / 2)+1, badgeTextSize.width, badgeTextSize.height)); CGRect lblFrame = self.textLabel.frame; diff --git a/Vendored/QuickDialog/Sources/QClassicAppearance.m b/Vendored/QuickDialog/Sources/QClassicAppearance.m index c383c0f..7ff66f7 100644 --- a/Vendored/QuickDialog/Sources/QClassicAppearance.m +++ b/Vendored/QuickDialog/Sources/QClassicAppearance.m @@ -84,7 +84,7 @@ - (UIView *)buildFooterForSection:(QSection *)section andTableView:(QuickDialogT float margin = [self currentGroupedTableViewMarginForTableView:tableView] + 8; if (self.sectionFooterFont!=nil && tableView.style == UITableViewStyleGrouped){ - CGSize textSize = [section.footer sizeWithFont:self.sectionFooterFont constrainedToSize:CGSizeMake(tableView.bounds.size.width-margin-margin, 1000000)]; + CGSize textSize = [section.footer boundingRectWithSize:CGSizeMake(tableView.bounds.size.width-margin-margin, 1000000) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName: self.sectionFooterFont} context:nil].size; UIView *containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, tableView.bounds.size.width, textSize.height)]; containerView.backgroundColor = [UIColor clearColor]; containerView.autoresizingMask = UIViewAutoresizingFlexibleWidth; @@ -120,7 +120,7 @@ - (CGFloat)heightForHeaderInSection:(QSection *)section andTableView:(QuickDialo return section.footer == NULL ? -1 - : [section.title sizeWithFont:self.sectionTitleFont constrainedToSize:CGSizeMake(tableView.bounds.size.width-margin-margin, 1000000)].height+22; + : [section.title boundingRectWithSize:CGSizeMake(tableView.bounds.size.width-margin-margin, 1000000) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName: self.sectionTitleFont} context:nil].size.height+22; } CGFloat stringTitleHeight = 0; @@ -130,9 +130,8 @@ - (CGFloat)heightForHeaderInSection:(QSection *)section andTableView:(QuickDialo CGFloat maxHeight = 9999; CGSize maximumLabelSize = CGSizeMake(maxWidth,maxHeight); QAppearance *appearance = ((QuickDialogTableView *)tableView).root.appearance; - CGSize expectedLabelSize = [section.title sizeWithFont:appearance==nil? [UIFont systemFontOfSize:[UIFont labelFontSize]] : appearance.sectionTitleFont - constrainedToSize:maximumLabelSize - lineBreakMode:NSLineBreakByWordWrapping]; + UIFont *titleFont = appearance==nil? [UIFont systemFontOfSize:[UIFont labelFontSize]] : appearance.sectionTitleFont; + CGSize expectedLabelSize = [section.title boundingRectWithSize:maximumLabelSize options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName: titleFont} context:nil].size; stringTitleHeight = expectedLabelSize.height+24.f; } @@ -151,7 +150,7 @@ - (CGFloat)heightForFooterInSection:(QSection *)section andTableView:(QuickDialo return section.footer == NULL ? -1 - : [section.footer sizeWithFont:appearance.sectionFooterFont constrainedToSize:CGSizeMake(tableView.bounds.size.width-margin-margin, 1000000)].height+22; + : [section.footer boundingRectWithSize:CGSizeMake(tableView.bounds.size.width-margin-margin, 1000000) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName: appearance.sectionFooterFont} context:nil].size.height+22; } - (void)cell:(UITableViewCell *)cell willAppearForElement:(QElement *)element atIndexPath:(NSIndexPath *)path { diff --git a/Vendored/QuickDialog/Sources/QDateEntryTableViewCell.m b/Vendored/QuickDialog/Sources/QDateEntryTableViewCell.m index 4274ee0..68fabfd 100644 --- a/Vendored/QuickDialog/Sources/QDateEntryTableViewCell.m +++ b/Vendored/QuickDialog/Sources/QDateEntryTableViewCell.m @@ -139,7 +139,9 @@ - (void)prepareForElement:(QEntryElement *)element inTableView:(QuickDialogTable _textField.text = value; _textField.placeholder = dateElement.placeholder; +#if !TARGET_OS_VISION _textField.inputAccessoryView.hidden = dateElement.hiddenToolbar; +#endif self.centeredLabel.textColor = dateElement.appearance.entryTextColorEnabled; } diff --git a/Vendored/QuickDialog/Sources/QEntryTableViewCell.m b/Vendored/QuickDialog/Sources/QEntryTableViewCell.m index 959c5b1..1f543db 100644 --- a/Vendored/QuickDialog/Sources/QEntryTableViewCell.m +++ b/Vendored/QuickDialog/Sources/QEntryTableViewCell.m @@ -35,7 +35,7 @@ -(UIToolbar *)createActionBar { _prevNext = [[UISegmentedControl alloc] initWithItems:[NSArray arrayWithObjects:NSLocalizedString(@"Previous", @""), NSLocalizedString(@"Next", @""), nil]]; _prevNext.momentary = YES; - _prevNext.segmentedControlStyle = UISegmentedControlStyleBar; + // segmentedControlStyle removed (deprecated) _prevNext.tintColor = actionBar.tintColor; [_prevNext addTarget:self action:@selector(handleActionBarPreviousNext:) forControlEvents:UIControlEventValueChanged]; UIBarButtonItem *prevNextWrapper = [[UIBarButtonItem alloc] initWithCustomView:_prevNext]; @@ -97,7 +97,7 @@ - (CGRect)calculateFrameForEntryElement { QEntryElement *q = (QEntryElement*)el; CGFloat imageWidth = q.image == NULL ? 0 : self.imageView.frame.size.width; CGFloat fontSize = self.textLabel.font.pointSize == 0? 17 : self.textLabel.font.pointSize; - CGSize size = [((QEntryElement *)el).title sizeWithFont:[self.textLabel.font fontWithSize:fontSize] forWidth:CGFLOAT_MAX lineBreakMode:NSLineBreakByWordWrapping] ; + CGSize size = [((QEntryElement *)el).title boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName: [self.textLabel.font fontWithSize:fontSize]} context:nil].size; CGFloat width = size.width + imageWidth + 20; if (width>titleWidth) titleWidth = width; @@ -148,6 +148,7 @@ - (void)prepareForElement:(QEntryElement *)element inTableView:(QuickDialogTable self.accessoryType = _entryElement.accessoryType; +#if !TARGET_OS_VISION if (_entryElement.hiddenToolbar){ _textField.inputAccessoryView = nil; } else if (_textField!=nil){ @@ -156,6 +157,7 @@ - (void)prepareForElement:(QEntryElement *)element inTableView:(QuickDialogTable toolbar.translucent = element.appearance.toolbarTranslucent; _textField.inputAccessoryView = toolbar; } +#endif [self updatePrevNextStatus]; @@ -294,7 +296,7 @@ - (BOOL)handleActionBarDone:(UIBarButtonItem *)doneButton { [self endEditing:YES]; [self endEditing:NO]; [_textField resignFirstResponder]; - [[[UIApplication sharedApplication] keyWindow] endEditing:YES]; + [self.window endEditing:YES]; if(_entryElement && _entryElement.delegate && [_entryElement.delegate respondsToSelector:@selector(QEntryMustReturnForElement:andCell:)]){ [_entryElement.delegate QEntryMustReturnForElement:_entryElement andCell:self]; diff --git a/Vendored/QuickDialog/Sources/QFloatTableViewCell.m b/Vendored/QuickDialog/Sources/QFloatTableViewCell.m index 3349511..35b6100 100644 --- a/Vendored/QuickDialog/Sources/QFloatTableViewCell.m +++ b/Vendored/QuickDialog/Sources/QFloatTableViewCell.m @@ -30,7 +30,8 @@ - (id)initWithFrame:(CGRect)frame { - (void)layoutSubviews { [super layoutSubviews]; - self.textLabel.frame = CGRectMake(self.textLabel.frame.origin.x, self.textLabel.frame.origin.y, [self.textLabel.text sizeWithFont:self.textLabel.font].width, self.textLabel.frame.size.height); + CGSize labelSize = [self.textLabel.text boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:0 attributes:@{NSFontAttributeName: self.textLabel.font} context:nil].size; + self.textLabel.frame = CGRectMake(self.textLabel.frame.origin.x, self.textLabel.frame.origin.y, labelSize.width, self.textLabel.frame.size.height); CGFloat width = self.textLabel.frame.origin.x + self.textLabel.frame.size.width; diff --git a/Vendored/QuickDialog/Sources/QLoadingElement.m b/Vendored/QuickDialog/Sources/QLoadingElement.m index 4262ffe..3956abf 100644 --- a/Vendored/QuickDialog/Sources/QLoadingElement.m +++ b/Vendored/QuickDialog/Sources/QLoadingElement.m @@ -24,7 +24,7 @@ @implementation QLoadingElement { - (QLoadingElement *)init { self = [super init]; - self.indicatorStyle = UIActivityIndicatorViewStyleGray; + self.indicatorStyle = UIActivityIndicatorViewStyleMedium; return self; } diff --git a/Vendored/QuickDialog/Sources/QMailElement.m b/Vendored/QuickDialog/Sources/QMailElement.m index 6bc3271..be0104d 100644 --- a/Vendored/QuickDialog/Sources/QMailElement.m +++ b/Vendored/QuickDialog/Sources/QMailElement.m @@ -52,8 +52,9 @@ - (void)selected:(QuickDialogTableView *)tableView controller:(QuickDialogContro [controller displayViewController:mc]; } else { - UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"No Mail Accounts" message:@"Please set up a Mail account in order to send email." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; - [alert show]; + UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"No Mail Accounts" message:@"Please set up a Mail account in order to send email." preferredStyle:UIAlertControllerStyleAlert]; + [alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]]; + [controller presentViewController:alert animated:YES completion:nil]; } } diff --git a/Vendored/QuickDialog/Sources/QMapViewController.m b/Vendored/QuickDialog/Sources/QMapViewController.m index 16212ff..a602703 100644 --- a/Vendored/QuickDialog/Sources/QMapViewController.m +++ b/Vendored/QuickDialog/Sources/QMapViewController.m @@ -56,7 +56,7 @@ - (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id - @end +#endif @implementation SPLHandoffWebViewController - (instancetype)initWithURL:(NSURL *)url { self = [super initWithURL:url]; if (self) { +#if !TARGET_OS_VISION self.delegate = self; +#endif self.webActivity = [SPLWebActivity activityWithURL:url]; } return self; @@ -32,6 +35,7 @@ - (void)viewWillDisappear:(BOOL)animated { } } +#if !TARGET_OS_VISION #pragma mark - SFSafariViewControllerDelegate - (void)safariViewController:(SFSafariViewController *)controller didCompleteInitialNavigation:(BOOL)didLoadSuccessfully { @@ -41,5 +45,6 @@ - (void)safariViewController:(SFSafariViewController *)controller didCompleteIni - (void)safariViewControllerDidFinish:(SFSafariViewController *)controller { [self dismissViewControllerAnimated:YES completion:nil]; } +#endif @end diff --git a/src/Mudrammer/SSAppDelegate.m b/src/Mudrammer/SSAppDelegate.m index 818c9d7..8804b69 100644 --- a/src/Mudrammer/SSAppDelegate.m +++ b/src/Mudrammer/SSAppDelegate.m @@ -57,7 +57,9 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( [SSThemes sharedThemer]; // UIAppearance™ Inside® +#if !TARGET_OS_VISION application.idleTimerDisabled = YES; +#endif // disallow webview cache NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:nil]; diff --git a/src/Mudrammer/Views/SSMUDToolbar.m b/src/Mudrammer/Views/SSMUDToolbar.m index ea4b760..485cc13 100644 --- a/src/Mudrammer/Views/SSMUDToolbar.m +++ b/src/Mudrammer/Views/SSMUDToolbar.m @@ -11,7 +11,9 @@ #import "SSSettingsViewController.h" #import "SSAccessoryToolbar.h" #import "SSMudHistoryDelegate.h" +#if !TARGET_OS_VISION #import +#endif @import Masonry; UIEdgeInsets const kToolbarInsets = (UIEdgeInsets) { 4, 8, 4, 8 }; @@ -128,10 +130,12 @@ - (instancetype)initWithFrame:(CGRect)frame { name:NSUserDefaultsDidChangeNotification object:nil]; +#if !TARGET_OS_VISION [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refreshTextViewHeight) name:UIDeviceOrientationDidChangeNotification object:nil]; +#endif } return self; @@ -352,6 +356,7 @@ - (void)setDarkKeyboardEnabled:(BOOL)enabled { } - (void)setInputAccessoryBarEnabled:(BOOL)enabled { +#if !TARGET_OS_VISION dispatch_async( dispatch_get_main_queue(), ^{ BOOL isEnabled = self.textView.inputAccessoryView != nil && CGRectGetHeight(self.textView.inputAccessoryView.frame) > 0; @@ -371,6 +376,7 @@ - (void)setInputAccessoryBarEnabled:(BOOL)enabled { self.textView.inputAccessoryView = toolbar; } }); +#endif } - (void)userDefaultsDidChange:(NSNotification *)notification { diff --git a/src/Mudrammer/Views/SSMudView.m b/src/Mudrammer/Views/SSMudView.m index cdb9793..6e0c28c 100644 --- a/src/Mudrammer/Views/SSMudView.m +++ b/src/Mudrammer/Views/SSMudView.m @@ -88,7 +88,7 @@ - (instancetype)initWithFrame:(CGRect)frame { [self.radialControl setEnabled:NO]; [self addSubview:self.radialControl]; -#if TARGET_OS_MACCATALYST +#if TARGET_OS_MACCATALYST || TARGET_OS_VISION self.movementControl.hidden = YES; self.radialControl.hidden = YES; #endif @@ -151,7 +151,7 @@ - (instancetype)initWithFrame:(CGRect)frame { } - (void)setKeyboardPanningEnabled:(BOOL)enabled { -#if TARGET_OS_MACCATALYST +#if TARGET_OS_MACCATALYST || TARGET_OS_VISION return; #else [self removeKeyboardControl]; diff --git a/src/Mudrammer/Views/SSTextTableView.m b/src/Mudrammer/Views/SSTextTableView.m index 915577a..df495ee 100644 --- a/src/Mudrammer/Views/SSTextTableView.m +++ b/src/Mudrammer/Views/SSTextTableView.m @@ -49,11 +49,13 @@ - (instancetype)initWithFrame:(CGRect)frame { alpha:0.5f]; }]; +#if !TARGET_OS_VISION // Scroll after rotation [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deviceOrientationDidChange) name:UIDeviceOrientationDidChangeNotification object:nil]; +#endif } return self; diff --git a/src/Mudrammer/WammerSceneDelegate.swift b/src/Mudrammer/WammerSceneDelegate.swift index b433eb3..0505bc6 100644 --- a/src/Mudrammer/WammerSceneDelegate.swift +++ b/src/Mudrammer/WammerSceneDelegate.swift @@ -4,7 +4,9 @@ import UserNotifications @objc(WammerSceneDelegate) class WammerSceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? + #if !os(visionOS) private var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid + #endif func scene( _ scene: UIScene, @@ -18,6 +20,11 @@ class WammerSceneDelegate: UIResponder, UIWindowSceneDelegate { window.makeKeyAndVisible() self.window = window + #if os(visionOS) + let prefs = UIWindowScene.GeometryPreferences.Vision(size: CGSize(width: 1280, height: 960)) + windowScene.requestGeometryUpdate(prefs) + #endif + for urlContext in connectionOptions.urlContexts { handleURL(urlContext.url) } @@ -35,20 +42,26 @@ class WammerSceneDelegate: UIResponder, UIWindowSceneDelegate { } func sceneDidEnterBackground(_ scene: UIScene) { + #if !os(visionOS) backgroundTaskID = UIApplication.shared.beginBackgroundTask { [weak self] in self?.endBackgroundTask() } + #endif } func sceneWillEnterForeground(_ scene: UIScene) { + #if !os(visionOS) endBackgroundTask() + #endif } + #if !os(visionOS) private func endBackgroundTask() { guard backgroundTaskID != .invalid else { return } UIApplication.shared.endBackgroundTask(backgroundTaskID) backgroundTaskID = .invalid } + #endif private func handleURL(_ url: URL) { guard url.scheme == "telnet", let host = url.host?.lowercased() else { return } From 443b8d85f9b29f09d7d2d3641fbeda9883036b18 Mon Sep 17 00:00:00 2001 From: James Power Date: Thu, 12 Mar 2026 19:26:27 -0400 Subject: [PATCH 2/3] Add visionOS build to CI pipeline. --- .github/workflows/ci.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9a51733..7476c0d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,6 +40,29 @@ jobs: -skipPackagePluginValidation \ | xcbeautify + build-visionos: + runs-on: macos-26 + steps: + - uses: actions/checkout@v4 + + - name: Select Xcode 26.3 + run: sudo xcode-select -s /Applications/Xcode_26.3.0.app/Contents/Developer + + - name: Install tools + run: brew install tuist xcbeautify + + - name: Generate project + run: tuist generate --no-open + + - name: Build (visionOS) + run: | + xcodebuild build \ + -workspace Wammer.xcworkspace \ + -scheme Wammer \ + -destination 'platform=visionOS Simulator,name=Apple Vision Pro' \ + -skipPackagePluginValidation \ + | xcbeautify + test: runs-on: macos-26 needs: build From 4f42d9fdcaa046b35cf0873e980fd54e5f1b3ffa Mon Sep 17 00:00:00 2001 From: James Power Date: Thu, 12 Mar 2026 19:26:59 -0400 Subject: [PATCH 3/3] Add visionOS platform badge to README. --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 512fd18..917a8ee 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,13 @@ [![Build](https://github.com/ncmud/Wammer/actions/workflows/ci.yml/badge.svg)](https://github.com/ncmud/Wammer/actions/workflows/ci.yml) [![Swift](https://img.shields.io/badge/Swift-6.2-orange)](https://swift.org) [![Platform](https://img.shields.io/badge/platform-iOS%2026-blue)](https://developer.apple.com/ios/) +[![Platform](https://img.shields.io/badge/platform-visionOS%2026-blue)](https://developer.apple.com/visionos/) ![It's dangerous!](https://img.shields.io/badge/You_are_likely_to_be_eaten_by_a-grue-red.svg) [![Take this.](https://img.shields.io/badge/get-lamp-yellow.svg)](http://getlamp.com) This is a fork of [MUDRammer](https://github.com/splinesoft/MUDRammer), a MUD client for iPhone and iPad originally created by [Jonathan Hersh](https://github.com/jhersh). MUDRammer was a fantastic piece of work — a polished, accessible, and thoughtfully designed MUD client that served the community well from its first App Store release in February 2013 through its removal in March 2025. -This fork aims to re-release the app under new branding with proper attribution to Jonathan and the original MUDRammer project. The codebase has been modernized to build with current tools and target iOS 26. +This fork aims to re-release the app under new branding with proper attribution to Jonathan and the original MUDRammer project. The codebase has been modernized to build with current tools and targets iOS 26, Mac Catalyst, and visionOS 26. ## Getting Started