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

View File

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

View File

@ -10,6 +10,7 @@
#import "AppDelegate.h" #import "AppDelegate.h"
#import "SettingsViewController.h" #import "SettingsViewController.h"
#import "InsertDiskViewController.h" #import "InsertDiskViewController.h"
#import "ViewController.h"
static AppDelegate *sharedAppDelegate = nil; static AppDelegate *sharedAppDelegate = nil;
static NSObject<Emulator> *sharedEmulator = nil; static NSObject<Emulator> *sharedEmulator = nil;
@ -101,7 +102,14 @@ NSString *DocumentsChangedNotification = @"documentsChanged";
NSBundle *emulatorBundle = [NSBundle bundleWithPath:emulatorBundlePath]; NSBundle *emulatorBundle = [NSBundle bundleWithPath:emulatorBundlePath];
[emulatorBundle load]; [emulatorBundle load];
sharedEmulator = [[emulatorBundle principalClass] new]; sharedEmulator = [[emulatorBundle principalClass] new];
sharedEmulator.rootViewController = self.window.rootViewController;
sharedEmulator.showAlert = ^(NSString *title, NSString *message) {
[self showAlertWithTitle:title message:message];
};
sharedEmulator.dataPath = self.documentsPath; sharedEmulator.dataPath = self.documentsPath;
#if defined(TARGET_OS_VISION) && TARGET_OS_VISION == 1
[ViewController adjustToScreenSize];
#endif
return sharedEmulator != nil; return sharedEmulator != nil;
} }
@ -167,8 +175,11 @@ NSString *DocumentsChangedNotification = @"documentsChanged";
}); });
return; return;
} }
BOOL wasRunning = sharedEmulator.isRunning;
UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert]; 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; UIViewController *controller = self.window.rootViewController;
while (controller.presentedViewController) { while (controller.presentedViewController) {
controller = controller.presentedViewController; controller = controller.presentedViewController;
@ -178,7 +189,7 @@ NSString *DocumentsChangedNotification = @"documentsChanged";
- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler { - (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler {
BOOL success = NO; BOOL success = NO;
if ([shortcutItem.type isEqualToString:@"disk"] && sharedEmulator.isRunning) { if ([shortcutItem.type isEqualToString:@"disk"]) {
NSString *fileName = (NSString*)shortcutItem.userInfo[@"disk"]; NSString *fileName = (NSString*)shortcutItem.userInfo[@"disk"];
NSString *filePath = [self.documentsPath stringByAppendingPathComponent:fileName]; NSString *filePath = [self.documentsPath stringByAppendingPathComponent:fileName];
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath] && ![sharedEmulator isDiskInserted:filePath]) { if ([[NSFileManager defaultManager] fileExistsAtPath:filePath] && ![sharedEmulator isDiskInserted:filePath]) {
@ -246,6 +257,16 @@ NSString *DocumentsChangedNotification = @"documentsChanged";
return userKeyboardLayoutsPath; 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 { - (BOOL)importFileToDocuments:(NSURL *)url copy:(BOOL)copy {
if (url.fileURL) { if (url.fileURL) {
// opening file // opening file
@ -304,4 +325,41 @@ NSString *DocumentsChangedNotification = @"documentsChanged";
return YES; 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 @end

View File

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

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" : { "info" : {
"version" : 1, "author" : "xcode",
"author" : "xcode" "version" : 1
} }
} }

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 B

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 401 B

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 563 B

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 526 B

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 726 B

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 526 B

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 718 B

View File

@ -1,6 +1,7 @@
{ {
"images" : [ "images" : [
{ {
"filename" : "KBHide~ipad 2.png",
"idiom" : "iphone", "idiom" : "iphone",
"scale" : "1x" "scale" : "1x"
}, },
@ -23,6 +24,11 @@
"filename" : "KBHide~ipad@2x.png", "filename" : "KBHide~ipad@2x.png",
"idiom" : "ipad", "idiom" : "ipad",
"scale" : "2x" "scale" : "2x"
},
{
"filename" : "KBHide~ipad@2x 1.png",
"idiom" : "vision",
"scale" : "2x"
} }
], ],
"info" : { "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" "mode" : "9-part"
}, },
"scale" : "2x" "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" : { "info" : {

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 B

View File

@ -219,6 +219,25 @@
"mode" : "9-part" "mode" : "9-part"
}, },
"scale" : "2x" "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" : { "info" : {

Binary file not shown.

After

Width:  |  Height:  |  Size: 636 B

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 B

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 B

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 B

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 467 B

View File

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

View File

@ -1,23 +1,26 @@
{ {
"images" : [ "images" : [
{ {
"idiom" : "universal",
"filename" : "Settings.png", "filename" : "Settings.png",
"idiom" : "universal",
"scale" : "1x" "scale" : "1x"
}, },
{ {
"idiom" : "universal",
"filename" : "Settings@2x.png", "filename" : "Settings@2x.png",
"idiom" : "universal",
"scale" : "2x" "scale" : "2x"
}, },
{ {
"idiom" : "universal",
"filename" : "Settings@3x.png", "filename" : "Settings@3x.png",
"idiom" : "universal",
"scale" : "3x" "scale" : "3x"
} }
], ],
"info" : { "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 CoreGraphics;
@import QuartzCore; @import QuartzCore;
typedef enum : NSInteger { typedef NS_ENUM(NSInteger, EmulatorSpeed) {
EmulatorSpeedAllOut = -1, EmulatorSpeedAllOut = -1,
EmulatorSpeed1x = 0, EmulatorSpeed1x = 0,
EmulatorSpeed2x = 1, EmulatorSpeed2x = 1,
@ -18,7 +18,7 @@ typedef enum : NSInteger {
EmulatorSpeed8x = 3, EmulatorSpeed8x = 3,
EmulatorSpeed16x = 4, EmulatorSpeed16x = 4,
EmulatorSpeed32x = 5 EmulatorSpeed32x = 5
} EmulatorSpeed; };
@protocol Emulator <NSObject> @protocol Emulator <NSObject>
@ -38,6 +38,9 @@ typedef enum : NSInteger {
@property (nonatomic, readonly) BOOL anyDiskInserted; @property (nonatomic, readonly) BOOL anyDiskInserted;
@property (nonatomic, readonly) NSString *currentApplication; @property (nonatomic, readonly) NSString *currentApplication;
@property (nonatomic, strong) void (^showAlert)(NSString *title, NSString *message);
@property (nonatomic, strong) UIViewController *rootViewController;
- (void)run; - (void)run;
- (void)reset; - (void)reset;
- (void)interrupt; - (void)interrupt;

View File

@ -176,5 +176,28 @@
</dict> </dict>
</dict> </dict>
</array> </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> </dict>
</plist> </plist>

View File

@ -473,7 +473,7 @@
} }
- (void)showFileActivityIndicatorWithTitle:(NSString*)title completion:(void (^_Nonnull)(UIActivityIndicatorView* activityIndicatorView))completionBlock { - (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, *)) { if (@available(iOS 13, *)) {
activityIndicatorView.color = [UIColor labelColor]; activityIndicatorView.color = [UIColor labelColor];
} else { } else {

View File

@ -9,6 +9,12 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import "KBKeyboardLayout.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; @class KBKey;
@protocol KBKeyboardViewDelegate <NSObject> @protocol KBKeyboardViewDelegate <NSObject>
@ -22,6 +28,7 @@
@property (weak, nonatomic, nullable) id<KBKeyboardViewDelegate> delegate; @property (weak, nonatomic, nullable) id<KBKeyboardViewDelegate> delegate;
@property (nonatomic, strong, nullable) KBKeyboardLayout *layout; @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*>* keys;
@property (nonatomic, readonly, nonnull) NSArray<KBKey*>* stickyKeys; @property (nonatomic, readonly, nonnull) NSArray<KBKey*>* stickyKeys;

View File

@ -9,26 +9,23 @@
#import "KBKeyboardView.h" #import "KBKeyboardView.h"
#import "KBKey.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 { @implementation KBKeyboardView {
NSMutableArray *keyPlanes; NSMutableArray *keyPlanes, *emptyKeyPlanes;
NSMutableSet *modifiers; NSMutableSet *modifiers;
NSMutableIndexSet *keysDown; NSMutableIndexSet *keysDown;
CGAffineTransform defaultKeyTransform; CGAffineTransform defaultKeyTransform;
CGFloat fontSize; CGFloat fontSize;
CGSize selectedSize; CGSize selectedSize;
UIEdgeInsets safeAreaInsets; UIEdgeInsets safeAreaInsets;
CGSize intrinsicSize;
} }
- (instancetype)initWithFrame:(CGRect)frame safeAreaInsets:(UIEdgeInsets)insets { - (instancetype)initWithFrame:(CGRect)frame safeAreaInsets:(UIEdgeInsets)insets {
self = [super initWithFrame:frame]; self = [super initWithFrame:frame];
if (self) { if (self) {
safeAreaInsets = insets; safeAreaInsets = insets;
intrinsicSize = frame.size;
#if !defined(TARGET_OS_VISION) || TARGET_OS_VISION == 0
if (@available(iOS 13.0, *)) { if (@available(iOS 13.0, *)) {
self.backgroundColor = [UIColor clearColor]; self.backgroundColor = [UIColor clearColor];
UIVisualEffectView *backgroundView = [[UIVisualEffectView alloc] initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleSystemThickMaterial]]; UIVisualEffectView *backgroundView = [[UIVisualEffectView alloc] initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleSystemThickMaterial]];
@ -37,8 +34,10 @@
} else { } else {
self.backgroundColor = [UIColor colorWithRed:0xEB / 255.0 green:0xF0 / 255.0 blue:0xF7 / 255.0 alpha:0.9]; self.backgroundColor = [UIColor colorWithRed:0xEB / 255.0 green:0xF0 / 255.0 blue:0xF7 / 255.0 alpha:0.9];
} }
#endif
modifiers = [NSMutableSet setWithCapacity:4]; modifiers = [NSMutableSet setWithCapacity:4];
keysDown = [NSMutableIndexSet indexSet]; keysDown = [NSMutableIndexSet indexSet];
self.autoresizesSubviews = NO;
} }
return self; return self;
} }
@ -47,10 +46,29 @@
return [self initWithFrame:frame safeAreaInsets:UIEdgeInsetsZero]; return [self initWithFrame:frame safeAreaInsets:UIEdgeInsetsZero];
} }
- (CGSize)intrinsicContentSize {
return intrinsicSize;
}
- (BOOL)isCompactKeyboardSize:(CGSize)size { - (BOOL)isCompactKeyboardSize:(CGSize)size {
return size.width < 768.0; 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)findBestSizeForWidth:(CGFloat)preferredWidth inArray:(NSArray<NSValue*>*)sizes {
CGSize selectedSize = CGSizeZero; CGSize selectedSize = CGSizeZero;
for (NSValue *key in sizes) { for (NSValue *key in sizes) {
@ -116,7 +134,8 @@
for (int i = 0; i < numberOfKeyPlanes; i++) { for (int i = 0; i < numberOfKeyPlanes; i++) {
[keyPlanes addObject:[NSNull null]]; [keyPlanes addObject:[NSNull null]];
} }
emptyKeyPlanes = keyPlanes.mutableCopy;
[self switchToKeyPlane:0]; [self switchToKeyPlane:0];
} }
@ -127,7 +146,16 @@
} }
if (scancode == VKC_HIDE) { if (scancode == VKC_HIDE) {
key = [[KBHideKey alloc] initWithFrame:keyFrame]; 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]; [key addTarget:self action:@selector(hideKeyboard:) forControlEvents:UIControlEventTouchUpInside];
#endif
} else if (scancode == VKC_SHIFT_CAPS) { } else if (scancode == VKC_SHIFT_CAPS) {
key = [[KBShiftCapsKey alloc] initWithFrame:keyFrame]; key = [[KBShiftCapsKey alloc] initWithFrame:keyFrame];
key.scancode = KC_SHIFT; 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) { if (SavedBriefMsg != nullpr) {
NSString *title = NSStringCreateFromSubstCStr(SavedBriefMsg, falseblnr); NSString *title = NSStringCreateFromSubstCStr(SavedBriefMsg, falseblnr);
NSString *message = NSStringCreateFromSubstCStr(SavedLongMsg, falseblnr); NSString *message = NSStringCreateFromSubstCStr(SavedLongMsg, falseblnr);
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert]; sharedEmulator.showAlert(title, message);
blnr wasStopped = SpeedStopped;
[alertController addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
SpeedStopped = wasStopped;
}]];
SpeedStopped = trueblnr; SpeedStopped = trueblnr;
[[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alertController animated:YES completion:nil];
SavedBriefMsg = nullpr; SavedBriefMsg = nullpr;
SavedLongMsg = nullpr; SavedLongMsg = nullpr;
} }
@ -1642,6 +1637,8 @@ GLOBALPROC WaitForNextTick(void) {
} }
@synthesize dataPath; @synthesize dataPath;
@synthesize showAlert;
@synthesize rootViewController;
- (instancetype)init { - (instancetype)init {
if ((self = [super 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) { [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Save", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[self didMakeNewDisk:self->nameTextField.text size:size]; [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 { - (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 updateVideoLayer];
[self.layer addSublayer:videoLayer]; [self.layer addSublayer:videoLayer];
[[AppDelegate sharedInstance] addObserver:self forKeyPath:@"sharedEmulator" options:NSKeyValueObservingOptionNew context:NULL]; [[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 { + (instancetype)sharedScreenView {
@ -57,17 +57,25 @@ static ScreenView *sharedScreenView = nil;
- (void)layoutSubviews { - (void)layoutSubviews {
[super layoutSubviews]; [super layoutSubviews];
CGRect viewBounds = self.bounds; CGRect viewBounds = self.bounds;
CGFloat screenScale = MAX(screenSize.width / viewBounds.size.width, screenSize.height / viewBounds.size.height); CGFloat screenScale = MIN(viewBounds.size.width / screenSize.width, viewBounds.size.height / screenSize.height);
if (screenScale > 0.9 && screenScale <= 1.0) { 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; 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.origin.x = (viewBounds.size.width - screenBounds.size.width)/2;
screenBounds = CGRectIntegral(screenBounds); 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; videoLayer.frame = screenBounds;
screenBounds.origin.y = self.frame.origin.y; screenBounds.origin.y += self.frame.origin.y;
BOOL scaleIsIntegral = (floor(screenScale) == screenScale); BOOL scaleIsIntegral = (floor(screenScale) == screenScale);
NSString *screenFilter = scaleIsIntegral ? kCAFilterNearest : [[NSUserDefaults standardUserDefaults] stringForKey:@"screenFilter"]; if (scaleIsIntegral) screenFilter = kCAFilterNearest;
videoLayer.magnificationFilter = screenFilter; videoLayer.magnificationFilter = screenFilter;
videoLayer.minificationFilter = 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 { - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([object isEqual:[NSUserDefaults standardUserDefaults]]) { if ([object isEqual:[NSUserDefaults standardUserDefaults]]) {
if ([keyPath isEqualToString:@"screenFilter"]) { if ([keyPath isEqualToString:@"screenFilter"]) {
NSString *oldValue = change[NSKeyValueChangeOldKey];
NSString *value = change[NSKeyValueChangeNewKey]; NSString *value = change[NSKeyValueChangeNewKey];
videoLayer.magnificationFilter = value; videoLayer.magnificationFilter = value;
videoLayer.minificationFilter = value; videoLayer.minificationFilter = value;
if ([value isEqualToString:kCAFilterNearest] || [oldValue isEqualToString:kCAFilterNearest]) {
[self setNeedsLayout];
[self layoutIfNeeded];
}
} }
} else if (object == [AppDelegate sharedInstance] && [keyPath isEqualToString:@"sharedEmulator"]) { } else if (object == [AppDelegate sharedInstance] && [keyPath isEqualToString:@"sharedEmulator"]) {
[self updateVideoLayer]; [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 { - (void)viewDidLoad {
[super viewDidLoad]; [super viewDidLoad];
keyboardLayouts = [[NSBundle mainBundle] pathsForResourcesOfType:@"nfkeyboardlayout" inDirectory:@"Keyboard Layouts"]; keyboardLayouts = [AppDelegate sharedInstance].keyboardLayoutPaths;
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];
}
[self loadEmulatorBundles]; [self loadEmulatorBundles];
[self loadCredits]; [self loadCredits];
} }
@ -177,7 +172,11 @@ typedef enum : NSInteger {
case SettingsSectionMachine: case SettingsSectionMachine:
return machineList.count; return machineList.count;
case SettingsSectionAbout: case SettingsSectionAbout:
#if !defined(TARGET_OS_VISION) || TARGET_OS_VISION == 0
return aboutItems.count + 1; return aboutItems.count + 1;
#else
return aboutItems.count;
#endif
default: default:
return 1; return 1;
} }
@ -372,7 +371,7 @@ typedef enum : NSInteger {
// links in about // links in about
NSString *linkURL = aboutItems[indexPath.row][@"link"]; NSString *linkURL = aboutItems[indexPath.row][@"link"];
if (linkURL != nil) { 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 { - (BOOL)isMouseEvent:(UIEvent *)event {
#if __IPHONE_13_4 return event.buttonMask != 0;
if (@available(iOS 13.4, *)) {
return event.buttonMask != 0;
}
#endif
return NO;
} }
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

View File

@ -18,6 +18,15 @@
- (IBAction)showGestureHelp:(id)sender; - (IBAction)showGestureHelp:(id)sender;
- (IBAction)hideGestureHelp:(id)sender; - (IBAction)hideGestureHelp:(id)sender;
- (void)showKeyboard:(id)sender;
@end @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 "KBKeyboardView.h"
#import "KBKeyboardLayout.h" #import "KBKeyboardLayout.h"
@interface ViewController () <UIAdaptivePresentationControllerDelegate> @interface ViewController () <UIAdaptivePresentationControllerDelegate, UIPointerInteractionDelegate>
@end @end
#ifdef __IPHONE_13_4 static int8_t usb_to_adb_scancode[] = {
API_AVAILABLE(ios(13.4)) -1, -1, -1, -1, 0, 11, 8, 2, 14, 3, 5, 4, 34, 38, 40, 37,
@interface ViewController (PointerInteraction) <UIPointerInteractionDelegate> 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,
@end 30, 42, 42, 41, 39, 10, 43, 47, 44, 57, 122, 120, 99, 118, 96, 97,
#endif 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 @implementation ViewController
{ {
KBKeyboardView *keyboardView; KBKeyboardView *keyboardView;
UISwipeGestureRecognizer *showKeyboardGesture, *hideKeyboardGesture, *insertDiskGesture, *showSettingsGesture; UISwipeGestureRecognizer *showKeyboardGesture, *hideKeyboardGesture, *insertDiskGesture, *showSettingsGesture;
UIControl *pointingDeviceView; UIControl *pointingDeviceView;
id interaction; UIViewController *_keyboardViewController;
} BOOL physicalCapsLocked;
UIPointerInteraction *pointerInteraction;
- (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;
} }
- (void)viewDidLoad { - (void)viewDidLoad {
[super 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]; [self installKeyboardGestures];
insertDiskGesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(showInsertDisk:)]; insertDiskGesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(showInsertDisk:)];
insertDiskGesture.direction = UISwipeGestureRecognizerDirectionLeft; insertDiskGesture.direction = UISwipeGestureRecognizerDirectionLeft;
insertDiskGesture.numberOfTouchesRequired = 2; insertDiskGesture.numberOfTouchesRequired = 2;
[self.view addGestureRecognizer:insertDiskGesture]; [self.view addGestureRecognizer:insertDiskGesture];
showSettingsGesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(showSettings:)]; showSettingsGesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(showSettings:)];
showSettingsGesture.direction = UISwipeGestureRecognizerDirectionRight; showSettingsGesture.direction = UISwipeGestureRecognizerDirectionRight;
showSettingsGesture.numberOfTouchesRequired = 2; showSettingsGesture.numberOfTouchesRequired = 2;
[self.view addGestureRecognizer:showSettingsGesture]; [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 { - (void)showSettings:(id)sender {
@ -75,6 +116,11 @@ API_AVAILABLE(ios(13.4))
[self dismissViewControllerAnimated:YES completion:nil]; [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 { - (void)presentationControllerDidDismiss:(UIPresentationController *)presentationController {
@ -90,6 +136,7 @@ API_AVAILABLE(ios(13.4))
} }
} }
#if !defined(TARGET_OS_VISION) || TARGET_OS_VISION == 0
- (BOOL)prefersStatusBarHidden { - (BOOL)prefersStatusBarHidden {
UIScreen *screen = self.view.window.screen; UIScreen *screen = self.view.window.screen;
return CGRectEqualToRect(screen.bounds, self.view.window.bounds); return CGRectEqualToRect(screen.bounds, self.view.window.bounds);
@ -98,6 +145,7 @@ API_AVAILABLE(ios(13.4))
- (UIStatusBarStyle)preferredStatusBarStyle { - (UIStatusBarStyle)preferredStatusBarStyle {
return UIStatusBarStyleLightContent; return UIStatusBarStyleLightContent;
} }
#endif
- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures { - (UIRectEdge)preferredScreenEdgesDeferringSystemGestures {
return UIRectEdgeAll; return UIRectEdgeAll;
@ -113,29 +161,8 @@ API_AVAILABLE(ios(13.4))
[[NSUserDefaults standardUserDefaults] removeObserver:self forKeyPath:@"trackpad"]; [[NSUserDefaults standardUserDefaults] removeObserver:self forKeyPath:@"trackpad"];
} }
- (void)setUpPointingDevice { - (BOOL)prefersHomeIndicatorAutoHidden {
if (pointingDeviceView) { return YES;
[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];
}
} }
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event { - (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 { - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
if (object == [NSUserDefaults standardUserDefaults]) { if (object == [NSUserDefaults standardUserDefaults]) {
if ([keyPath isEqualToString:@"keyboardLayout"] && keyboardView != nil) { 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; BOOL keyboardWasVisible = self.keyboardVisible;
[self setKeyboardVisible:NO animated:NO]; [self setKeyboardVisible:NO animated:NO];
[keyboardView removeFromSuperview]; [keyboardView removeFromSuperview];
@ -154,6 +186,7 @@ API_AVAILABLE(ios(13.4))
if (keyboardWasVisible) { if (keyboardWasVisible) {
[self setKeyboardVisible:YES animated:NO]; [self setKeyboardVisible:YES animated:NO];
} }
#endif
} else if ([keyPath isEqualToString:@"trackpad"]) { } else if ([keyPath isEqualToString:@"trackpad"]) {
[self setUpPointingDevice]; [self setUpPointingDevice];
} }
@ -213,7 +246,9 @@ API_AVAILABLE(ios(13.4))
#pragma mark - Gesture Help #pragma mark - Gesture Help
- (void)showGestureHelp:(id)sender { - (void)showGestureHelp:(id)sender {
#if !defined(TARGET_OS_VISION) || TARGET_OS_VISION == 0
[self setGestureHelpHidden:NO]; [self setGestureHelpHidden:NO];
#endif
} }
- (void)hideGestureHelp:(id)sender { - (void)hideGestureHelp:(id)sender {
@ -269,7 +304,11 @@ API_AVAILABLE(ios(13.4))
} }
- (BOOL)isKeyboardVisible { - (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; return keyboardView != nil && CGRectIntersectsRect(keyboardView.frame, self.view.bounds) && !keyboardView.hidden;
#endif
} }
- (void)setKeyboardVisible:(BOOL)keyboardVisible { - (void)setKeyboardVisible:(BOOL)keyboardVisible {
@ -285,10 +324,24 @@ API_AVAILABLE(ios(13.4))
} }
- (void)setKeyboardVisible:(BOOL)visible animated:(BOOL)animated { - (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) { if (self.keyboardVisible == visible) {
return; return;
} }
if (visible) { if (visible) {
[[NSUserDefaults standardUserDefaults] addObserver:self forKeyPath:@"keyboardLayout" options:0 context:NULL]; [[NSUserDefaults standardUserDefaults] addObserver:self forKeyPath:@"keyboardLayout" options:0 context:NULL];
[self loadKeyboardView]; [self loadKeyboardView];
@ -322,7 +375,7 @@ API_AVAILABLE(ios(13.4))
keyboardView.hidden = YES; keyboardView.hidden = YES;
} }
} }
#endif
} }
- (void)loadKeyboardView { - (void)loadKeyboardView {
@ -364,16 +417,106 @@ API_AVAILABLE(ios(13.4))
[[AppDelegate sharedEmulator] keyUp:scancode]; [[AppDelegate sharedEmulator] keyUp:scancode];
} }
@end - (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event {
for (UIPress *press in presses) {
#ifdef __IPHONE_13_4 [self handlePressEvent:press];
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)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; return defaultRegion;
} }
@ -381,5 +524,9 @@ API_AVAILABLE(ios(13.4))
return [UIPointerStyle hiddenPointerStyle]; return [UIPointerStyle hiddenPointerStyle];
} }
- (void)hover:(UIHoverGestureRecognizer *)hoverGestureRecognizer {
Point mouseLoc = [self mouseLocForCGPoint:[hoverGestureRecognizer locationInView:self.screenView]];
[[AppDelegate sharedEmulator] setMouseX:mouseLoc.h Y:mouseLoc.v];
}
@end @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[]) { int main(int argc, char * argv[]) {
@autoreleasepool { @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 * Emulates Mac Plus, Mac II or Mac 128K
* Full simulated keyboard (including all Mac keys) * Full simulated keyboard (including all Mac keys)
* Full sound output * Full sound output
* Uses external keyboard if available * Uses external keyboard and mouse/trackpad if available
* Regulable emulation speed * Regulable emulation speed
* Easy(ish) to import/export disk images * Easy(ish) to import/export disk images
## Requirements ## 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 * ROM image from Mac Plus, Mac II and/or Mac 128K
* Disk images with Mac software * 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 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 ### Settings
Swipe right with two fingers to show the settings dialog, where you can change Swipe right with two fingers to show the settings dialog, where you can change