Compare commits

...

28 Commits

Author SHA1 Message Date
Jesús A. Álvarez fdbf6aab7e iPad: when creating a new window, take over and close the previous one 2024-03-04 23:54:25 +01:00
Jesús A. Álvarez 19c0072d4c fix quick actions 2024-03-04 23:39:41 +01:00
Jesús A. Álvarez da3f639ef2 prefer pixel-perfect scaling when filter is set to nearest 2024-03-02 11:51:29 +01:00
Jesús A. Álvarez 65d377884b auto-hide home indicator 2024-03-01 22:49:04 +01:00
Jesús A. Álvarez 2dab9cca28 update README 2024-03-01 22:24:34 +01:00
Jesús A. Álvarez fcb71f79ff visionOS: use native toggles in menus 2024-02-17 15:21:41 +01:00
Jesús A. Álvarez 2ccef4e3a6 visionOS: add “run in background” setting and respect it 2024-02-17 15:21:41 +01:00
Jesús A. Álvarez fca6943bfe close keyboard when closing screen 2024-02-17 14:42:31 +01:00
Jesús A. Álvarez afef4e5b25 always show keyboard again when pressing keyboard button 2024-02-17 14:41:22 +01:00
Jesús A. Álvarez 772ce2c326 handle mouse input with UIHoverGestureRecognizer 2024-02-17 14:35:19 +01:00
Jesús A. Álvarez ef6ef61037 target iOS 13.4 2024-02-17 13:31:39 +01:00
Jesús A. Álvarez 3b4244e4e3 ifdef around all visionOS-specific things 2024-02-17 13:25:32 +01:00
Jesús A. Álvarez c1c7c93c6a handle key input with UIPress (iOS ≥9)
contributed by Steven Troughton-Smith
2024-02-17 13:25:32 +01:00
Jesús A. Álvarez 61dc6ddcd7 target iOS 13.0 2024-02-17 13:20:50 +01:00
Jesús A. Álvarez 0204043e10 add glass background to bar, remove from buttons 2024-02-16 21:29:22 +01:00
Jesús A. Álvarez 043c20b645 visionOS: make keyboard resizable ±25% 2024-02-10 21:37:13 +01:00
Jesús A. Álvarez 7a93412c3f visionOS: add reset, interrupt and shut down controls 2024-02-10 14:52:06 +01:00
Jesús A. Álvarez e1adde78f0 visionOS: hide toolbar from insert disk modal 2024-02-10 14:43:09 +01:00
Jesús A. Álvarez 632f01f169 visionOS: native settings menu 2024-02-10 14:43:09 +01:00
Jesús A. Álvarez ed99d1bbfb visionOS: show layout menu from keyboard button 2024-02-10 14:23:42 +01:00
Jesús A. Álvarez 53cb92f4ef find keyboard layouts from AppDelegate 2024-02-10 14:09:02 +01:00
Jesús A. Álvarez 22256824d2 make AppDelegate a bit swifty 2024-02-10 13:36:20 +01:00
Jesús A. Álvarez 3b96faf502 fix bundle identifier for MacII-512x384 2024-02-10 13:31:28 +01:00
Jesús A. Álvarez 33e01e0847 visionOS: always use touchscreen 2024-02-10 12:19:20 +01:00
Jesús A. Álvarez 7c1f280374 keyboard for visionOS 2024-02-10 12:15:01 +01:00
Jesús A. Álvarez 4aef44bf76 add icon for visionOS 2024-02-10 12:02:13 +01:00
Jesús A. Álvarez cf86226c6b build for visionOS 2024-02-09 16:06:37 +01:00
Jesús A. Álvarez 8f4bc46d12 target iOS 12.0 2024-02-09 15:39:32 +01:00
72 changed files with 1198 additions and 320 deletions

View File

@ -30,7 +30,6 @@
28BA89801CE7315400A98104 /* KBKeyboardView.m in Sources */ = {isa = PBXBuildFile; fileRef = 28BA89791CE7315400A98104 /* KBKeyboardView.m */; };
28BA89821CE7336500A98104 /* Keyboard Layouts in Resources */ = {isa = PBXBuildFile; fileRef = 28BA89811CE7336500A98104 /* Keyboard Layouts */; };
28BA89851CE73E7200A98104 /* TrackPad.m in Sources */ = {isa = PBXBuildFile; fileRef = 28BA89841CE73E7200A98104 /* TrackPad.m */; };
28BA89881CE73FBC00A98104 /* MNVMApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = 28BA89871CE73FBC00A98104 /* MNVMApplication.m */; };
28BDBEA01D230EEB0072ED5B /* MYOSGLUE.m in Sources */ = {isa = PBXBuildFile; fileRef = 28CE8ECB1CD4CDC500FE25A8 /* MYOSGLUE.m */; };
28BDBEB11D230EEB0072ED5B /* Icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 283423EC1CFA329C0088B634 /* Icon.png */; };
28BDBEB21D230EEB0072ED5B /* Icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 283423ED1CFA329C0088B634 /* Icon@2x.png */; };
@ -58,6 +57,11 @@
28C67BE92AC49E46000C7540 /* Icon@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 283423EE1CFA329C0088B634 /* Icon@3x.png */; };
28C67BF62AC49FA1000C7540 /* MacII-512x384.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 28C67BEE2AC49E46000C7540 /* MacII-512x384.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
28CE8ED61CD4F56C00FE25A8 /* ScreenView.m in Sources */ = {isa = PBXBuildFile; fileRef = 28CE8ED51CD4F56C00FE25A8 /* ScreenView.m */; };
28D3C6152B7681420079E915 /* VisionSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D3C6142B7681420079E915 /* VisionSupport.swift */; };
28D3C6172B76B8970079E915 /* DefaultSceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D3C6162B76B8970079E915 /* DefaultSceneDelegate.swift */; };
28D3C61B2B7781700079E915 /* KeyboardSceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D3C61A2B7781700079E915 /* KeyboardSceneDelegate.swift */; };
28D3C61D2B7795060079E915 /* SettingsMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D3C61C2B7795060079E915 /* SettingsMenu.swift */; };
28D3C61F2B77B51D0079E915 /* PowerMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D3C61E2B77B51D0079E915 /* PowerMenu.swift */; };
28D5A3FD1CD6868F001A33F6 /* TouchScreen.m in Sources */ = {isa = PBXBuildFile; fileRef = 28D5A3FC1CD6868E001A33F6 /* TouchScreen.m */; };
28E3B7DF251D0F13007C273F /* MOUSEMDV.c in Sources */ = {isa = PBXBuildFile; fileRef = 28E3B7CC251D0F12007C273F /* MOUSEMDV.c */; };
28E3B7E0251D0F13007C273F /* MOUSEMDV.c in Sources */ = {isa = PBXBuildFile; fileRef = 28E3B7CC251D0F12007C273F /* MOUSEMDV.c */; };
@ -272,8 +276,6 @@
28BA89811CE7336500A98104 /* Keyboard Layouts */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "Keyboard Layouts"; sourceTree = "<group>"; };
28BA89831CE73E7200A98104 /* TrackPad.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TrackPad.h; sourceTree = "<group>"; };
28BA89841CE73E7200A98104 /* TrackPad.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TrackPad.m; sourceTree = "<group>"; };
28BA89861CE73FBC00A98104 /* MNVMApplication.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MNVMApplication.h; sourceTree = "<group>"; };
28BA89871CE73FBC00A98104 /* MNVMApplication.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MNVMApplication.m; sourceTree = "<group>"; };
28BDBEB71D230EEB0072ED5B /* MacII-640x480.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = "MacII-640x480.framework"; sourceTree = BUILT_PRODUCTS_DIR; };
28C67BEE2AC49E46000C7540 /* MacII-512x384.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = "MacII-512x384.framework"; sourceTree = BUILT_PRODUCTS_DIR; };
28C67BF12AC49F0F000C7540 /* CNFUDALL.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CNFUDALL.h; sourceTree = "<group>"; };
@ -282,6 +284,12 @@
28CE8ECB1CD4CDC500FE25A8 /* MYOSGLUE.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MYOSGLUE.m; sourceTree = "<group>"; };
28CE8ED41CD4F56C00FE25A8 /* ScreenView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ScreenView.h; sourceTree = "<group>"; };
28CE8ED51CD4F56C00FE25A8 /* ScreenView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ScreenView.m; sourceTree = "<group>"; };
28D3C6132B7681420079E915 /* Mini vMac-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Mini vMac-Bridging-Header.h"; sourceTree = "<group>"; };
28D3C6142B7681420079E915 /* VisionSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisionSupport.swift; sourceTree = "<group>"; };
28D3C6162B76B8970079E915 /* DefaultSceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultSceneDelegate.swift; sourceTree = "<group>"; };
28D3C61A2B7781700079E915 /* KeyboardSceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardSceneDelegate.swift; sourceTree = "<group>"; };
28D3C61C2B7795060079E915 /* SettingsMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsMenu.swift; sourceTree = "<group>"; };
28D3C61E2B77B51D0079E915 /* PowerMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PowerMenu.swift; sourceTree = "<group>"; };
28D5A3FB1CD6868E001A33F6 /* TouchScreen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TouchScreen.h; sourceTree = "<group>"; };
28D5A3FC1CD6868E001A33F6 /* TouchScreen.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TouchScreen.m; sourceTree = "<group>"; };
28E3B7CC251D0F12007C273F /* MOUSEMDV.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = MOUSEMDV.c; sourceTree = "<group>"; };
@ -601,8 +609,6 @@
children = (
28F676C31CD15E0B00FC6FA6 /* AppDelegate.h */,
28F676C41CD15E0B00FC6FA6 /* AppDelegate.m */,
28BA89861CE73FBC00A98104 /* MNVMApplication.h */,
28BA89871CE73FBC00A98104 /* MNVMApplication.m */,
28CE8ED41CD4F56C00FE25A8 /* ScreenView.h */,
28CE8ED51CD4F56C00FE25A8 /* ScreenView.m */,
28D5A3FB1CD6868E001A33F6 /* TouchScreen.h */,
@ -611,6 +617,9 @@
28BA89841CE73E7200A98104 /* TrackPad.m */,
28F676C61CD15E0B00FC6FA6 /* ViewController.h */,
28F676C71CD15E0B00FC6FA6 /* ViewController.m */,
28D3C6142B7681420079E915 /* VisionSupport.swift */,
28D3C61C2B7795060079E915 /* SettingsMenu.swift */,
28D3C61E2B77B51D0079E915 /* PowerMenu.swift */,
28848B601CDE97D600B86C45 /* InsertDiskViewController.h */,
28848B611CDE97D600B86C45 /* InsertDiskViewController.m */,
28848B631CDE97E900B86C45 /* SettingsViewController.h */,
@ -627,6 +636,8 @@
28F6B4CE1CF77099002D76D0 /* compat.m */,
283CA9821DF47AF300B33D5E /* BTCMouse.h */,
283422EF1CF8F33A0088B634 /* Emulator Bundles */,
28D3C6162B76B8970079E915 /* DefaultSceneDelegate.swift */,
28D3C61A2B7781700079E915 /* KeyboardSceneDelegate.swift */,
);
path = "Mini vMac";
sourceTree = "<group>";
@ -634,6 +645,7 @@
28F676C01CD15E0B00FC6FA6 /* Supporting Files */ = {
isa = PBXGroup;
children = (
28D3C6132B7681420079E915 /* Mini vMac-Bridging-Header.h */,
28F6B4551CF07C9A002D76D0 /* libhfs */,
28F6B4541CF07C8D002D76D0 /* libmfs */,
28F6B4531CF07C83002D76D0 /* libres */,
@ -950,6 +962,7 @@
28F676BC1CD15E0B00FC6FA6 = {
CreatedOnToolsVersion = 7.3;
DevelopmentTeam = UJXNDZ5TNU;
LastSwiftMigration = 1520;
};
28F6B48D1CF07DDD002D76D0 = {
CreatedOnToolsVersion = 7.3.1;
@ -1287,14 +1300,18 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
28D3C6152B7681420079E915 /* VisionSupport.swift in Sources */,
28BA897E1CE7315400A98104 /* KBKey.m in Sources */,
28BA89851CE73E7200A98104 /* TrackPad.m in Sources */,
28D3C6172B76B8970079E915 /* DefaultSceneDelegate.swift in Sources */,
28CE8ED61CD4F56C00FE25A8 /* ScreenView.m in Sources */,
28848B651CDE97E900B86C45 /* SettingsViewController.m in Sources */,
28BA89881CE73FBC00A98104 /* MNVMApplication.m in Sources */,
28F6B4CF1CF77099002D76D0 /* compat.m in Sources */,
28F6B4521CF07C48002D76D0 /* UIImage+DiskImageIcon.m in Sources */,
28D3C61D2B7795060079E915 /* SettingsMenu.swift in Sources */,
28BA897F1CE7315400A98104 /* KBKeyboardLayout.m in Sources */,
28D3C61B2B7781700079E915 /* KeyboardSceneDelegate.swift in Sources */,
28D3C61F2B77B51D0079E915 /* PowerMenu.swift in Sources */,
28848B621CDE97D600B86C45 /* InsertDiskViewController.m in Sources */,
28F676C81CD15E0B00FC6FA6 /* ViewController.m in Sources */,
28D5A3FD1CD6868F001A33F6 /* TouchScreen.m in Sources */,
@ -1405,6 +1422,7 @@
INFOPLIST_PREPROCESS = YES;
INFOPLIST_PREPROCESSOR_DEFINITIONS = PLIST_PREPROCESSOR;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_DYLIB_INSTALL_NAME = "$(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(EXECUTABLE_PATH)";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@ -1414,6 +1432,10 @@
PRODUCT_BUNDLE_IDENTIFIER = net.namedfork.minivmac.macplus4m;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
SUPPORTS_MACCATALYST = YES;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
TARGETED_DEVICE_FAMILY = "1,2,7";
USER_HEADER_SEARCH_PATHS = (
"\"$(SRCROOT)/Mini vMac/$(PRODUCT_NAME)\"",
"\"$(SRCROOT)/Mini vMac/\"",
@ -1440,6 +1462,7 @@
INFOPLIST_PREPROCESS = YES;
INFOPLIST_PREPROCESSOR_DEFINITIONS = PLIST_PREPROCESSOR;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_DYLIB_INSTALL_NAME = "$(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(EXECUTABLE_PATH)";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@ -1449,6 +1472,10 @@
PRODUCT_BUNDLE_IDENTIFIER = net.namedfork.minivmac.macplus4m;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
SUPPORTS_MACCATALYST = YES;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
TARGETED_DEVICE_FAMILY = "1,2,7";
USER_HEADER_SEARCH_PATHS = (
"\"$(SRCROOT)/Mini vMac/$(PRODUCT_NAME)\"",
"\"$(SRCROOT)/Mini vMac/\"",
@ -1475,6 +1502,7 @@
INFOPLIST_PREPROCESS = YES;
INFOPLIST_PREPROCESSOR_DEFINITIONS = PLIST_PREPROCESSOR;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_DYLIB_INSTALL_NAME = "$(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(EXECUTABLE_PATH)";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@ -1484,6 +1512,10 @@
PRODUCT_BUNDLE_IDENTIFIER = net.namedfork.minivmac.macii;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
SUPPORTS_MACCATALYST = YES;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
TARGETED_DEVICE_FAMILY = "1,2,7";
USER_HEADER_SEARCH_PATHS = (
"\"$(SRCROOT)/Mini vMac/$(PRODUCT_NAME)\"",
"\"$(SRCROOT)/Mini vMac/\"",
@ -1510,6 +1542,7 @@
INFOPLIST_PREPROCESS = YES;
INFOPLIST_PREPROCESSOR_DEFINITIONS = PLIST_PREPROCESSOR;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_DYLIB_INSTALL_NAME = "$(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(EXECUTABLE_PATH)";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@ -1519,6 +1552,10 @@
PRODUCT_BUNDLE_IDENTIFIER = net.namedfork.minivmac.macii;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
SUPPORTS_MACCATALYST = YES;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
TARGETED_DEVICE_FAMILY = "1,2,7";
USER_HEADER_SEARCH_PATHS = (
"\"$(SRCROOT)/Mini vMac/$(PRODUCT_NAME)\"",
"\"$(SRCROOT)/Mini vMac/\"",
@ -1545,6 +1582,7 @@
INFOPLIST_PREPROCESS = YES;
INFOPLIST_PREPROCESSOR_DEFINITIONS = PLIST_PREPROCESSOR;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_DYLIB_INSTALL_NAME = "$(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(EXECUTABLE_PATH)";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@ -1554,6 +1592,10 @@
PRODUCT_BUNDLE_IDENTIFIER = net.namedfork.minivmac.mac128k;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
SUPPORTS_MACCATALYST = YES;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
TARGETED_DEVICE_FAMILY = "1,2,7";
USER_HEADER_SEARCH_PATHS = (
"\"$(SRCROOT)/Mini vMac/$(PRODUCT_NAME)\"",
"\"$(SRCROOT)/Mini vMac/\"",
@ -1580,6 +1622,7 @@
INFOPLIST_PREPROCESS = YES;
INFOPLIST_PREPROCESSOR_DEFINITIONS = PLIST_PREPROCESSOR;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_DYLIB_INSTALL_NAME = "$(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(EXECUTABLE_PATH)";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@ -1589,6 +1632,10 @@
PRODUCT_BUNDLE_IDENTIFIER = net.namedfork.minivmac.mac128k;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
SUPPORTS_MACCATALYST = YES;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
TARGETED_DEVICE_FAMILY = "1,2,7";
USER_HEADER_SEARCH_PATHS = (
"\"$(SRCROOT)/Mini vMac/$(PRODUCT_NAME)\"",
"\"$(SRCROOT)/Mini vMac/\"",
@ -1615,6 +1662,7 @@
INFOPLIST_PREPROCESS = YES;
INFOPLIST_PREPROCESSOR_DEFINITIONS = PLIST_PREPROCESSOR;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_DYLIB_INSTALL_NAME = "$(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(EXECUTABLE_PATH)";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@ -1624,6 +1672,10 @@
PRODUCT_BUNDLE_IDENTIFIER = "net.namedfork.minivmac.macii-640x480";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
SUPPORTS_MACCATALYST = YES;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
TARGETED_DEVICE_FAMILY = "1,2,7";
USER_HEADER_SEARCH_PATHS = (
"\"$(SRCROOT)/Mini vMac/$(PRODUCT_NAME)\"",
"\"$(SRCROOT)/Mini vMac/\"",
@ -1650,6 +1702,7 @@
INFOPLIST_PREPROCESS = YES;
INFOPLIST_PREPROCESSOR_DEFINITIONS = PLIST_PREPROCESSOR;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_DYLIB_INSTALL_NAME = "$(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(EXECUTABLE_PATH)";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@ -1659,6 +1712,10 @@
PRODUCT_BUNDLE_IDENTIFIER = "net.namedfork.minivmac.macii-640x480";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
SUPPORTS_MACCATALYST = YES;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
TARGETED_DEVICE_FAMILY = "1,2,7";
USER_HEADER_SEARCH_PATHS = (
"\"$(SRCROOT)/Mini vMac/$(PRODUCT_NAME)\"",
"\"$(SRCROOT)/Mini vMac/\"",
@ -1685,15 +1742,20 @@
INFOPLIST_PREPROCESS = YES;
INFOPLIST_PREPROCESSOR_DEFINITIONS = PLIST_PREPROCESSOR;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_DYLIB_INSTALL_NAME = "$(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(EXECUTABLE_PATH)";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = "net.namedfork.minivmac.macii-640x480";
PRODUCT_BUNDLE_IDENTIFIER = "net.namedfork.minivmac.macii-512x384";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
SUPPORTS_MACCATALYST = YES;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
TARGETED_DEVICE_FAMILY = "1,2,7";
USER_HEADER_SEARCH_PATHS = (
"\"$(SRCROOT)/Mini vMac/$(PRODUCT_NAME)\"",
"\"$(SRCROOT)/Mini vMac/\"",
@ -1720,15 +1782,20 @@
INFOPLIST_PREPROCESS = YES;
INFOPLIST_PREPROCESSOR_DEFINITIONS = PLIST_PREPROCESSOR;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_DYLIB_INSTALL_NAME = "$(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(EXECUTABLE_PATH)";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = "net.namedfork.minivmac.macii-640x480";
PRODUCT_BUNDLE_IDENTIFIER = "net.namedfork.minivmac.macii-512x384";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
SUPPORTS_MACCATALYST = YES;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
TARGETED_DEVICE_FAMILY = "1,2,7";
USER_HEADER_SEARCH_PATHS = (
"\"$(SRCROOT)/Mini vMac/$(PRODUCT_NAME)\"",
"\"$(SRCROOT)/Mini vMac/\"",
@ -1792,6 +1859,7 @@
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
@ -1841,6 +1909,7 @@
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
@ -1850,13 +1919,14 @@
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 14;
DEVELOPMENT_TEAM = UJXNDZ5TNU;
HEADER_SEARCH_PATHS = "$(SRCROOT)";
INFOPLIST_FILE = "Mini vMac/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.4;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -1866,6 +1936,13 @@
PRODUCT_BUNDLE_IDENTIFIER = net.namedfork.minivmac;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE = "";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_OBJC_BRIDGING_HEADER = "Mini vMac/Mini vMac-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,7";
};
name = Debug;
};
@ -1873,13 +1950,14 @@
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 14;
DEVELOPMENT_TEAM = UJXNDZ5TNU;
HEADER_SEARCH_PATHS = "$(SRCROOT)";
INFOPLIST_FILE = "Mini vMac/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.4;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -1889,70 +1967,100 @@
PRODUCT_BUNDLE_IDENTIFIER = net.namedfork.minivmac;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE = "";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_OBJC_BRIDGING_HEADER = "Mini vMac/Mini vMac-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,7";
};
name = Release;
};
28F6B4951CF07DDD002D76D0 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES;
CLANG_ENABLE_MODULES = NO;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
OTHER_CFLAGS = "-DHAVE_CONFIG_H";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "xrsimulator xros watchsimulator watchos macosx iphonesimulator iphoneos driverkit appletvsimulator appletvos";
SUPPORTS_MACCATALYST = YES;
};
name = Debug;
};
28F6B4961CF07DDD002D76D0 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES;
CLANG_ENABLE_MODULES = NO;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
OTHER_CFLAGS = "-DHAVE_CONFIG_H";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "xrsimulator xros watchsimulator watchos macosx iphonesimulator iphoneos driverkit appletvsimulator appletvos";
SUPPORTS_MACCATALYST = YES;
};
name = Release;
};
28F6B4AF1CF07EC9002D76D0 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = NO;
HEADER_SEARCH_PATHS = "$(SRCROOT)";
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
OTHER_CFLAGS = "-DUSE_LIBRES";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "xrsimulator xros watchsimulator watchos macosx iphonesimulator iphoneos driverkit appletvsimulator appletvos";
SUPPORTS_MACCATALYST = YES;
};
name = Debug;
};
28F6B4B01CF07EC9002D76D0 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = NO;
HEADER_SEARCH_PATHS = "$(SRCROOT)";
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
OTHER_CFLAGS = "-DUSE_LIBRES";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "xrsimulator xros watchsimulator watchos macosx iphonesimulator iphoneos driverkit appletvsimulator appletvos";
SUPPORTS_MACCATALYST = YES;
};
name = Release;
};
28F6B4BD1CF07F32002D76D0 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "xrsimulator xros watchsimulator watchos macosx iphonesimulator iphoneos driverkit appletvsimulator appletvos";
SUPPORTS_MACCATALYST = YES;
};
name = Debug;
};
28F6B4BE1CF07F32002D76D0 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "xrsimulator xros watchsimulator watchos macosx iphonesimulator iphoneos driverkit appletvsimulator appletvos";
SUPPORTS_MACCATALYST = YES;
};
name = Release;
};

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:Mini vMac.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,37 @@
{
"DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "9C19786155D97B8ED78AB84AFAAA50249126341A",
"DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : {
},
"DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : {
"9C19786155D97B8ED78AB84AFAAA50249126341A" : 0,
"F2E8D120DACD6BF5E1EFFA012C92CE6FA0E44B4C" : 0,
"45D35ABA2A883CFEB6F6014CF9723CDA568B602B" : 0
},
"DVTSourceControlWorkspaceBlueprintIdentifierKey" : "A273011D-9F4F-4F93-8287-77CF2E175709",
"DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : {
"9C19786155D97B8ED78AB84AFAAA50249126341A" : "minivmac4ios\/",
"F2E8D120DACD6BF5E1EFFA012C92CE6FA0E44B4C" : "minivmac4ios\/libres\/",
"45D35ABA2A883CFEB6F6014CF9723CDA568B602B" : "minivmac4ios\/libmfs\/"
},
"DVTSourceControlWorkspaceBlueprintNameKey" : "Mini vMac",
"DVTSourceControlWorkspaceBlueprintVersion" : 204,
"DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "Mini vMac.xcodeproj",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [
{
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/zydeco\/libmfs.git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "45D35ABA2A883CFEB6F6014CF9723CDA568B602B"
},
{
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:zydeco\/minivmac4ios.git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "9C19786155D97B8ED78AB84AFAAA50249126341A"
},
{
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/zydeco\/libres.git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "F2E8D120DACD6BF5E1EFFA012C92CE6FA0E44B4C"
}
]
}

View File

@ -40,7 +40,7 @@
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Release"
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"

View File

@ -22,15 +22,17 @@ extern NSString *DocumentsChangedNotification;
@property (nonatomic, readonly) NSString *emulatorBundlesPath;
@property (readonly, nonatomic, getter = isSandboxed) BOOL sandboxed;
@property (readonly, nonatomic) id<Emulator> sharedEmulator;
@property (readonly, nonatomic) NSArray<NSString*> *keyboardLayoutPaths;
+ (instancetype)sharedInstance;
+ (id<Emulator>)sharedEmulator;
@property (class, readonly, strong) AppDelegate *sharedInstance NS_SWIFT_NAME(shared);
@property (class, readonly, strong) id<Emulator> sharedEmulator NS_SWIFT_NAME(emulator);
- (void)loadAndStartEmulator;
- (void)showAlertWithTitle:(NSString *)title message:(NSString *)message;
- (IBAction)showInsertDisk:(id)sender;
- (IBAction)showSettings:(id)sender;
- (IBAction)showGestureHelp:(id)sender;
- (UIScene*)sceneWithName:(NSString*)name;
@end

View File

@ -10,6 +10,7 @@
#import "AppDelegate.h"
#import "SettingsViewController.h"
#import "InsertDiskViewController.h"
#import "ViewController.h"
static AppDelegate *sharedAppDelegate = nil;
static NSObject<Emulator> *sharedEmulator = nil;
@ -101,7 +102,14 @@ NSString *DocumentsChangedNotification = @"documentsChanged";
NSBundle *emulatorBundle = [NSBundle bundleWithPath:emulatorBundlePath];
[emulatorBundle load];
sharedEmulator = [[emulatorBundle principalClass] new];
sharedEmulator.rootViewController = self.window.rootViewController;
sharedEmulator.showAlert = ^(NSString *title, NSString *message) {
[self showAlertWithTitle:title message:message];
};
sharedEmulator.dataPath = self.documentsPath;
#if defined(TARGET_OS_VISION) && TARGET_OS_VISION == 1
[ViewController adjustToScreenSize];
#endif
return sharedEmulator != nil;
}
@ -167,8 +175,11 @@ NSString *DocumentsChangedNotification = @"documentsChanged";
});
return;
}
BOOL wasRunning = sharedEmulator.isRunning;
UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]];
[alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[sharedEmulator setRunning:wasRunning];
}]];
UIViewController *controller = self.window.rootViewController;
while (controller.presentedViewController) {
controller = controller.presentedViewController;
@ -178,7 +189,7 @@ NSString *DocumentsChangedNotification = @"documentsChanged";
- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler {
BOOL success = NO;
if ([shortcutItem.type isEqualToString:@"disk"] && sharedEmulator.isRunning) {
if ([shortcutItem.type isEqualToString:@"disk"]) {
NSString *fileName = (NSString*)shortcutItem.userInfo[@"disk"];
NSString *filePath = [self.documentsPath stringByAppendingPathComponent:fileName];
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath] && ![sharedEmulator isDiskInserted:filePath]) {
@ -246,6 +257,16 @@ NSString *DocumentsChangedNotification = @"documentsChanged";
return userKeyboardLayoutsPath;
}
- (NSArray<NSString *> *)keyboardLayoutPaths {
NSArray *keyboardLayouts = [[NSBundle mainBundle] pathsForResourcesOfType:@"nfkeyboardlayout" inDirectory:@"Keyboard Layouts"];
NSString *userKeyboardLayoutsPath = [AppDelegate sharedInstance].userKeyboardLayoutsPath;
NSArray *userKeyboardLayouts = [[[NSFileManager defaultManager] contentsOfDirectoryAtPath:userKeyboardLayoutsPath error:nil] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"pathExtension.lowercaseString = %@", @"nfkeyboardlayout"]];
if (userKeyboardLayouts.count > 0) {
keyboardLayouts = [keyboardLayouts arrayByAddingObjectsFromArray:userKeyboardLayouts];
}
return keyboardLayouts;
}
- (BOOL)importFileToDocuments:(NSURL *)url copy:(BOOL)copy {
if (url.fileURL) {
// opening file
@ -304,4 +325,41 @@ NSString *DocumentsChangedNotification = @"documentsChanged";
return YES;
}
#pragma mark - Making a Scene
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options {
for (NSUserActivity *activity in options.userActivities) {
if ([activity.activityType isEqualToString:@"net.namedfork.keyboard"]) {
return [UISceneConfiguration configurationWithName:@"Keyboard" sessionRole:UIWindowSceneSessionRoleApplication];
}
}
if ([self sceneWithName:@"Default"] == nil) {
[[AppDelegate sharedEmulator] setRunning:YES];
return [UISceneConfiguration configurationWithName:@"Default" sessionRole:UIWindowSceneSessionRoleApplication];
}
return nil;
}
- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet<UISceneSession *> *)sceneSessions {
if ([self sceneWithName:@"Default"] == nil) {
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"runInBackground"] == NO) {
[[AppDelegate sharedEmulator] setRunning:NO];
}
UIScene *keyboardScene = [self sceneWithName:@"Keyboard"];
if (keyboardScene != nil) {
// if only keyboard is left, close it too
[application requestSceneSessionDestruction:keyboardScene.session options:nil errorHandler:nil];
}
}
}
- (UIScene*)sceneWithName:(NSString*)name {
for (UIScene *scene in [UIApplication sharedApplication].connectedScenes) {
if ([scene.session.configuration.name isEqualToString:name]) {
return scene;
}
}
return nil;
}
@end

View File

@ -2,113 +2,110 @@
"images" : [
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "2x"
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "3x"
"scale" : "3x",
"size" : "20x20"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-Small@2x.png",
"scale" : "2x"
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-Small@3x.png",
"scale" : "3x"
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-Small-40@2x-1.png",
"scale" : "2x"
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-Small-40@3x.png",
"scale" : "3x"
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"filename" : "Icon-60@3x.png",
"scale" : "3x"
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "1x"
"scale" : "1x",
"size" : "20x20"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "2x"
"scale" : "2x",
"size" : "20x20"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-Small.png",
"scale" : "1x"
"idiom" : "ipad",
"scale" : "1x",
"size" : "29x29"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-Small@2x-1.png",
"scale" : "2x"
"idiom" : "ipad",
"scale" : "2x",
"size" : "29x29"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-Small-40.png",
"scale" : "1x"
"idiom" : "ipad",
"scale" : "1x",
"size" : "40x40"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-Small-40@2x.png",
"scale" : "2x"
"idiom" : "ipad",
"scale" : "2x",
"size" : "40x40"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-76.png",
"scale" : "1x"
"idiom" : "ipad",
"scale" : "1x",
"size" : "76x76"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-76@2x.png",
"scale" : "2x"
"idiom" : "ipad",
"scale" : "2x",
"size" : "76x76"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-iPadPro.png",
"scale" : "2x"
"idiom" : "ipad",
"scale" : "2x",
"size" : "83.5x83.5"
},
{
"idiom" : "ios-marketing",
"size" : "1024x1024",
"scale" : "1x"
"scale" : "1x",
"size" : "1024x1024"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"pre-rendered" : true
"author" : "xcode",
"version" : 1
}
}
}

View File

@ -0,0 +1,13 @@
{
"images" : [
{
"filename" : "back.png",
"idiom" : "vision",
"scale" : "2x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -0,0 +1,12 @@
{
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"frame-size" : {
"height" : 512,
"width" : 512
}
}
}

View File

@ -0,0 +1,17 @@
{
"info" : {
"author" : "xcode",
"version" : 1
},
"layers" : [
{
"filename" : "Front.solidimagestacklayer"
},
{
"filename" : "Middle.solidimagestacklayer"
},
{
"filename" : "Back.solidimagestacklayer"
}
]
}

View File

@ -0,0 +1,13 @@
{
"images" : [
{
"filename" : "Icon.png",
"idiom" : "vision",
"scale" : "2x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 736 B

View File

@ -0,0 +1,12 @@
{
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"frame-size" : {
"height" : 512,
"width" : 512
}
}
}

View File

@ -0,0 +1,13 @@
{
"images" : [
{
"filename" : "screen.png",
"idiom" : "vision",
"scale" : "2x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 934 B

View File

@ -0,0 +1,12 @@
{
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"frame-size" : {
"height" : 512,
"width" : 512
}
}
}

View File

@ -1,6 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
}
}
}

View File

@ -23,6 +23,11 @@
"filename" : "KBCapsLock~ipad@2x.png",
"idiom" : "ipad",
"scale" : "2x"
},
{
"filename" : "KBCapsLock~ipad@2x 1.png",
"idiom" : "vision",
"scale" : "2x"
}
],
"info" : {

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 B

View File

@ -23,6 +23,11 @@
"filename" : "KBClearDown~ipad@2x.png",
"idiom" : "ipad",
"scale" : "2x"
},
{
"filename" : "KBClearDown~ipad@2x 1.png",
"idiom" : "vision",
"scale" : "2x"
}
],
"info" : {

Binary file not shown.

After

Width:  |  Height:  |  Size: 401 B

View File

@ -23,6 +23,11 @@
"filename" : "KBClearUp~ipad@2x.png",
"idiom" : "ipad",
"scale" : "2x"
},
{
"filename" : "KBClearUp~ipad@2x 1.png",
"idiom" : "vision",
"scale" : "2x"
}
],
"info" : {

Binary file not shown.

After

Width:  |  Height:  |  Size: 563 B

View File

@ -23,6 +23,11 @@
"filename" : "KBCommand@2x~ipad.png",
"idiom" : "ipad",
"scale" : "2x"
},
{
"filename" : "KBCommand@2x~ipad 1.png",
"idiom" : "vision",
"scale" : "2x"
}
],
"info" : {

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -23,6 +23,11 @@
"filename" : "KBDeleteDown~ipad@2x.png",
"idiom" : "ipad",
"scale" : "2x"
},
{
"filename" : "KBDeleteDown~ipad@2x 1.png",
"idiom" : "vision",
"scale" : "2x"
}
],
"info" : {

Binary file not shown.

After

Width:  |  Height:  |  Size: 526 B

View File

@ -23,6 +23,11 @@
"filename" : "KBDeleteUp~ipad@2x.png",
"idiom" : "ipad",
"scale" : "2x"
},
{
"filename" : "KBDeleteUp~ipad@2x 1.png",
"idiom" : "vision",
"scale" : "2x"
}
],
"info" : {

Binary file not shown.

After

Width:  |  Height:  |  Size: 726 B

View File

@ -23,6 +23,11 @@
"filename" : "KBForwardDeleteDown~ipad@2x.png",
"idiom" : "ipad",
"scale" : "2x"
},
{
"filename" : "KBForwardDeleteDown~ipad@2x 1.png",
"idiom" : "vision",
"scale" : "2x"
}
],
"info" : {

Binary file not shown.

After

Width:  |  Height:  |  Size: 526 B

View File

@ -23,6 +23,11 @@
"filename" : "KBForwardDeleteUp~ipad@2x.png",
"idiom" : "ipad",
"scale" : "2x"
},
{
"filename" : "KBForwardDeleteUp~ipad@2x 1.png",
"idiom" : "vision",
"scale" : "2x"
}
],
"info" : {

Binary file not shown.

After

Width:  |  Height:  |  Size: 718 B

View File

@ -1,6 +1,7 @@
{
"images" : [
{
"filename" : "KBHide~ipad 2.png",
"idiom" : "iphone",
"scale" : "1x"
},
@ -23,6 +24,11 @@
"filename" : "KBHide~ipad@2x.png",
"idiom" : "ipad",
"scale" : "2x"
},
{
"filename" : "KBHide~ipad@2x 1.png",
"idiom" : "vision",
"scale" : "2x"
}
],
"info" : {

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 B

View File

@ -219,6 +219,25 @@
"mode" : "9-part"
},
"scale" : "2x"
},
{
"filename" : "KBKey@2x~ipad-2 1.png",
"idiom" : "vision",
"resizing" : {
"cap-insets" : {
"bottom" : 32,
"left" : 32,
"right" : 32,
"top" : 32
},
"center" : {
"height" : 2,
"mode" : "tile",
"width" : 2
},
"mode" : "9-part"
},
"scale" : "2x"
}
],
"info" : {

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 B

View File

@ -219,6 +219,25 @@
"mode" : "9-part"
},
"scale" : "2x"
},
{
"filename" : "KBKeyDark@2x~ipad-1 1.png",
"idiom" : "vision",
"resizing" : {
"cap-insets" : {
"bottom" : 32,
"left" : 32,
"right" : 32,
"top" : 32
},
"center" : {
"height" : 2,
"mode" : "tile",
"width" : 2
},
"mode" : "9-part"
},
"scale" : "2x"
}
],
"info" : {

Binary file not shown.

After

Width:  |  Height:  |  Size: 636 B

View File

@ -23,6 +23,11 @@
"filename" : "KBNumPad@2x~ipad.png",
"idiom" : "ipad",
"scale" : "2x"
},
{
"filename" : "KBNumPad@2x~ipad 1.png",
"idiom" : "vision",
"scale" : "2x"
}
],
"info" : {

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 B

View File

@ -23,6 +23,11 @@
"filename" : "KBOption@2x~ipad.png",
"idiom" : "ipad",
"scale" : "2x"
},
{
"filename" : "KBOption@2x~ipad 1.png",
"idiom" : "vision",
"scale" : "2x"
}
],
"info" : {

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 B

View File

@ -23,6 +23,11 @@
"filename" : "KBShiftDown~ipad@2x.png",
"idiom" : "ipad",
"scale" : "2x"
},
{
"filename" : "KBShiftDown~ipad@2x 1.png",
"idiom" : "vision",
"scale" : "2x"
}
],
"info" : {

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 B

View File

@ -23,6 +23,11 @@
"filename" : "KBShiftUp~ipad@2x.png",
"idiom" : "ipad",
"scale" : "2x"
},
{
"filename" : "KBShiftUp~ipad@2x 1.png",
"idiom" : "vision",
"scale" : "2x"
}
],
"info" : {

Binary file not shown.

After

Width:  |  Height:  |  Size: 467 B

View File

@ -1,23 +1,26 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "reset.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "reset@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "reset@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}
}

View File

@ -1,23 +1,26 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "Settings.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "Settings@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "Settings@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}
}

View File

@ -0,0 +1,64 @@
//
// DefaultSceneDelegate.swift
// Mini vMac
//
// Created by Jesús A. Álvarez on 2024-02-09.
// Copyright © 2024 namedfork. All rights reserved.
//
import UIKit
class DefaultSceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow? // keep window reference to be able to set background colour before destroying
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = scene as? UIWindowScene else {
fatalError("Expected scene of type UIWindowScene but got an unexpected type")
}
guard let appDelegate = AppDelegate.shared else {
fatalError("No app delegate")
}
let size = CGSize(width: 1024.0, height: 684.0)
windowScene.sizeRestrictions?.minimumSize = size
windowScene.sizeRestrictions?.maximumSize = size
window = UIWindow(windowScene: windowScene)
if let window {
appDelegate.window = window
window.rootViewController = UIStoryboard(name: "Main", bundle: .main).instantiateInitialViewController()
window.makeKeyAndVisible()
}
self.destroyOtherSessions(not: session)
}
private func destroyOtherSessions(not session: UISceneSession) {
let app = UIApplication.shared
let options = UIWindowSceneDestructionRequestOptions()
options.windowDismissalAnimation = .decline
for otherSession in app.openSessions.filter({ $0 != session && $0.configuration.name == "Default"}) {
if let window = (otherSession.scene as? UIWindowScene)?.windows.first {
window.rootViewController?.view.removeFromSuperview()
window.backgroundColor = .darkGray
app.requestSceneSessionRefresh(otherSession)
}
app.requestSceneSessionDestruction(otherSession, options: options)
// window will remain visible until window switcher is dismissed!
}
}
func sceneDidEnterBackground(_ scene: UIScene) {
let app = UIApplication.shared
if UserDefaults.standard.bool(forKey: "runInBackground") == false && app.connectedScenes.filter({ $0 != scene && $0.session.configuration.name == "Default"}).isEmpty {
AppDelegate.emulator.isRunning = false
}
}
func sceneDidBecomeActive(_ scene: UIScene) {
AppDelegate.emulator.isRunning = true
}
func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
AppDelegate.shared.application(UIApplication.shared, performActionFor: shortcutItem, completionHandler: completionHandler)
}
}

View File

@ -10,7 +10,7 @@
@import CoreGraphics;
@import QuartzCore;
typedef enum : NSInteger {
typedef NS_ENUM(NSInteger, EmulatorSpeed) {
EmulatorSpeedAllOut = -1,
EmulatorSpeed1x = 0,
EmulatorSpeed2x = 1,
@ -18,7 +18,7 @@ typedef enum : NSInteger {
EmulatorSpeed8x = 3,
EmulatorSpeed16x = 4,
EmulatorSpeed32x = 5
} EmulatorSpeed;
};
@protocol Emulator <NSObject>
@ -38,6 +38,9 @@ typedef enum : NSInteger {
@property (nonatomic, readonly) BOOL anyDiskInserted;
@property (nonatomic, readonly) NSString *currentApplication;
@property (nonatomic, strong) void (^showAlert)(NSString *title, NSString *message);
@property (nonatomic, strong) UIViewController *rootViewController;
- (void)run;
- (void)reset;
- (void)interrupt;

View File

@ -176,5 +176,28 @@
</dict>
</dict>
</array>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).DefaultSceneDelegate</string>
</dict>
<dict>
<key>UISceneConfigurationName</key>
<string>Keyboard</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).KeyboardSceneDelegate</string>
</dict>
</array>
</dict>
</dict>
</dict>
</plist>

View File

@ -473,7 +473,7 @@
}
- (void)showFileActivityIndicatorWithTitle:(NSString*)title completion:(void (^_Nonnull)(UIActivityIndicatorView* activityIndicatorView))completionBlock {
UIActivityIndicatorView *activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
UIActivityIndicatorView *activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleLarge];
if (@available(iOS 13, *)) {
activityIndicatorView.color = [UIColor labelColor];
} else {

View File

@ -9,6 +9,12 @@
#import <UIKit/UIKit.h>
#import "KBKeyboardLayout.h"
#define KC_COMMAND 55
#define KC_SHIFT 56
#define KC_CAPSLOCK 57
#define KC_OPTION 58
#define KC_CONTROL 59
@class KBKey;
@protocol KBKeyboardViewDelegate <NSObject>
@ -22,6 +28,7 @@
@property (weak, nonatomic, nullable) id<KBKeyboardViewDelegate> delegate;
@property (nonatomic, strong, nullable) KBKeyboardLayout *layout;
@property (nonatomic, strong, nullable) UIMenu *layoutMenu;
@property (nonatomic, readonly, nonnull) NSArray<KBKey*>* keys;
@property (nonatomic, readonly, nonnull) NSArray<KBKey*>* stickyKeys;

View File

@ -9,26 +9,23 @@
#import "KBKeyboardView.h"
#import "KBKey.h"
#define KC_COMMAND 55
#define KC_SHIFT 56
#define KC_CAPSLOCK 57
#define KC_OPTION 58
#define KC_CONTROL 59
@implementation KBKeyboardView {
NSMutableArray *keyPlanes;
NSMutableArray *keyPlanes, *emptyKeyPlanes;
NSMutableSet *modifiers;
NSMutableIndexSet *keysDown;
CGAffineTransform defaultKeyTransform;
CGFloat fontSize;
CGSize selectedSize;
UIEdgeInsets safeAreaInsets;
CGSize intrinsicSize;
}
- (instancetype)initWithFrame:(CGRect)frame safeAreaInsets:(UIEdgeInsets)insets {
self = [super initWithFrame:frame];
if (self) {
safeAreaInsets = insets;
intrinsicSize = frame.size;
#if !defined(TARGET_OS_VISION) || TARGET_OS_VISION == 0
if (@available(iOS 13.0, *)) {
self.backgroundColor = [UIColor clearColor];
UIVisualEffectView *backgroundView = [[UIVisualEffectView alloc] initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleSystemThickMaterial]];
@ -37,8 +34,10 @@
} else {
self.backgroundColor = [UIColor colorWithRed:0xEB / 255.0 green:0xF0 / 255.0 blue:0xF7 / 255.0 alpha:0.9];
}
#endif
modifiers = [NSMutableSet setWithCapacity:4];
keysDown = [NSMutableIndexSet indexSet];
self.autoresizesSubviews = NO;
}
return self;
}
@ -47,10 +46,29 @@
return [self initWithFrame:frame safeAreaInsets:UIEdgeInsetsZero];
}
- (CGSize)intrinsicContentSize {
return intrinsicSize;
}
- (BOOL)isCompactKeyboardSize:(CGSize)size {
return size.width < 768.0;
}
#if defined(TARGET_OS_VISION) && TARGET_OS_VISION == 1
- (void)layoutSubviews {
// TODO: optimize this if needed
CGSize size = self.bounds.size;
CGFloat scale = size.width / intrinsicSize.width;
CGAffineTransform transform = CGAffineTransformMakeScale(scale, scale);
if (!CGAffineTransformEqualToTransform(transform, defaultKeyTransform)) {
defaultKeyTransform = transform;
keyPlanes = emptyKeyPlanes.mutableCopy;
fontSize = 30.0 * scale;
[self switchToKeyPlane:0];
}
}
#endif
- (CGSize)findBestSizeForWidth:(CGFloat)preferredWidth inArray:(NSArray<NSValue*>*)sizes {
CGSize selectedSize = CGSizeZero;
for (NSValue *key in sizes) {
@ -116,7 +134,8 @@
for (int i = 0; i < numberOfKeyPlanes; i++) {
[keyPlanes addObject:[NSNull null]];
}
emptyKeyPlanes = keyPlanes.mutableCopy;
[self switchToKeyPlane:0];
}
@ -127,7 +146,16 @@
}
if (scancode == VKC_HIDE) {
key = [[KBHideKey alloc] initWithFrame:keyFrame];
#if defined(TARGET_OS_VISION) && TARGET_OS_VISION == 1
if (self.layoutMenu != nil) {
key.showsMenuAsPrimaryAction = YES;
key.menu = self.layoutMenu;
} else {
return;
}
#else
[key addTarget:self action:@selector(hideKeyboard:) forControlEvents:UIControlEventTouchUpInside];
#endif
} else if (scancode == VKC_SHIFT_CAPS) {
key = [[KBShiftCapsKey alloc] initWithFrame:keyFrame];
key.scancode = KC_SHIFT;

View File

@ -0,0 +1,51 @@
//
// KeyboardSceneDelegate.swift
// Mini vMac
//
// Created by Jesús A. Álvarez on 2024-02-10.
// Copyright © 2024 namedfork. All rights reserved.
//
import UIKit
#if os(visionOS)
class KeyboardSceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = scene as? UIWindowScene else {
fatalError("Expected scene of type UIWindowScene but got an unexpected type")
}
guard let mainViewController = AppDelegate.shared.window.rootViewController as? ViewController else {
fatalError("No main view controller")
}
scene.activationConditions.canActivateForTargetContentIdentifierPredicate = NSPredicate(format: "self == 'net.namedfork.keyboard'", argumentArray: nil)
let defaultSize = mainViewController.keyboardViewController.preferredContentSize
let minSize = defaultSize.applying(.init(scaleX: 0.75, y: 0.75))
let maxSize = defaultSize.applying(.init(scaleX: 1.25, y: 1.25))
windowScene.sizeRestrictions?.minimumSize = minSize
windowScene.sizeRestrictions?.maximumSize = maxSize
windowScene.requestGeometryUpdate(UIWindowScene.GeometryPreferences.Vision(
size: defaultSize,
minimumSize: minSize,
maximumSize: maxSize,
resizingRestrictions: .uniform
))
window = UIWindow(windowScene: windowScene)
// destroy existing keyboard window
if let oldWindow = mainViewController.keyboardViewController?.view?.window {
oldWindow.rootViewController = nil
if let oldScene = oldWindow.windowScene?.session.scene {
UIApplication.shared.requestSceneSessionDestruction(oldScene.session, options: nil)
}
}
if let window {
window.rootViewController = mainViewController.keyboardViewController
window.makeKeyAndVisible()
}
}
}
#endif

View File

@ -1,13 +0,0 @@
//
// MNVMApplication.h
// Mini vMac
//
// Created by Jesús A. Álvarez on 14/05/2016.
// Copyright © 2016-2018 namedfork. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface MNVMApplication : UIApplication
@end

View File

@ -1,120 +0,0 @@
//
// MNVMApplication.m
// Mini vMac
//
// Created by Jesús A. Álvarez on 14/05/2016.
// Copyright © 2016-2018 namedfork. All rights reserved.
//
#import "MNVMApplication.h"
#import "AppDelegate.h"
@interface UIApplication ()
- (void)handleKeyUIEvent:(UIEvent *)event;
@end
Class keyboardEventClass = nil;
static int8_t usb_to_adb_scancode[] = {
-1, -1, -1, -1, 0, 11, 8, 2, 14, 3, 5, 4, 34, 38, 40, 37,
46, 45, 31, 35, 12, 15, 1, 17, 32, 9, 13, 7, 16, 6, 18, 19,
20, 21, 23, 22, 26, 28, 25, 29, 36, 53, 51, 48, 49, 27, 24, 33,
30, 42, 42, 41, 39, 10, 43, 47, 44, 57, 122, 120, 99, 118, 96, 97,
98, 100, 101, 109, 103, 111, 105, 107, 113, 114, 115, 116, 117, 119, 121, 60,
59, 61, 62, 71, 75, 67, 78, 69, 76, 83, 84, 85, 86, 87, 88, 89,
91, 92, 82, 65, 50, 55, 126, 81, 105, 107, 113, 106, 64, 79, 80, 90,
-1, -1, -1, -1, -1, 114, -1, -1, -1, -1, -1, -1, -1, -1, -1, 74,
72, 73, -1, -1, -1, 95, -1, 94, -1, 93, -1, -1, -1, -1, -1, -1,
104, 102, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
54, 56, 58, 55, 54, 56, 58, 55, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
};
@interface UIPhysicalKeyboardEvent : UIPressesEvent
@property (nonatomic, readonly) BOOL _isKeyDown;
@property (nonatomic, readonly) long _keyCode;
@property (nonatomic) int _modifierFlags;
@property(retain, nonatomic) NSString *_unmodifiedInput;
@property(retain, nonatomic) NSString *_modifiedInput;
@end
@implementation MNVMApplication
{
BOOL physicalCapsLocked;
}
+ (void)load {
// class is not visible
keyboardEventClass = NSClassFromString(@"UIPhysicalKeyboardEvent");
}
- (void)handleKeyboardEvent:(UIPhysicalKeyboardEvent *)event {
long keycode = event._keyCode;
int scancode = -1;
if (keycode >= 0 && keycode < sizeof(usb_to_adb_scancode)) {
scancode = usb_to_adb_scancode[keycode];
}
if (scancode == 57) {
// caps lock
if (event._isKeyDown && !physicalCapsLocked) {
[[AppDelegate sharedEmulator] keyDown:scancode];
physicalCapsLocked = YES;
} else if (event._isKeyDown && physicalCapsLocked) {
[[AppDelegate sharedEmulator] keyUp:scancode];
physicalCapsLocked = NO;
}
} else if (scancode >= 0 && [AppDelegate sharedEmulator].running) {
if (event._isKeyDown) {
[self _updateCapsLockStatus:event];
[[AppDelegate sharedEmulator] keyDown:scancode];
} else {
[[AppDelegate sharedEmulator] keyUp:scancode];
}
}
}
- (void)_updateCapsLockStatus:(UIPhysicalKeyboardEvent *)event {
if (event._modifierFlags == 0 && event._unmodifiedInput.length == 1 && event._modifiedInput.length == 1) {
unichar unmodifiedChar = [event._unmodifiedInput characterAtIndex:0];
unichar modifiedChar = [event._modifiedInput characterAtIndex:0];
if ([[NSCharacterSet lowercaseLetterCharacterSet] characterIsMember:unmodifiedChar]) {
BOOL currentCapsLock = [[NSCharacterSet uppercaseLetterCharacterSet] characterIsMember:modifiedChar];
if (currentCapsLock != physicalCapsLocked) {
physicalCapsLocked = currentCapsLock;
if (physicalCapsLocked) {
[[AppDelegate sharedEmulator] keyDown:57];
} else {
[[AppDelegate sharedEmulator] keyUp:57];
}
}
}
}
}
- (void)handleKeyUIEvent:(UIEvent *)event {
static dispatch_once_t onceToken;
static BOOL handleKeyboardEvents = YES;
dispatch_once(&onceToken, ^{
if ([NSProcessInfo instancesRespondToSelector:@selector(operatingSystemVersion)]) {
handleKeyboardEvents = [NSProcessInfo processInfo].operatingSystemVersion.majorVersion >= 9;
} else {
handleKeyboardEvents = NO;
}
});
BOOL emulatorIsFrontmost = [AppDelegate sharedEmulator].running && [AppDelegate sharedInstance].window.rootViewController.presentedViewController == nil;
if ([event isKindOfClass:keyboardEventClass] && handleKeyboardEvents && emulatorIsFrontmost) {
[self handleKeyboardEvent:(UIPhysicalKeyboardEvent*)event];
} else {
[super handleKeyUIEvent:event];
}
}
@end

View File

@ -1471,13 +1471,8 @@ LOCALPROC MacMsgDisplayOn() {
if (SavedBriefMsg != nullpr) {
NSString *title = NSStringCreateFromSubstCStr(SavedBriefMsg, falseblnr);
NSString *message = NSStringCreateFromSubstCStr(SavedLongMsg, falseblnr);
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
blnr wasStopped = SpeedStopped;
[alertController addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
SpeedStopped = wasStopped;
}]];
sharedEmulator.showAlert(title, message);
SpeedStopped = trueblnr;
[[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alertController animated:YES completion:nil];
SavedBriefMsg = nullpr;
SavedLongMsg = nullpr;
}
@ -1642,6 +1637,8 @@ GLOBALPROC WaitForNextTick(void) {
}
@synthesize dataPath;
@synthesize showAlert;
@synthesize rootViewController;
- (instancetype)init {
if ((self = [super init])) {
@ -1792,7 +1789,7 @@ GLOBALPROC WaitForNextTick(void) {
[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Save", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[self didMakeNewDisk:self->nameTextField.text size:size];
}]];
[[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alertController animated:YES completion:nil];
[sharedEmulator.rootViewController presentViewController:alertController animated:YES completion:nil];
}
- (void)didMakeNewDisk:(NSString*)fileName size:(NSInteger)size {

View File

@ -0,0 +1,9 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "ViewController.h"
#import "AppDelegate.h"
#import "EmulatorProtocol.h"
#import "KBKeyboardView.h"
#import "KBKeyboardLayout.h"

35
Mini vMac/PowerMenu.swift Normal file
View File

@ -0,0 +1,35 @@
//
// PowerMenu.swift
// Mini vMac
//
// Created by Jesús A. Álvarez on 2024-02-10.
// Copyright © 2024 namedfork. All rights reserved.
//
import SwiftUI
#if os(visionOS)
struct PowerMenu: View {
var body: some View {
Menu() {
Section("Power") {
Button("Restart", systemImage: "arrowtriangle.right", role: .destructive) {
AppDelegate.shared.loadAndStartEmulator()
}
Button("Interrupt", systemImage: "arrowtriangle.down.circle", role: .destructive) {
AppDelegate.emulator.interrupt()
}
Button("Shut Down", systemImage: "minus.circle", role: .destructive) {
exit(0)
}
}
} label: {
Image(systemName: "power")
}.menuOrder(.fixed)
}
}
#Preview {
PowerMenu()
}
#endif

View File

@ -29,7 +29,7 @@ static ScreenView *sharedScreenView = nil;
[self updateVideoLayer];
[self.layer addSublayer:videoLayer];
[[AppDelegate sharedInstance] addObserver:self forKeyPath:@"sharedEmulator" options:NSKeyValueObservingOptionNew context:NULL];
[[NSUserDefaults standardUserDefaults] addObserver:self forKeyPath:@"screenFilter" options:NSKeyValueObservingOptionNew context:NULL];
[[NSUserDefaults standardUserDefaults] addObserver:self forKeyPath:@"screenFilter" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:NULL];
}
+ (instancetype)sharedScreenView {
@ -57,17 +57,25 @@ static ScreenView *sharedScreenView = nil;
- (void)layoutSubviews {
[super layoutSubviews];
CGRect viewBounds = self.bounds;
CGFloat screenScale = MAX(screenSize.width / viewBounds.size.width, screenSize.height / viewBounds.size.height);
if (screenScale > 0.9 && screenScale <= 1.0) {
CGFloat screenScale = MIN(viewBounds.size.width / screenSize.width, viewBounds.size.height / screenSize.height);
NSString *screenFilter = [[NSUserDefaults standardUserDefaults] stringForKey:@"screenFilter"];
if ([screenFilter isEqualToString:kCAFilterNearest] && screenScale > 1.0) {
screenScale = floor(screenScale);
} else if (screenScale > 1.0 && screenScale <= 1.1) {
screenScale = 1.0;
}
screenBounds = CGRectMake(0, 0, screenSize.width / screenScale, screenSize.height / screenScale);
screenBounds = CGRectMake(0, 0, screenSize.width * screenScale, screenSize.height * screenScale);
screenBounds.origin.x = (viewBounds.size.width - screenBounds.size.width)/2;
screenBounds = CGRectIntegral(screenBounds);
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad && (viewBounds.size.height - screenBounds.size.height) >= 30.0) {
// move under multitasking indicator on iPad
screenBounds.origin.y += 30;
}
videoLayer.frame = screenBounds;
screenBounds.origin.y = self.frame.origin.y;
screenBounds.origin.y += self.frame.origin.y;
BOOL scaleIsIntegral = (floor(screenScale) == screenScale);
NSString *screenFilter = scaleIsIntegral ? kCAFilterNearest : [[NSUserDefaults standardUserDefaults] stringForKey:@"screenFilter"];
if (scaleIsIntegral) screenFilter = kCAFilterNearest;
videoLayer.magnificationFilter = screenFilter;
videoLayer.minificationFilter = screenFilter;
}
@ -75,13 +83,19 @@ static ScreenView *sharedScreenView = nil;
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([object isEqual:[NSUserDefaults standardUserDefaults]]) {
if ([keyPath isEqualToString:@"screenFilter"]) {
NSString *oldValue = change[NSKeyValueChangeOldKey];
NSString *value = change[NSKeyValueChangeNewKey];
videoLayer.magnificationFilter = value;
videoLayer.minificationFilter = value;
if ([value isEqualToString:kCAFilterNearest] || [oldValue isEqualToString:kCAFilterNearest]) {
[self setNeedsLayout];
[self layoutIfNeeded];
}
}
} else if (object == [AppDelegate sharedInstance] && [keyPath isEqualToString:@"sharedEmulator"]) {
[self updateVideoLayer];
[self layoutSubviews];
[self setNeedsLayout];
[self layoutIfNeeded];
}
}

View File

@ -0,0 +1,141 @@
//
// SettingsMenu.swift
// Mini vMac
//
// Created by Jesús A. Álvarez on 2024-02-10.
// Copyright © 2024 namedfork. All rights reserved.
//
import SwiftUI
#if os(visionOS)
struct SettingsMenu: View {
var body: some View {
Menu() {
Section("Settings") {
SpeedMenu()
MachineMenu()
DisplayScalingMenu()
}
} label: {
Image(systemName: "gear")
}.menuOrder(.fixed)
}
}
struct SpeedMenu: View {
@AppStorage("speedValue") var currentSpeed: EmulatorSpeed = .speed1x
@AppStorage("runInBackground") var runInBackground: Bool = false
private var currentSpeedImage: String {
switch currentSpeed {
case .speed1x:
"tortoise"
case .speedAllOut:
"hare"
case .speed2x:
"2.square"
case .speed4x:
"4.square"
case .speed8x:
"8.square"
case .speed16x:
"16.square"
case .speed32x:
"32.square"
@unknown default:
"hare"
}
}
var body: some View {
Menu("Speed", systemImage:currentSpeedImage) {
SpeedButton(label: "1x", speed: .speed1x)
SpeedButton(label: "2x", speed: .speed2x)
SpeedButton(label: "4x", speed: .speed4x)
SpeedButton(label: "8x", speed: .speed8x)
SpeedButton(label: "16x", speed: .speed16x)
SpeedButton(label: "32x", speed: .speed32x)
SpeedButton(label: "Unlimited", speed: .speedAllOut)
Divider()
Toggle("Run in Background", isOn: $runInBackground)
}.menuOrder(.fixed)
}
}
struct SpeedButton: View {
@AppStorage("speedValue") var currentSpeed: EmulatorSpeed = .speed1x
let label: LocalizedStringKey
let speed: EmulatorSpeed
var body: some View {
Toggle(label, isOn: Binding(get: {
currentSpeed == speed
}, set: { enable in
if enable {
currentSpeed = speed
}
}))
}
}
struct MachineMenu: View {
@AppStorage("machine") var currentMachine: String = "Plus4M"
let bundles = AppDelegate.shared.emulatorBundles?.sorted(by: { $0.bundleIdentifier! < $1.bundleIdentifier! }) ?? []
var body: some View {
Menu("Machine", systemImage: "desktopcomputer") {
ForEach(bundles, id: \.bundleIdentifier) { bundle in
MachineButton(bundle: bundle)
}
}.menuOrder(.fixed)
}
}
struct MachineButton: View {
@AppStorage("machine") var currentMachine: String = "Plus4M"
var bundle: Bundle
var body: some View {
Toggle(isOn: Binding(get: {
currentMachine == bundle.name
}, set: { enable in
if enable && !AppDelegate.emulator.anyDiskInserted {
currentMachine = bundle.name
AppDelegate.shared.loadAndStartEmulator()
}
}), label: {
Text("\(bundle.displayName ?? bundle.name)\n\(bundle.getInfoString ?? "")")
})
}
}
fileprivate extension Bundle {
var name: String { ((self.bundlePath as NSString).lastPathComponent as NSString).deletingPathExtension }
var displayName: String? { self.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String }
var getInfoString: String? { self.object(forInfoDictionaryKey: "CFBundleGetInfoString") as? String }
}
struct DisplayScalingMenu: View {
@AppStorage("screenFilter") var scalingFilter: CALayerContentsFilter = .nearest
static let filters = [
DisplayScalingFilter(filter: .nearest, name: "Nearest"),
DisplayScalingFilter(filter: .linear, name: "Linear"),
DisplayScalingFilter(filter: .trilinear, name: "Trilinear"),
]
var body: some View {
Menu("Display Scaling", systemImage: "rectangle.and.text.magnifyingglass") {
ForEach(DisplayScalingMenu.filters, id: \.filter) { filter in
Toggle(filter.name, isOn: Binding(get: {
scalingFilter == filter.filter
}, set: { enable in
if enable {
scalingFilter = filter.filter
}
}))
}
}.menuOrder(.fixed)
}
}
struct DisplayScalingFilter {
let filter: CALayerContentsFilter
let name: String
}
#endif

View File

@ -37,12 +37,7 @@ typedef enum : NSInteger {
- (void)viewDidLoad {
[super viewDidLoad];
keyboardLayouts = [[NSBundle mainBundle] pathsForResourcesOfType:@"nfkeyboardlayout" inDirectory:@"Keyboard Layouts"];
NSString *userKeyboardLayoutsPath = [AppDelegate sharedInstance].userKeyboardLayoutsPath;
NSArray *userKeyboardLayouts = [[[NSFileManager defaultManager] contentsOfDirectoryAtPath:userKeyboardLayoutsPath error:nil] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"pathExtension.lowercaseString = %@", @"nfkeyboardlayout"]];
if (userKeyboardLayouts.count > 0) {
keyboardLayouts = [keyboardLayouts arrayByAddingObjectsFromArray:userKeyboardLayouts];
}
keyboardLayouts = [AppDelegate sharedInstance].keyboardLayoutPaths;
[self loadEmulatorBundles];
[self loadCredits];
}
@ -177,7 +172,11 @@ typedef enum : NSInteger {
case SettingsSectionMachine:
return machineList.count;
case SettingsSectionAbout:
#if !defined(TARGET_OS_VISION) || TARGET_OS_VISION == 0
return aboutItems.count + 1;
#else
return aboutItems.count;
#endif
default:
return 1;
}
@ -372,7 +371,7 @@ typedef enum : NSInteger {
// links in about
NSString *linkURL = aboutItems[indexPath.row][@"link"];
if (linkURL != nil) {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:linkURL]];
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:linkURL] options:@{} completionHandler:nil];
}
}
}

View File

@ -46,12 +46,7 @@
}
- (BOOL)isMouseEvent:(UIEvent *)event {
#if __IPHONE_13_4
if (@available(iOS 13.4, *)) {
return event.buttonMask != 0;
}
#endif
return NO;
return event.buttonMask != 0;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

View File

@ -18,6 +18,15 @@
- (IBAction)showGestureHelp:(id)sender;
- (IBAction)hideGestureHelp:(id)sender;
- (void)showKeyboard:(id)sender;
@end
#if defined(TARGET_OS_VISION) && TARGET_OS_VISION == 1
@interface ViewController (VisionSupport)
@property (nonatomic, readonly) UIViewController* keyboardViewController;
- (void)initXr;
+ (void)adjustToScreenSize;
- (UIMenu*)keyboardLayoutMenu;
@end
#endif

View File

@ -13,50 +13,91 @@
#import "KBKeyboardView.h"
#import "KBKeyboardLayout.h"
@interface ViewController () <UIAdaptivePresentationControllerDelegate>
@interface ViewController () <UIAdaptivePresentationControllerDelegate, UIPointerInteractionDelegate>
@end
#ifdef __IPHONE_13_4
API_AVAILABLE(ios(13.4))
@interface ViewController (PointerInteraction) <UIPointerInteractionDelegate>
@end
#endif
static int8_t usb_to_adb_scancode[] = {
-1, -1, -1, -1, 0, 11, 8, 2, 14, 3, 5, 4, 34, 38, 40, 37,
46, 45, 31, 35, 12, 15, 1, 17, 32, 9, 13, 7, 16, 6, 18, 19,
20, 21, 23, 22, 26, 28, 25, 29, 36, 53, 51, 48, 49, 27, 24, 33,
30, 42, 42, 41, 39, 10, 43, 47, 44, 57, 122, 120, 99, 118, 96, 97,
98, 100, 101, 109, 103, 111, 105, 107, 113, 114, 115, 116, 117, 119, 121, 60,
59, 61, 62, 71, 75, 67, 78, 69, 76, 83, 84, 85, 86, 87, 88, 89,
91, 92, 82, 65, 50, 55, 126, 81, 105, 107, 113, 106, 64, 79, 80, 90,
-1, -1, -1, -1, -1, 114, -1, -1, -1, -1, -1, -1, -1, -1, -1, 74,
72, 73, -1, -1, -1, 95, -1, 94, -1, 93, -1, -1, -1, -1, -1, -1,
104, 102, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
54, 56, 58, 55, 54, 56, 58, 55, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
};
@implementation ViewController
{
KBKeyboardView *keyboardView;
UISwipeGestureRecognizer *showKeyboardGesture, *hideKeyboardGesture, *insertDiskGesture, *showSettingsGesture;
UIControl *pointingDeviceView;
id interaction;
}
- (Point)mouseLocForCGPoint:(CGPoint)point {
Point mouseLoc;
CGRect screenBounds = self.screenView.screenBounds;
CGSize screenSize = self.screenView.screenSize;
mouseLoc.h = (point.x - screenBounds.origin.x) * (screenSize.width/screenBounds.size.width);
mouseLoc.v = (point.y - screenBounds.origin.y) * (screenSize.height/screenBounds.size.height);
return mouseLoc;
UIViewController *_keyboardViewController;
BOOL physicalCapsLocked;
UIPointerInteraction *pointerInteraction;
}
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(emulatorDidShutDown:) name:[AppDelegate sharedEmulator].shutdownNotification object:nil];
#if defined(TARGET_OS_VISION) && TARGET_OS_VISION == 1
[self initXr];
#else
[self scheduleHelpPresentationIfNeededAfterDelay:6.0];
[self installGestures];
#endif
}
#if defined(TARGET_OS_VISION) && TARGET_OS_VISION == 1
- (UIViewController *)keyboardViewController {
if (keyboardView == nil) {
[[NSUserDefaults standardUserDefaults] addObserver:self forKeyPath:@"keyboardLayout" options:0 context:NULL];
KBKeyboardLayout *layout = [self keyboardLayout];
CGSize keyboardSize = CGSizeZero;
for (NSValue *size in layout.availableSizes) {
if (size.CGSizeValue.width > keyboardSize.width) {
keyboardSize = size.CGSizeValue;
}
}
keyboardView = [[KBKeyboardView alloc] initWithFrame:CGRectMake(0, 0, keyboardSize.width, keyboardSize.height)];
keyboardView.layoutMenu = [self keyboardLayoutMenu];
keyboardView.layout = layout;
keyboardView.delegate = self;
}
if (_keyboardViewController == nil) {
_keyboardViewController = [UIViewController alloc];
_keyboardViewController.view = keyboardView;
_keyboardViewController.preferredContentSize = keyboardView.frame.size;
} else if (_keyboardViewController.view != keyboardView) {
_keyboardViewController.view = keyboardView;
}
return _keyboardViewController;
}
#endif
- (void)installGestures {
[self installKeyboardGestures];
insertDiskGesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(showInsertDisk:)];
insertDiskGesture.direction = UISwipeGestureRecognizerDirectionLeft;
insertDiskGesture.numberOfTouchesRequired = 2;
[self.view addGestureRecognizer:insertDiskGesture];
showSettingsGesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(showSettings:)];
showSettingsGesture.direction = UISwipeGestureRecognizerDirectionRight;
showSettingsGesture.numberOfTouchesRequired = 2;
[self.view addGestureRecognizer:showSettingsGesture];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(emulatorDidShutDown:) name:[AppDelegate sharedEmulator].shutdownNotification object:nil];
[self scheduleHelpPresentationIfNeededAfterDelay:6.0];
}
- (void)showSettings:(id)sender {
@ -75,6 +116,11 @@ API_AVAILABLE(ios(13.4))
[self dismissViewControllerAnimated:YES completion:nil];
}
}
#if defined(TARGET_OS_VISION) && TARGET_OS_VISION == 1
if ([segue.identifier isEqualToString:@"disk"]) {
[(UINavigationController*)segue.destinationViewController setToolbarHidden:YES];
}
#endif
}
- (void)presentationControllerDidDismiss:(UIPresentationController *)presentationController {
@ -90,6 +136,7 @@ API_AVAILABLE(ios(13.4))
}
}
#if !defined(TARGET_OS_VISION) || TARGET_OS_VISION == 0
- (BOOL)prefersStatusBarHidden {
UIScreen *screen = self.view.window.screen;
return CGRectEqualToRect(screen.bounds, self.view.window.bounds);
@ -98,6 +145,7 @@ API_AVAILABLE(ios(13.4))
- (UIStatusBarStyle)preferredStatusBarStyle {
return UIStatusBarStyleLightContent;
}
#endif
- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures {
return UIRectEdgeAll;
@ -113,29 +161,8 @@ API_AVAILABLE(ios(13.4))
[[NSUserDefaults standardUserDefaults] removeObserver:self forKeyPath:@"trackpad"];
}
- (void)setUpPointingDevice {
if (pointingDeviceView) {
[pointingDeviceView removeFromSuperview];
pointingDeviceView = nil;
}
#ifdef __IPHONE_13_4
if (@available(iOS 13.4, *)) {
if (interaction == nil) {
interaction = [[UIPointerInteraction alloc] initWithDelegate: self];
[self.view addInteraction:interaction];
}
}
#endif
BOOL useTrackPad = [[NSUserDefaults standardUserDefaults] boolForKey:@"trackpad"];
Class pointingDeviceClass = useTrackPad ? [TrackPad class] : [TouchScreen class];
pointingDeviceView = [[pointingDeviceClass alloc] initWithFrame:self.view.bounds];
pointingDeviceView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.view insertSubview:pointingDeviceView aboveSubview:self.screenView];
if ([UIApplication instancesRespondToSelector:@selector(btcMouseSetRawMode:)]) {
[[UIApplication sharedApplication] btcMouseSetRawMode:YES];
}
- (BOOL)prefersHomeIndicatorAutoHidden {
return YES;
}
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
@ -147,6 +174,11 @@ API_AVAILABLE(ios(13.4))
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
if (object == [NSUserDefaults standardUserDefaults]) {
if ([keyPath isEqualToString:@"keyboardLayout"] && keyboardView != nil) {
#if defined(TARGET_OS_VISION) && TARGET_OS_VISION == 1
// FIXME: do this nicelier
keyboardView = nil;
[self keyboardViewController];
#else
BOOL keyboardWasVisible = self.keyboardVisible;
[self setKeyboardVisible:NO animated:NO];
[keyboardView removeFromSuperview];
@ -154,6 +186,7 @@ API_AVAILABLE(ios(13.4))
if (keyboardWasVisible) {
[self setKeyboardVisible:YES animated:NO];
}
#endif
} else if ([keyPath isEqualToString:@"trackpad"]) {
[self setUpPointingDevice];
}
@ -213,7 +246,9 @@ API_AVAILABLE(ios(13.4))
#pragma mark - Gesture Help
- (void)showGestureHelp:(id)sender {
#if !defined(TARGET_OS_VISION) || TARGET_OS_VISION == 0
[self setGestureHelpHidden:NO];
#endif
}
- (void)hideGestureHelp:(id)sender {
@ -269,7 +304,11 @@ API_AVAILABLE(ios(13.4))
}
- (BOOL)isKeyboardVisible {
#if defined(TARGET_OS_VISION) && TARGET_OS_VISION == 1
return _keyboardViewController.view.window != nil;
#else
return keyboardView != nil && CGRectIntersectsRect(keyboardView.frame, self.view.bounds) && !keyboardView.hidden;
#endif
}
- (void)setKeyboardVisible:(BOOL)keyboardVisible {
@ -285,10 +324,24 @@ API_AVAILABLE(ios(13.4))
}
- (void)setKeyboardVisible:(BOOL)visible animated:(BOOL)animated {
#if defined(TARGET_OS_VISION) && TARGET_OS_VISION == 1
if (visible) {
UISceneSessionActivationRequest *request = [UISceneSessionActivationRequest requestWithRole:UIWindowSceneSessionRoleApplication];
request.userActivity = [[NSUserActivity alloc] initWithActivityType:@"net.namedfork.keyboard"];
request.userActivity.targetContentIdentifier = @"net.namedfork.keyboard";
[[UIApplication sharedApplication] activateSceneSessionForRequest:request errorHandler:^(NSError * _Nonnull error) {
NSLog(@"Activation error: %@", error);
}];
} else {
UIScene *keyboardScene = [[AppDelegate sharedInstance] sceneWithName:@"Keyboard"];
if (keyboardScene != nil) {
[[UIApplication sharedApplication] requestSceneSessionDestruction:keyboardScene.session options:nil errorHandler:nil];
}
}
#else
if (self.keyboardVisible == visible) {
return;
}
if (visible) {
[[NSUserDefaults standardUserDefaults] addObserver:self forKeyPath:@"keyboardLayout" options:0 context:NULL];
[self loadKeyboardView];
@ -322,7 +375,7 @@ API_AVAILABLE(ios(13.4))
keyboardView.hidden = YES;
}
}
#endif
}
- (void)loadKeyboardView {
@ -364,16 +417,106 @@ API_AVAILABLE(ios(13.4))
[[AppDelegate sharedEmulator] keyUp:scancode];
}
@end
#ifdef __IPHONE_13_4
API_AVAILABLE(ios(13.4))
@implementation ViewController (PointerInteraction)
- (UIPointerRegion *)pointerInteraction:(UIPointerInteraction *)interaction regionForRequest:(UIPointerRegionRequest *)request defaultRegion:(UIPointerRegion *)defaultRegion API_AVAILABLE(ios(13.4)){
if (request != nil) {
Point mouseLoc = [self mouseLocForCGPoint:request.location];
[[AppDelegate sharedEmulator] setMouseX:mouseLoc.h Y:mouseLoc.v];
- (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event {
for (UIPress *press in presses) {
[self handlePressEvent:press];
}
}
- (void)pressesChanged:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event {
for (UIPress *press in presses) {
[self handlePressEvent:press];
}
}
- (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event {
for (UIPress *press in presses) {
[self handlePressEvent:press];
}
}
- (void)handlePressEvent:(UIPress *)event {
long keycode = event.key.keyCode;
int scancode = -1;
BOOL emulatorIsFrontmost = [AppDelegate sharedEmulator].running && [AppDelegate sharedInstance].window.rootViewController.presentedViewController == nil;
BOOL isKeyDown = (event.phase == UIPressPhaseBegan);
if (keycode >= 0 && keycode < sizeof(usb_to_adb_scancode)) {
scancode = usb_to_adb_scancode[keycode];
}
if (scancode == KC_CAPSLOCK) {
// caps lock
if (isKeyDown && !physicalCapsLocked) {
[[AppDelegate sharedEmulator] keyDown:KC_CAPSLOCK];
physicalCapsLocked = YES;
} else if (isKeyDown && physicalCapsLocked) {
[[AppDelegate sharedEmulator] keyUp:KC_CAPSLOCK];
physicalCapsLocked = NO;
}
} else if (scancode >= 0 && emulatorIsFrontmost) {
[self _updateCapsLockStatus:event];
if (isKeyDown) {
[[AppDelegate sharedEmulator] keyDown:scancode];
} else {
[[AppDelegate sharedEmulator] keyUp:scancode];
}
}
}
- (void)_updateCapsLockStatus:(UIPress *)event {
BOOL currentCapsLock = (event.key.modifierFlags & UIKeyModifierAlphaShift) != 0;
if (currentCapsLock != physicalCapsLocked) {
physicalCapsLocked = currentCapsLock;
if (physicalCapsLocked) {
[[AppDelegate sharedEmulator] keyDown:KC_CAPSLOCK];
} else {
[[AppDelegate sharedEmulator] keyUp:KC_CAPSLOCK];
}
}
}
#pragma mark - Mouse
- (void)setUpPointingDevice {
if (pointingDeviceView) {
[pointingDeviceView removeFromSuperview];
pointingDeviceView = nil;
}
if (pointerInteraction == nil) {
pointerInteraction = [[UIPointerInteraction alloc] initWithDelegate: self];
[self.view addInteraction:pointerInteraction];
UIHoverGestureRecognizer *hoverGestureRecognizer = [[UIHoverGestureRecognizer alloc] initWithTarget:self action:@selector(hover:)];
[self.view addGestureRecognizer:hoverGestureRecognizer];
}
#if defined(TARGET_OS_VISION) && TARGET_OS_VISION == 1
Class pointingDeviceClass = [TouchScreen class];
#else
BOOL useTrackPad = [[NSUserDefaults standardUserDefaults] boolForKey:@"trackpad"];
Class pointingDeviceClass = useTrackPad ? [TrackPad class] : [TouchScreen class];
#endif
pointingDeviceView = [[pointingDeviceClass alloc] initWithFrame:self.view.bounds];
pointingDeviceView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.view insertSubview:pointingDeviceView aboveSubview:self.screenView];
if ([UIApplication instancesRespondToSelector:@selector(btcMouseSetRawMode:)]) {
[[UIApplication sharedApplication] btcMouseSetRawMode:YES];
}
}
- (Point)mouseLocForCGPoint:(CGPoint)point {
Point mouseLoc;
CGRect screenBounds = self.screenView.screenBounds;
CGSize screenSize = self.screenView.screenSize;
mouseLoc.h = (point.x - screenBounds.origin.x) * (screenSize.width/screenBounds.size.width);
mouseLoc.v = (point.y - screenBounds.origin.y) * (screenSize.height/screenBounds.size.height);
return mouseLoc;
}
- (UIPointerRegion *)pointerInteraction:(UIPointerInteraction *)interaction regionForRequest:(UIPointerRegionRequest *)request defaultRegion:(UIPointerRegion *)defaultRegion {
return defaultRegion;
}
@ -381,5 +524,9 @@ API_AVAILABLE(ios(13.4))
return [UIPointerStyle hiddenPointerStyle];
}
- (void)hover:(UIHoverGestureRecognizer *)hoverGestureRecognizer {
Point mouseLoc = [self mouseLocForCGPoint:[hoverGestureRecognizer locationInView:self.screenView]];
[[AppDelegate sharedEmulator] setMouseX:mouseLoc.h Y:mouseLoc.v];
}
@end
#endif

View File

@ -0,0 +1,80 @@
//
// VisionSupport.swift
// Mini vMac
//
// Created by Jesús A. Álvarez on 2024-02-09.
// Copyright © 2024 namedfork. All rights reserved.
//
import Foundation
import UIKit
import SwiftUI
extension ViewController {
#if os(visionOS)
@objc
func initXr() {
ViewController.adjustToScreenSize()
ornaments = [
UIHostingOrnament(sceneAnchor: .bottom, contentAlignment: .center) {
VStack {
Spacer(minLength: 80.0)
HStack {
PowerMenu()
SettingsMenu()
Button(action: {
AppDelegate.shared.showInsertDisk(self)
}, label: {
Image(systemName: "opticaldiscdrive")
})
Button(action: {
self.showKeyboard(self)
}, label: {
Image(systemName: "keyboard")
})
}
.padding(.all)
.glassBackgroundEffect()
}
}
]
}
@objc
static func adjustToScreenSize() {
let screenSize = AppDelegate.emulator.screenSize
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
return
}
let minSize = screenSize
let defaultSize = screenSize.applying(.init(scaleX: 2.0, y: 2.0))
let maxSize = screenSize.applying(.init(scaleX: 3.0, y: 3.0))
windowScene.sizeRestrictions?.minimumSize = minSize
windowScene.sizeRestrictions?.maximumSize = maxSize
windowScene.requestGeometryUpdate(UIWindowScene.GeometryPreferences.Vision(
size: defaultSize,
minimumSize: minSize,
maximumSize: maxSize,
resizingRestrictions: .uniform
))
}
@objc
func keyboardLayoutMenu() -> UIMenu {
let layouts = AppDelegate.shared.keyboardLayoutPaths ?? []
let items: [UIMenuElement] = layouts.map({ path in
UIDeferredMenuElement.uncached { completion in
let layoutId = (path as NSString).lastPathComponent
let displayName = (layoutId as NSString).deletingPathExtension
let selected = UserDefaults.standard.string(forKey: "keyboardLayout") == layoutId
completion([UIAction(title: displayName, state: selected ? .on : .off) { _ in
UserDefaults.standard.setValue(layoutId, forKey: "keyboardLayout")
}])
}
})
return UIMenu(title: "Layout", options: [], children: items)
}
#endif
}

View File

@ -11,6 +11,6 @@
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, @"MNVMApplication", NSStringFromClass([AppDelegate class]));
return UIApplicationMain(argc, argv, NSStringFromClass([UIApplication class]), NSStringFromClass([AppDelegate class]));
}
}

View File

@ -5,13 +5,14 @@
* Emulates Mac Plus, Mac II or Mac 128K
* Full simulated keyboard (including all Mac keys)
* Full sound output
* Uses external keyboard if available
* Uses external keyboard and mouse/trackpad if available
* Regulable emulation speed
* Easy(ish) to import/export disk images
## Requirements
* iOS 9 or later
* iOS/iPadOS 13.4 or later, or visionOS
* Previous versions support down to iOS 7
* ROM image from Mac Plus, Mac II and/or Mac 128K
* Disk images with Mac software
@ -59,9 +60,6 @@ dragging on supported devices.
If you use a mouse or trackpad on iPad OS 13.4 or newer, it will be used automatically.
If you have a jailbroken device and [BTC Mouse & Trackpad](http://www.ringwald.ch/cydia/mouse/),
Mini vMac will use your bluetooth mouse or trackpad.
### Settings
Swipe right with two fingers to show the settings dialog, where you can change