├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── OpenSim.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── OpenSim.xcscheme ├── OpenSim ├── ActionMenu.swift ├── AppDelegate.swift ├── AppInfoView.swift ├── AppMenuItem.swift ├── Application.swift ├── ApplicationActionable.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── industry-electro-devices-icon-1024.png │ │ ├── industry-electro-devices-icon-128.png │ │ ├── industry-electro-devices-icon-16.png │ │ ├── industry-electro-devices-icon-256.png │ │ ├── industry-electro-devices-icon-32.png │ │ ├── industry-electro-devices-icon-512.png │ │ └── industry-electro-devices-icon-64.png │ ├── Contents.json │ ├── DefaultAppIcon.imageset │ │ ├── Contents.json │ │ ├── icon-60.png │ │ ├── icon-60@2x.png │ │ └── icon-60@3x.png │ ├── active.imageset │ │ ├── Contents.json │ │ ├── active.png │ │ ├── active@2x.png │ │ └── active@3x.png │ ├── inactive.imageset │ │ ├── Contents.json │ │ ├── inactive.png │ │ ├── inactive@2x.png │ │ └── inactive@3x.png │ ├── launch.imageset │ │ ├── Contents.json │ │ ├── launch.png │ │ └── launch@2x.png │ ├── menubar.imageset │ │ ├── 1447255133_electro_devices-1.png │ │ ├── 1447255133_electro_devices.png │ │ └── Contents.json │ ├── reveal.imageset │ │ ├── Contents.json │ │ ├── reveal.png │ │ └── reveal@2x.png │ ├── share.imageset │ │ ├── Contents.json │ │ ├── share.png │ │ └── share@2x.png │ ├── terminal.imageset │ │ ├── Contents.json │ │ ├── term.png │ │ └── term@2x.png │ └── uninstall.imageset │ │ ├── Contents.json │ │ ├── uninstall.png │ │ └── uninstall@2x.png ├── Base.lproj │ ├── Localizable.strings │ └── MainMenu.xib ├── CancelBlocks.swift ├── Constants.swift ├── CopyToPasteboardAction.swift ├── Device.swift ├── DeviceManager.swift ├── DirectoryWatcher.swift ├── ExtraApplicationActionable.swift ├── FileInfo.swift ├── Helper.swift ├── ImageExtension.swift ├── Info.plist ├── LaunchAction.swift ├── LaunchAtLoginHelper.swift ├── MenuManager.swift ├── OpenInItermAction.swift ├── OpenInTerminalAction.swift ├── OpenRealmAction.swift ├── RevealInFinderAction.swift ├── Runtime.swift ├── Simulator.swift ├── SimulatorController.swift ├── SimulatorEraseMenuItem.swift ├── SimulatorMenuItem.swift ├── SimulatorResetMenuItem.swift ├── SimulatorShutdownMenuItem.swift ├── URLHelper.swift ├── UninstallAction.swift └── pt-BR.lproj │ └── Localizable.strings └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/swift 2 | 3 | ### Swift ### 4 | # Xcode 5 | # 6 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 7 | 8 | ## Build generated 9 | build/ 10 | DerivedData 11 | 12 | ## Various settings 13 | *.pbxuser 14 | !default.pbxuser 15 | *.mode1v3 16 | !default.mode1v3 17 | *.mode2v3 18 | !default.mode2v3 19 | *.perspectivev3 20 | !default.perspectivev3 21 | xcuserdata 22 | 23 | ## Other 24 | *.xccheckout 25 | *.moved-aside 26 | *.xcuserstate 27 | *.xcscmblueprint 28 | 29 | ## Obj-C/Swift specific 30 | *.hmap 31 | *.ipa 32 | 33 | # CocoaPods 34 | # 35 | # We recommend against adding the Pods directory to your .gitignore. However 36 | # you should judge for yourself, the pros and cons are mentioned at: 37 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 38 | # 39 | # Pods/ 40 | 41 | # Carthage 42 | # 43 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 44 | # Carthage/Checkouts 45 | 46 | Carthage/Build 47 | 48 | .DS_Store 49 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | 3 | xcode_project: OpenSim.xcodeproj 4 | xcode_scheme: OpenSim 5 | osx_image: xcode10.1 6 | 7 | script: 8 | - xcodebuild clean build -project OpenSim.xcodeproj -scheme OpenSim 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | #Change Log 2 | 3 | ## 0.2.0 (2017-05-16) 4 | 5 | ### Added 6 | * Extensions have been added. Users should be able to perform extra behaviors depending on the apps installed on their machines. Currently, iTerm and Realm browser are supported. 7 | 8 | ### Changed 9 | * Simulators with unavailable runtimes are not displayed. 10 | * Simulators with no apps are not displayed. 11 | 12 | ### Fixed 13 | * Fix an issue where checking exisiting startup items may crash the app. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Luo Sheng 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /OpenSim.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 256B782C2278A08500052809 /* LaunchAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 256B782B2278A08500052809 /* LaunchAction.swift */; }; 11 | 312A27FD21A753E600699668 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 312A27FC21A753E600699668 /* Constants.swift */; }; 12 | 31A79B15219B81660024DF7B /* Simulator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31A79B14219B81660024DF7B /* Simulator.swift */; }; 13 | 31C4BF3221A8AC56008B97A1 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 31C4BF3021A8AC56008B97A1 /* Localizable.strings */; }; 14 | AF9D003A1D110E750065AFD0 /* Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF9D00391D110E750065AFD0 /* Helper.swift */; }; 15 | B20671202353ECAA00D6ED0D /* SimulatorShutdownMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B206711F2353ECAA00D6ED0D /* SimulatorShutdownMenuItem.swift */; }; 16 | B20671222353ECB600D6ED0D /* SimulatorResetMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B20671212353ECB600D6ED0D /* SimulatorResetMenuItem.swift */; }; 17 | B272BE94239A0D5B001C50A1 /* SimulatorEraseMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B272BE93239A0D5B001C50A1 /* SimulatorEraseMenuItem.swift */; }; 18 | B3054E181BF381CE00F433C2 /* FileInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3054E161BF381CE00F433C2 /* FileInfo.swift */; }; 19 | B3054E1A1BF3958500F433C2 /* DirectoryWatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3054E191BF3958500F433C2 /* DirectoryWatcher.swift */; }; 20 | B3054E1C1BF3988300F433C2 /* CancelBlocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3054E1B1BF3988300F433C2 /* CancelBlocks.swift */; }; 21 | B310B99B1D1AA08500D880CA /* ImageExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B310B99A1D1AA08500D880CA /* ImageExtension.swift */; }; 22 | B32480551EBEB705000633FC /* ActionMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = B32480541EBEB705000633FC /* ActionMenu.swift */; }; 23 | B39B2B611EBEF1A100CDD74C /* AppMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B39B2B601EBEF1A100CDD74C /* AppMenuItem.swift */; }; 24 | B39B2B631EBEFFB700CDD74C /* AppInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B39B2B621EBEFFB700CDD74C /* AppInfoView.swift */; }; 25 | B39B2B671EBF0D0600CDD74C /* LaunchAtLoginHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B39B2B661EBF0D0600CDD74C /* LaunchAtLoginHelper.swift */; }; 26 | B39B2B6A1EBF501F00CDD74C /* ApplicationActionable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B39B2B691EBF501F00CDD74C /* ApplicationActionable.swift */; }; 27 | B39B2B6C1EBF510F00CDD74C /* RevealInFinderAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B39B2B6B1EBF510F00CDD74C /* RevealInFinderAction.swift */; }; 28 | B39B2B6F1EBF567F00CDD74C /* CopyToPasteboardAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B39B2B6E1EBF567F00CDD74C /* CopyToPasteboardAction.swift */; }; 29 | B39B2B711EBF56F200CDD74C /* OpenInTerminalAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B39B2B701EBF56F200CDD74C /* OpenInTerminalAction.swift */; }; 30 | B39B2B731EBF573C00CDD74C /* UninstallAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B39B2B721EBF573C00CDD74C /* UninstallAction.swift */; }; 31 | B39B2B751EBF58EC00CDD74C /* OpenInItermAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B39B2B741EBF58EC00CDD74C /* OpenInItermAction.swift */; }; 32 | B39B2B771EBF5F4200CDD74C /* ExtraApplicationActionable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B39B2B761EBF5F4200CDD74C /* ExtraApplicationActionable.swift */; }; 33 | B39B2B791EBF614400CDD74C /* OpenRealmAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B39B2B781EBF614400CDD74C /* OpenRealmAction.swift */; }; 34 | B3A1E3231BF049690090EC58 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3A1E3221BF049690090EC58 /* AppDelegate.swift */; }; 35 | B3A1E3251BF049690090EC58 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B3A1E3241BF049690090EC58 /* Assets.xcassets */; }; 36 | B3A1E3281BF049690090EC58 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = B3A1E3261BF049690090EC58 /* MainMenu.xib */; }; 37 | B3A1E3301BF05F980090EC58 /* DeviceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3A1E32F1BF05F980090EC58 /* DeviceManager.swift */; }; 38 | B3A1E3321BF062C40090EC58 /* Device.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3A1E3311BF062C40090EC58 /* Device.swift */; }; 39 | B3A1E3341BF0709A0090EC58 /* URLHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3A1E3331BF0709A0090EC58 /* URLHelper.swift */; }; 40 | B3D188E91BF436480064E851 /* Runtime.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3D188E81BF436480064E851 /* Runtime.swift */; }; 41 | B3E67B5E1CA412BD00744B38 /* MenuManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3E67B5D1CA412BD00744B38 /* MenuManager.swift */; }; 42 | B3F493A61BF0822800C64FF0 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3F493A51BF0822800C64FF0 /* Application.swift */; }; 43 | B537A3F3228D2D1400E6E51E /* SimulatorMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B537A3F2228D2D1400E6E51E /* SimulatorMenuItem.swift */; }; 44 | D17669C11D1883AE00816D61 /* SimulatorController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D17669C01D1883AE00816D61 /* SimulatorController.swift */; }; 45 | /* End PBXBuildFile section */ 46 | 47 | /* Begin PBXFileReference section */ 48 | 256B782B2278A08500052809 /* LaunchAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LaunchAction.swift; sourceTree = ""; }; 49 | 312A27FC21A753E600699668 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 50 | 31A79B14219B81660024DF7B /* Simulator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Simulator.swift; sourceTree = ""; }; 51 | 31C4BF3121A8AC56008B97A1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; 52 | 31C4BF3421A8AC72008B97A1 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; 53 | AF9D00391D110E750065AFD0 /* Helper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Helper.swift; sourceTree = ""; }; 54 | B206711F2353ECAA00D6ED0D /* SimulatorShutdownMenuItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimulatorShutdownMenuItem.swift; sourceTree = ""; }; 55 | B20671212353ECB600D6ED0D /* SimulatorResetMenuItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimulatorResetMenuItem.swift; sourceTree = ""; }; 56 | B272BE93239A0D5B001C50A1 /* SimulatorEraseMenuItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimulatorEraseMenuItem.swift; sourceTree = ""; }; 57 | B3054E161BF381CE00F433C2 /* FileInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileInfo.swift; sourceTree = ""; }; 58 | B3054E191BF3958500F433C2 /* DirectoryWatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectoryWatcher.swift; sourceTree = ""; }; 59 | B3054E1B1BF3988300F433C2 /* CancelBlocks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CancelBlocks.swift; sourceTree = ""; }; 60 | B310B99A1D1AA08500D880CA /* ImageExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageExtension.swift; sourceTree = ""; }; 61 | B32480541EBEB705000633FC /* ActionMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionMenu.swift; sourceTree = ""; }; 62 | B39B2B601EBEF1A100CDD74C /* AppMenuItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppMenuItem.swift; sourceTree = ""; }; 63 | B39B2B621EBEFFB700CDD74C /* AppInfoView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppInfoView.swift; sourceTree = ""; }; 64 | B39B2B661EBF0D0600CDD74C /* LaunchAtLoginHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LaunchAtLoginHelper.swift; sourceTree = ""; }; 65 | B39B2B691EBF501F00CDD74C /* ApplicationActionable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplicationActionable.swift; sourceTree = ""; }; 66 | B39B2B6B1EBF510F00CDD74C /* RevealInFinderAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RevealInFinderAction.swift; sourceTree = ""; }; 67 | B39B2B6E1EBF567F00CDD74C /* CopyToPasteboardAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CopyToPasteboardAction.swift; sourceTree = ""; }; 68 | B39B2B701EBF56F200CDD74C /* OpenInTerminalAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenInTerminalAction.swift; sourceTree = ""; }; 69 | B39B2B721EBF573C00CDD74C /* UninstallAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UninstallAction.swift; sourceTree = ""; }; 70 | B39B2B741EBF58EC00CDD74C /* OpenInItermAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenInItermAction.swift; sourceTree = ""; }; 71 | B39B2B761EBF5F4200CDD74C /* ExtraApplicationActionable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtraApplicationActionable.swift; sourceTree = ""; }; 72 | B39B2B781EBF614400CDD74C /* OpenRealmAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenRealmAction.swift; sourceTree = ""; }; 73 | B3A1E31F1BF049690090EC58 /* OpenSim.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OpenSim.app; sourceTree = BUILT_PRODUCTS_DIR; }; 74 | B3A1E3221BF049690090EC58 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 75 | B3A1E3241BF049690090EC58 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 76 | B3A1E3271BF049690090EC58 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 77 | B3A1E3291BF049690090EC58 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 78 | B3A1E32F1BF05F980090EC58 /* DeviceManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceManager.swift; sourceTree = ""; }; 79 | B3A1E3311BF062C40090EC58 /* Device.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Device.swift; sourceTree = ""; }; 80 | B3A1E3331BF0709A0090EC58 /* URLHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLHelper.swift; sourceTree = ""; }; 81 | B3D188E81BF436480064E851 /* Runtime.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Runtime.swift; sourceTree = ""; }; 82 | B3E67B5D1CA412BD00744B38 /* MenuManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuManager.swift; sourceTree = ""; }; 83 | B3F493A51BF0822800C64FF0 /* Application.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = ""; }; 84 | B537A3F2228D2D1400E6E51E /* SimulatorMenuItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimulatorMenuItem.swift; sourceTree = ""; }; 85 | D17669C01D1883AE00816D61 /* SimulatorController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimulatorController.swift; sourceTree = ""; }; 86 | /* End PBXFileReference section */ 87 | 88 | /* Begin PBXFrameworksBuildPhase section */ 89 | B3A1E31C1BF049690090EC58 /* Frameworks */ = { 90 | isa = PBXFrameworksBuildPhase; 91 | buildActionMask = 2147483647; 92 | files = ( 93 | ); 94 | runOnlyForDeploymentPostprocessing = 0; 95 | }; 96 | /* End PBXFrameworksBuildPhase section */ 97 | 98 | /* Begin PBXGroup section */ 99 | 31C4BF2321A8AA3D008B97A1 /* AppDelegate */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | B3A1E3221BF049690090EC58 /* AppDelegate.swift */, 103 | ); 104 | name = AppDelegate; 105 | sourceTree = ""; 106 | }; 107 | 31C4BF2721A8AA52008B97A1 /* Supporting Files */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | B3A1E3241BF049690090EC58 /* Assets.xcassets */, 111 | 31C4BF3021A8AC56008B97A1 /* Localizable.strings */, 112 | B3A1E3261BF049690090EC58 /* MainMenu.xib */, 113 | B3A1E3291BF049690090EC58 /* Info.plist */, 114 | ); 115 | name = "Supporting Files"; 116 | sourceTree = ""; 117 | }; 118 | B36856AC1CA41BA8009BE4CC /* Models */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | B3F493A51BF0822800C64FF0 /* Application.swift */, 122 | 31A79B14219B81660024DF7B /* Simulator.swift */, 123 | B3A1E3311BF062C40090EC58 /* Device.swift */, 124 | B3D188E81BF436480064E851 /* Runtime.swift */, 125 | B3A1E32F1BF05F980090EC58 /* DeviceManager.swift */, 126 | B3054E161BF381CE00F433C2 /* FileInfo.swift */, 127 | B39B2B691EBF501F00CDD74C /* ApplicationActionable.swift */, 128 | B39B2B761EBF5F4200CDD74C /* ExtraApplicationActionable.swift */, 129 | ); 130 | name = Models; 131 | sourceTree = ""; 132 | }; 133 | B36856AD1CA41BCB009BE4CC /* Utilities */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | B3A1E3331BF0709A0090EC58 /* URLHelper.swift */, 137 | B3054E191BF3958500F433C2 /* DirectoryWatcher.swift */, 138 | B3054E1B1BF3988300F433C2 /* CancelBlocks.swift */, 139 | AF9D00391D110E750065AFD0 /* Helper.swift */, 140 | D17669C01D1883AE00816D61 /* SimulatorController.swift */, 141 | B310B99A1D1AA08500D880CA /* ImageExtension.swift */, 142 | B39B2B661EBF0D0600CDD74C /* LaunchAtLoginHelper.swift */, 143 | ); 144 | name = Utilities; 145 | sourceTree = ""; 146 | }; 147 | B36856AE1CA41BD4009BE4CC /* UI */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | B3E67B5D1CA412BD00744B38 /* MenuManager.swift */, 151 | B39B2B601EBEF1A100CDD74C /* AppMenuItem.swift */, 152 | B32480541EBEB705000633FC /* ActionMenu.swift */, 153 | B39B2B621EBEFFB700CDD74C /* AppInfoView.swift */, 154 | 312A27FC21A753E600699668 /* Constants.swift */, 155 | B537A3F2228D2D1400E6E51E /* SimulatorMenuItem.swift */, 156 | B206711F2353ECAA00D6ED0D /* SimulatorShutdownMenuItem.swift */, 157 | B20671212353ECB600D6ED0D /* SimulatorResetMenuItem.swift */, 158 | B272BE93239A0D5B001C50A1 /* SimulatorEraseMenuItem.swift */, 159 | ); 160 | name = UI; 161 | sourceTree = ""; 162 | }; 163 | B39B2B6D1EBF511200CDD74C /* Actions */ = { 164 | isa = PBXGroup; 165 | children = ( 166 | B39B2B6B1EBF510F00CDD74C /* RevealInFinderAction.swift */, 167 | B39B2B6E1EBF567F00CDD74C /* CopyToPasteboardAction.swift */, 168 | B39B2B701EBF56F200CDD74C /* OpenInTerminalAction.swift */, 169 | B39B2B721EBF573C00CDD74C /* UninstallAction.swift */, 170 | B39B2B741EBF58EC00CDD74C /* OpenInItermAction.swift */, 171 | B39B2B781EBF614400CDD74C /* OpenRealmAction.swift */, 172 | 256B782B2278A08500052809 /* LaunchAction.swift */, 173 | ); 174 | name = Actions; 175 | sourceTree = ""; 176 | }; 177 | B3A1E3161BF049690090EC58 = { 178 | isa = PBXGroup; 179 | children = ( 180 | B3A1E3211BF049690090EC58 /* OpenSim */, 181 | B3A1E3201BF049690090EC58 /* Products */, 182 | ); 183 | sourceTree = ""; 184 | }; 185 | B3A1E3201BF049690090EC58 /* Products */ = { 186 | isa = PBXGroup; 187 | children = ( 188 | B3A1E31F1BF049690090EC58 /* OpenSim.app */, 189 | ); 190 | name = Products; 191 | sourceTree = ""; 192 | }; 193 | B3A1E3211BF049690090EC58 /* OpenSim */ = { 194 | isa = PBXGroup; 195 | children = ( 196 | 31C4BF2321A8AA3D008B97A1 /* AppDelegate */, 197 | B36856AC1CA41BA8009BE4CC /* Models */, 198 | B39B2B6D1EBF511200CDD74C /* Actions */, 199 | B36856AD1CA41BCB009BE4CC /* Utilities */, 200 | B36856AE1CA41BD4009BE4CC /* UI */, 201 | 31C4BF2721A8AA52008B97A1 /* Supporting Files */, 202 | ); 203 | path = OpenSim; 204 | sourceTree = ""; 205 | }; 206 | /* End PBXGroup section */ 207 | 208 | /* Begin PBXNativeTarget section */ 209 | B3A1E31E1BF049690090EC58 /* OpenSim */ = { 210 | isa = PBXNativeTarget; 211 | buildConfigurationList = B3A1E32C1BF049690090EC58 /* Build configuration list for PBXNativeTarget "OpenSim" */; 212 | buildPhases = ( 213 | B3A1E31B1BF049690090EC58 /* Sources */, 214 | B3A1E31C1BF049690090EC58 /* Frameworks */, 215 | B3A1E31D1BF049690090EC58 /* Resources */, 216 | ); 217 | buildRules = ( 218 | ); 219 | dependencies = ( 220 | ); 221 | name = OpenSim; 222 | productName = OpenSim; 223 | productReference = B3A1E31F1BF049690090EC58 /* OpenSim.app */; 224 | productType = "com.apple.product-type.application"; 225 | }; 226 | /* End PBXNativeTarget section */ 227 | 228 | /* Begin PBXProject section */ 229 | B3A1E3171BF049690090EC58 /* Project object */ = { 230 | isa = PBXProject; 231 | attributes = { 232 | LastSwiftUpdateCheck = 0710; 233 | LastUpgradeCheck = 1000; 234 | ORGANIZATIONNAME = "Luo Sheng"; 235 | TargetAttributes = { 236 | B3A1E31E1BF049690090EC58 = { 237 | CreatedOnToolsVersion = 7.1; 238 | LastSwiftMigration = 0900; 239 | }; 240 | }; 241 | }; 242 | buildConfigurationList = B3A1E31A1BF049690090EC58 /* Build configuration list for PBXProject "OpenSim" */; 243 | compatibilityVersion = "Xcode 3.2"; 244 | developmentRegion = English; 245 | hasScannedForEncodings = 0; 246 | knownRegions = ( 247 | English, 248 | en, 249 | Base, 250 | "pt-BR", 251 | ); 252 | mainGroup = B3A1E3161BF049690090EC58; 253 | productRefGroup = B3A1E3201BF049690090EC58 /* Products */; 254 | projectDirPath = ""; 255 | projectRoot = ""; 256 | targets = ( 257 | B3A1E31E1BF049690090EC58 /* OpenSim */, 258 | ); 259 | }; 260 | /* End PBXProject section */ 261 | 262 | /* Begin PBXResourcesBuildPhase section */ 263 | B3A1E31D1BF049690090EC58 /* Resources */ = { 264 | isa = PBXResourcesBuildPhase; 265 | buildActionMask = 2147483647; 266 | files = ( 267 | B3A1E3251BF049690090EC58 /* Assets.xcassets in Resources */, 268 | 31C4BF3221A8AC56008B97A1 /* Localizable.strings in Resources */, 269 | B3A1E3281BF049690090EC58 /* MainMenu.xib in Resources */, 270 | ); 271 | runOnlyForDeploymentPostprocessing = 0; 272 | }; 273 | /* End PBXResourcesBuildPhase section */ 274 | 275 | /* Begin PBXSourcesBuildPhase section */ 276 | B3A1E31B1BF049690090EC58 /* Sources */ = { 277 | isa = PBXSourcesBuildPhase; 278 | buildActionMask = 2147483647; 279 | files = ( 280 | 31A79B15219B81660024DF7B /* Simulator.swift in Sources */, 281 | B39B2B6C1EBF510F00CDD74C /* RevealInFinderAction.swift in Sources */, 282 | D17669C11D1883AE00816D61 /* SimulatorController.swift in Sources */, 283 | B3F493A61BF0822800C64FF0 /* Application.swift in Sources */, 284 | B39B2B611EBEF1A100CDD74C /* AppMenuItem.swift in Sources */, 285 | B3A1E3321BF062C40090EC58 /* Device.swift in Sources */, 286 | B20671202353ECAA00D6ED0D /* SimulatorShutdownMenuItem.swift in Sources */, 287 | B39B2B711EBF56F200CDD74C /* OpenInTerminalAction.swift in Sources */, 288 | B310B99B1D1AA08500D880CA /* ImageExtension.swift in Sources */, 289 | B3054E1C1BF3988300F433C2 /* CancelBlocks.swift in Sources */, 290 | B3A1E3341BF0709A0090EC58 /* URLHelper.swift in Sources */, 291 | B272BE94239A0D5B001C50A1 /* SimulatorEraseMenuItem.swift in Sources */, 292 | B537A3F3228D2D1400E6E51E /* SimulatorMenuItem.swift in Sources */, 293 | B3D188E91BF436480064E851 /* Runtime.swift in Sources */, 294 | B39B2B791EBF614400CDD74C /* OpenRealmAction.swift in Sources */, 295 | B39B2B771EBF5F4200CDD74C /* ExtraApplicationActionable.swift in Sources */, 296 | B39B2B751EBF58EC00CDD74C /* OpenInItermAction.swift in Sources */, 297 | B3054E181BF381CE00F433C2 /* FileInfo.swift in Sources */, 298 | B39B2B6A1EBF501F00CDD74C /* ApplicationActionable.swift in Sources */, 299 | B3E67B5E1CA412BD00744B38 /* MenuManager.swift in Sources */, 300 | B39B2B671EBF0D0600CDD74C /* LaunchAtLoginHelper.swift in Sources */, 301 | B39B2B631EBEFFB700CDD74C /* AppInfoView.swift in Sources */, 302 | B3A1E3301BF05F980090EC58 /* DeviceManager.swift in Sources */, 303 | 312A27FD21A753E600699668 /* Constants.swift in Sources */, 304 | B3A1E3231BF049690090EC58 /* AppDelegate.swift in Sources */, 305 | B39B2B6F1EBF567F00CDD74C /* CopyToPasteboardAction.swift in Sources */, 306 | B39B2B731EBF573C00CDD74C /* UninstallAction.swift in Sources */, 307 | B3054E1A1BF3958500F433C2 /* DirectoryWatcher.swift in Sources */, 308 | B20671222353ECB600D6ED0D /* SimulatorResetMenuItem.swift in Sources */, 309 | B32480551EBEB705000633FC /* ActionMenu.swift in Sources */, 310 | 256B782C2278A08500052809 /* LaunchAction.swift in Sources */, 311 | AF9D003A1D110E750065AFD0 /* Helper.swift in Sources */, 312 | ); 313 | runOnlyForDeploymentPostprocessing = 0; 314 | }; 315 | /* End PBXSourcesBuildPhase section */ 316 | 317 | /* Begin PBXVariantGroup section */ 318 | 31C4BF3021A8AC56008B97A1 /* Localizable.strings */ = { 319 | isa = PBXVariantGroup; 320 | children = ( 321 | 31C4BF3121A8AC56008B97A1 /* Base */, 322 | 31C4BF3421A8AC72008B97A1 /* pt-BR */, 323 | ); 324 | name = Localizable.strings; 325 | sourceTree = ""; 326 | }; 327 | B3A1E3261BF049690090EC58 /* MainMenu.xib */ = { 328 | isa = PBXVariantGroup; 329 | children = ( 330 | B3A1E3271BF049690090EC58 /* Base */, 331 | ); 332 | name = MainMenu.xib; 333 | sourceTree = ""; 334 | }; 335 | /* End PBXVariantGroup section */ 336 | 337 | /* Begin XCBuildConfiguration section */ 338 | B3A1E32A1BF049690090EC58 /* Debug */ = { 339 | isa = XCBuildConfiguration; 340 | buildSettings = { 341 | ALWAYS_SEARCH_USER_PATHS = NO; 342 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 343 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 344 | CLANG_CXX_LIBRARY = "libc++"; 345 | CLANG_ENABLE_MODULES = YES; 346 | CLANG_ENABLE_OBJC_ARC = YES; 347 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 348 | CLANG_WARN_BOOL_CONVERSION = YES; 349 | CLANG_WARN_COMMA = YES; 350 | CLANG_WARN_CONSTANT_CONVERSION = YES; 351 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 352 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 353 | CLANG_WARN_EMPTY_BODY = YES; 354 | CLANG_WARN_ENUM_CONVERSION = YES; 355 | CLANG_WARN_INFINITE_RECURSION = YES; 356 | CLANG_WARN_INT_CONVERSION = YES; 357 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 358 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 359 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 360 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 361 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 362 | CLANG_WARN_STRICT_PROTOTYPES = YES; 363 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 364 | CLANG_WARN_UNREACHABLE_CODE = YES; 365 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 366 | CODE_SIGN_IDENTITY = "-"; 367 | COPY_PHASE_STRIP = NO; 368 | DEBUG_INFORMATION_FORMAT = dwarf; 369 | ENABLE_STRICT_OBJC_MSGSEND = YES; 370 | ENABLE_TESTABILITY = YES; 371 | GCC_C_LANGUAGE_STANDARD = gnu99; 372 | GCC_DYNAMIC_NO_PIC = NO; 373 | GCC_NO_COMMON_BLOCKS = YES; 374 | GCC_OPTIMIZATION_LEVEL = 0; 375 | GCC_PREPROCESSOR_DEFINITIONS = ( 376 | "DEBUG=1", 377 | "$(inherited)", 378 | ); 379 | GCC_TREAT_WARNINGS_AS_ERRORS = YES; 380 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 381 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 382 | GCC_WARN_UNDECLARED_SELECTOR = YES; 383 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 384 | GCC_WARN_UNUSED_FUNCTION = YES; 385 | GCC_WARN_UNUSED_VARIABLE = YES; 386 | MACOSX_DEPLOYMENT_TARGET = 10.11; 387 | MTL_ENABLE_DEBUG_INFO = YES; 388 | ONLY_ACTIVE_ARCH = YES; 389 | SDKROOT = macosx; 390 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 391 | }; 392 | name = Debug; 393 | }; 394 | B3A1E32B1BF049690090EC58 /* Release */ = { 395 | isa = XCBuildConfiguration; 396 | buildSettings = { 397 | ALWAYS_SEARCH_USER_PATHS = NO; 398 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 399 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 400 | CLANG_CXX_LIBRARY = "libc++"; 401 | CLANG_ENABLE_MODULES = YES; 402 | CLANG_ENABLE_OBJC_ARC = YES; 403 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 404 | CLANG_WARN_BOOL_CONVERSION = YES; 405 | CLANG_WARN_COMMA = YES; 406 | CLANG_WARN_CONSTANT_CONVERSION = YES; 407 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 408 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 409 | CLANG_WARN_EMPTY_BODY = YES; 410 | CLANG_WARN_ENUM_CONVERSION = YES; 411 | CLANG_WARN_INFINITE_RECURSION = YES; 412 | CLANG_WARN_INT_CONVERSION = YES; 413 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 414 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 415 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 416 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 417 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 418 | CLANG_WARN_STRICT_PROTOTYPES = YES; 419 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 420 | CLANG_WARN_UNREACHABLE_CODE = YES; 421 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 422 | CODE_SIGN_IDENTITY = "-"; 423 | COPY_PHASE_STRIP = NO; 424 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 425 | ENABLE_NS_ASSERTIONS = NO; 426 | ENABLE_STRICT_OBJC_MSGSEND = YES; 427 | GCC_C_LANGUAGE_STANDARD = gnu99; 428 | GCC_NO_COMMON_BLOCKS = YES; 429 | GCC_TREAT_WARNINGS_AS_ERRORS = YES; 430 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 431 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 432 | GCC_WARN_UNDECLARED_SELECTOR = YES; 433 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 434 | GCC_WARN_UNUSED_FUNCTION = YES; 435 | GCC_WARN_UNUSED_VARIABLE = YES; 436 | MACOSX_DEPLOYMENT_TARGET = 10.11; 437 | MTL_ENABLE_DEBUG_INFO = NO; 438 | SDKROOT = macosx; 439 | }; 440 | name = Release; 441 | }; 442 | B3A1E32D1BF049690090EC58 /* Debug */ = { 443 | isa = XCBuildConfiguration; 444 | buildSettings = { 445 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 446 | COMBINE_HIDPI_IMAGES = YES; 447 | INFOPLIST_FILE = OpenSim/Info.plist; 448 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 449 | MACOSX_DEPLOYMENT_TARGET = 10.10; 450 | PRODUCT_BUNDLE_IDENTIFIER = "com.pop-tap.OpenSim"; 451 | PRODUCT_NAME = OpenSim; 452 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 453 | SWIFT_VERSION = 4.0; 454 | }; 455 | name = Debug; 456 | }; 457 | B3A1E32E1BF049690090EC58 /* Release */ = { 458 | isa = XCBuildConfiguration; 459 | buildSettings = { 460 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 461 | COMBINE_HIDPI_IMAGES = YES; 462 | INFOPLIST_FILE = OpenSim/Info.plist; 463 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 464 | MACOSX_DEPLOYMENT_TARGET = 10.10; 465 | PRODUCT_BUNDLE_IDENTIFIER = "com.pop-tap.OpenSim"; 466 | PRODUCT_NAME = OpenSim; 467 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 468 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 469 | SWIFT_VERSION = 4.0; 470 | }; 471 | name = Release; 472 | }; 473 | /* End XCBuildConfiguration section */ 474 | 475 | /* Begin XCConfigurationList section */ 476 | B3A1E31A1BF049690090EC58 /* Build configuration list for PBXProject "OpenSim" */ = { 477 | isa = XCConfigurationList; 478 | buildConfigurations = ( 479 | B3A1E32A1BF049690090EC58 /* Debug */, 480 | B3A1E32B1BF049690090EC58 /* Release */, 481 | ); 482 | defaultConfigurationIsVisible = 0; 483 | defaultConfigurationName = Release; 484 | }; 485 | B3A1E32C1BF049690090EC58 /* Build configuration list for PBXNativeTarget "OpenSim" */ = { 486 | isa = XCConfigurationList; 487 | buildConfigurations = ( 488 | B3A1E32D1BF049690090EC58 /* Debug */, 489 | B3A1E32E1BF049690090EC58 /* Release */, 490 | ); 491 | defaultConfigurationIsVisible = 0; 492 | defaultConfigurationName = Release; 493 | }; 494 | /* End XCConfigurationList section */ 495 | }; 496 | rootObject = B3A1E3171BF049690090EC58 /* Project object */; 497 | } 498 | -------------------------------------------------------------------------------- /OpenSim.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /OpenSim.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /OpenSim.xcodeproj/xcshareddata/xcschemes/OpenSim.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /OpenSim/ActionMenu.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ActionMenu.swift 3 | // OpenSim 4 | // 5 | // Created by Luo Sheng on 07/05/2017. 6 | // Copyright © 2017 Luo Sheng. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | final class ActionMenu: NSMenu { 12 | 13 | private weak var application: Application! 14 | 15 | private static let standardActions: [ApplicationActionable.Type] = [ 16 | RevealInFinderAction.self, 17 | CopyToPasteboardAction.self, 18 | OpenInTerminalAction.self, 19 | LaunchAction.self, 20 | UninstallAction.self 21 | ] 22 | 23 | private static let extraActions: [ApplicationActionable.Type] = [ 24 | OpenInItermAction.self, 25 | OpenRealmAction.self 26 | ] 27 | 28 | private var appInfoItem: NSMenuItem { 29 | let item = NSMenuItem() 30 | item.view = AppInfoView(application: application) 31 | return item 32 | } 33 | 34 | init(device: Device, application: Application) { 35 | self.application = application 36 | 37 | super.init(title: "") 38 | 39 | buildMenuItems() 40 | } 41 | 42 | required init(coder decoder: NSCoder) { 43 | fatalError("init(coder:) has not been implemented") 44 | } 45 | 46 | private func buildMenuItems() { 47 | let createAction: (ApplicationActionable.Type) -> ApplicationActionable = { $0.init(application: self.application) } 48 | 49 | self.buildMenuSection(title: UIConstants.strings.menuHeaderActions, actions: ActionMenu.standardActions.map(createAction)) 50 | self.buildMenuSection(title: UIConstants.strings.menuHeaderExtensions, actions: ActionMenu.extraActions.map(createAction)) 51 | self.addItem(self.buildSectionTitle(title: UIConstants.strings.menuHeaderAppInformation)) 52 | self.addItem(appInfoItem) 53 | } 54 | 55 | private func buildMenuSection(title: String, actions: [ApplicationActionable]) { 56 | self.addItem(self.buildSectionTitle(title: title)) 57 | 58 | actions.forEach { (action) in 59 | if let item = buildMenuItem(for: action) { 60 | self.addItem(item) 61 | } 62 | } 63 | 64 | self.addItem(NSMenuItem.separator()) 65 | } 66 | 67 | private func buildSectionTitle(title: String) -> NSMenuItem { 68 | let titleItem = NSMenuItem(title: title, action: nil, keyEquivalent: "") 69 | titleItem.isEnabled = false 70 | return titleItem 71 | } 72 | 73 | private func buildMenuItem(`for` action: ApplicationActionable) -> NSMenuItem? { 74 | if !action.isAvailable { 75 | return nil 76 | } 77 | let item = NSMenuItem(title: action.title, action: #selector(actionMenuItemClicked(_:)), keyEquivalent: "") 78 | item.representedObject = action 79 | item.image = action.icon 80 | item.target = self 81 | return item 82 | } 83 | 84 | @objc private func actionMenuItemClicked(_ sender: NSMenuItem) { 85 | (sender.representedObject as? ApplicationActionable)?.perform() 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /OpenSim/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SimPholders 4 | // 5 | // Created by Luo Sheng on 11/9/15. 6 | // Copyright © 2015 Luo Sheng. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate, MenuManagerDelegate { 13 | 14 | @IBOutlet weak var window: NSWindow! 15 | 16 | var menuManager: MenuManager! 17 | 18 | func applicationDidFinishLaunching(_ aNotification: Notification) { 19 | menuManager = MenuManager() 20 | menuManager.delegate = self 21 | menuManager.start() 22 | } 23 | 24 | func applicationWillTerminate(_ aNotification: Notification) { 25 | // Insert code here to tear down your application 26 | } 27 | 28 | func shouldQuitApp() { 29 | NSApplication.shared.terminate(self) 30 | } 31 | 32 | } 33 | 34 | -------------------------------------------------------------------------------- /OpenSim/AppInfoView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppInfoView.swift 3 | // OpenSim 4 | // 5 | // Created by Luo Sheng on 07/05/2017. 6 | // Copyright © 2017 Luo Sheng. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | final class AppInfoView: NSView { 12 | 13 | var application: Application 14 | var textField: NSTextField! 15 | 16 | static let width: CGFloat = 250 17 | static let edgeInsets = NSEdgeInsets(top: 0, left: 20, bottom: 5, right: 0) 18 | static let leftMargin: CGFloat = 20 19 | 20 | init(application: Application) { 21 | self.application = application 22 | super.init(frame: NSRect.zero) 23 | 24 | setupViews() 25 | update() 26 | 27 | let size = textField.sizeThatFits(NSSize(width: CGFloat.infinity, height: CGFloat.infinity)) 28 | textField.frame = NSRect(x: AppInfoView.leftMargin, y: AppInfoView.edgeInsets.bottom, width: AppInfoView.width - AppInfoView.edgeInsets.left, height: size.height) 29 | frame = NSRect(x: 0, y: 0, width: AppInfoView.width, height: size.height + AppInfoView.edgeInsets.bottom) 30 | 31 | application.calcSize { [weak self] _ in 32 | DispatchQueue.main.async { 33 | self?.update() 34 | } 35 | } 36 | } 37 | 38 | required init?(coder: NSCoder) { 39 | fatalError("init(coder:) has not been implemented") 40 | } 41 | 42 | private func setupViews() { 43 | textField = NSTextField(frame: NSRect(x: 20, y: 0, width: 230, height: 100)) 44 | textField.isBezeled = false 45 | textField.drawsBackground = false 46 | textField.isEditable = false 47 | textField.isSelectable = false 48 | textField.cell?.wraps = false 49 | textField.textColor = NSColor.disabledControlTextColor 50 | addSubview(textField) 51 | } 52 | 53 | private func update() { 54 | var sizeDescription = "---" 55 | if let size = application.size { 56 | sizeDescription = ByteCountFormatter.string(fromByteCount: Int64(size), countStyle: .file) 57 | } 58 | let string = "\(application.bundleID)\n" + 59 | "\(UIConstants.strings.appInfoVersion): \(application.bundleVersion) (\(application.bundleShortVersion))\n" + 60 | "\(UIConstants.strings.appInfoSize): \(sizeDescription)" 61 | textField.stringValue = string 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /OpenSim/AppMenuItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppMenuItem.swift 3 | // OpenSim 4 | // 5 | // Created by Luo Sheng on 07/05/2017. 6 | // Copyright © 2017 Luo Sheng. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class AppMenuItem: NSMenuItem { 12 | 13 | private weak var application: Application! 14 | 15 | init(application: Application) { 16 | self.application = application 17 | let title = " \(application.bundleDisplayName)" 18 | super.init(title: title, action: nil, keyEquivalent: "") 19 | 20 | // Reverse the array to get the higher quality images first 21 | for iconFile in application.iconFiles.reversed() { 22 | if let bundle = Bundle(url: application.url) { 23 | self.image = bundle.image(forResource: NSImage.Name(rawValue: iconFile))?.appIcon() 24 | if self.image != nil { 25 | return 26 | } 27 | } 28 | } 29 | 30 | // Default image 31 | self.image = #imageLiteral(resourceName: "DefaultAppIcon").appIcon() 32 | } 33 | 34 | required init(coder decoder: NSCoder) { 35 | fatalError("init(coder:) has not been implemented") 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /OpenSim/Application.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Application.swift 3 | // SimPholders 4 | // 5 | // Created by Luo Sheng on 11/9/15. 6 | // Copyright © 2015 Luo Sheng. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Cocoa 11 | 12 | final class Application { 13 | 14 | var device: Device! 15 | 16 | let bundleDisplayName: String 17 | let bundleID: String 18 | let bundleShortVersion: String 19 | let bundleVersion: String 20 | let url: URL 21 | var iconFiles: [String] = [] 22 | 23 | var size: UInt64? 24 | static let sizeDispatchQueue = DispatchQueue(label: "com.pop-tap.size", attributes: .concurrent, target: nil) 25 | 26 | var sandboxUrl: URL? { 27 | guard let url = device.containerURLForApplication(self), 28 | FileManager.default.fileExists(atPath: url.path) 29 | else { 30 | return nil 31 | } 32 | return url 33 | } 34 | 35 | init?(device: Device, url: Foundation.URL) { 36 | self.device = device 37 | guard let contents = try? FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: [.skipsSubdirectoryDescendants, .skipsHiddenFiles]), 38 | let url = contents.filter({ $0.absoluteString.hasSuffix(".app/") }).first // url ".app" diretory 39 | else { 40 | return nil 41 | } 42 | 43 | let appInfoPath = url.appendingPathComponent("Info.plist") 44 | 45 | guard let appInfoDict = NSDictionary(contentsOf: appInfoPath), 46 | let aBundleID = appInfoDict["CFBundleIdentifier"] as? String, 47 | let aBundleDisplayName = (appInfoDict["CFBundleDisplayName"] as? String) ?? (appInfoDict["CFBundleName"] as? String), 48 | let aBundleShortVersion = appInfoDict["CFBundleShortVersionString"] as? String, 49 | let aBundleVersion = appInfoDict["CFBundleVersion"] as? String else { 50 | return nil 51 | } 52 | 53 | self.url = url 54 | 55 | bundleDisplayName = aBundleDisplayName 56 | bundleID = aBundleID 57 | bundleShortVersion = aBundleShortVersion 58 | bundleVersion = aBundleVersion 59 | 60 | iconFiles = [] 61 | 62 | // iPhone icons 63 | if let bundleIcons = appInfoDict["CFBundleIcons"] as? NSDictionary { 64 | if let bundlePrimaryIcon = bundleIcons["CFBundlePrimaryIcon"] as? NSDictionary { 65 | if let bundleIconFiles = bundlePrimaryIcon["CFBundleIconFiles"] as? [String] { 66 | for iconFile in bundleIconFiles { 67 | iconFiles.append(iconFile) 68 | iconFiles.append(iconFile.appending("@2x")) 69 | } 70 | } 71 | } 72 | } 73 | 74 | // iPad icons 75 | if let bundleIcons = appInfoDict["CFBundleIcons~ipad"] as? NSDictionary { 76 | if let bundlePrimaryIcon = bundleIcons["CFBundlePrimaryIcon"] as? NSDictionary { 77 | if let bundleIconFiles = bundlePrimaryIcon["CFBundleIconFiles"] as? [String] { 78 | for iconFile in bundleIconFiles { 79 | iconFiles.append(iconFile.appending("~ipad")) 80 | iconFiles.append(iconFile.appending("@2x~ipad")) 81 | } 82 | } 83 | } 84 | } 85 | } 86 | 87 | func calcSize(block: @escaping (UInt64) -> Void) { 88 | if let size = size { 89 | block(size) 90 | } else { 91 | Application.sizeDispatchQueue.async { 92 | let duResult = shell("/usr/bin/du", arguments: ["-sk", self.url.path]) 93 | let stringBytes = String(duResult.split(separator: "\t").first ?? "") 94 | var bytes: UInt64 = 0 95 | if let kbytes = UInt64(stringBytes) { 96 | bytes = kbytes * 1000 97 | self.size = bytes; 98 | } 99 | block(bytes) 100 | } 101 | } 102 | } 103 | 104 | func launch() { 105 | if device.state != .booted { 106 | SimulatorController.boot(self) 107 | } 108 | SimulatorController.run(self) 109 | SimulatorController.launch(self) 110 | } 111 | 112 | func uninstall() { 113 | if device.state != .booted { 114 | SimulatorController.boot(self) 115 | } 116 | SimulatorController.uninstall(self) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /OpenSim/ApplicationActionable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ApplicationActionable.swift 3 | // OpenSim 4 | // 5 | // Created by Luo Sheng on 07/05/2017. 6 | // Copyright © 2017 Luo Sheng. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | protocol ApplicationActionable { 12 | 13 | init(application: Application) 14 | 15 | var application: Application? { get set } 16 | 17 | var title: String { get } 18 | 19 | var icon: NSImage? { get } 20 | 21 | var isAvailable: Bool { get } 22 | 23 | func perform() 24 | 25 | } 26 | -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "industry-electro-devices-icon-16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "industry-electro-devices-icon-32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "industry-electro-devices-icon-32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "industry-electro-devices-icon-64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "industry-electro-devices-icon-128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "industry-electro-devices-icon-256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "industry-electro-devices-icon-256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "industry-electro-devices-icon-512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "industry-electro-devices-icon-512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "industry-electro-devices-icon-1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/AppIcon.appiconset/industry-electro-devices-icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luosheng/OpenSim/6cf596c7dc1b1050f03905107926a61110f32d8f/OpenSim/Assets.xcassets/AppIcon.appiconset/industry-electro-devices-icon-1024.png -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/AppIcon.appiconset/industry-electro-devices-icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luosheng/OpenSim/6cf596c7dc1b1050f03905107926a61110f32d8f/OpenSim/Assets.xcassets/AppIcon.appiconset/industry-electro-devices-icon-128.png -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/AppIcon.appiconset/industry-electro-devices-icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luosheng/OpenSim/6cf596c7dc1b1050f03905107926a61110f32d8f/OpenSim/Assets.xcassets/AppIcon.appiconset/industry-electro-devices-icon-16.png -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/AppIcon.appiconset/industry-electro-devices-icon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luosheng/OpenSim/6cf596c7dc1b1050f03905107926a61110f32d8f/OpenSim/Assets.xcassets/AppIcon.appiconset/industry-electro-devices-icon-256.png -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/AppIcon.appiconset/industry-electro-devices-icon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luosheng/OpenSim/6cf596c7dc1b1050f03905107926a61110f32d8f/OpenSim/Assets.xcassets/AppIcon.appiconset/industry-electro-devices-icon-32.png -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/AppIcon.appiconset/industry-electro-devices-icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luosheng/OpenSim/6cf596c7dc1b1050f03905107926a61110f32d8f/OpenSim/Assets.xcassets/AppIcon.appiconset/industry-electro-devices-icon-512.png -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/AppIcon.appiconset/industry-electro-devices-icon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luosheng/OpenSim/6cf596c7dc1b1050f03905107926a61110f32d8f/OpenSim/Assets.xcassets/AppIcon.appiconset/industry-electro-devices-icon-64.png -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/DefaultAppIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-60.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-60@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "icon-60@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/DefaultAppIcon.imageset/icon-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luosheng/OpenSim/6cf596c7dc1b1050f03905107926a61110f32d8f/OpenSim/Assets.xcassets/DefaultAppIcon.imageset/icon-60.png -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/DefaultAppIcon.imageset/icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luosheng/OpenSim/6cf596c7dc1b1050f03905107926a61110f32d8f/OpenSim/Assets.xcassets/DefaultAppIcon.imageset/icon-60@2x.png -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/DefaultAppIcon.imageset/icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luosheng/OpenSim/6cf596c7dc1b1050f03905107926a61110f32d8f/OpenSim/Assets.xcassets/DefaultAppIcon.imageset/icon-60@3x.png -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/active.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "active.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "active@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "active@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/active.imageset/active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luosheng/OpenSim/6cf596c7dc1b1050f03905107926a61110f32d8f/OpenSim/Assets.xcassets/active.imageset/active.png -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/active.imageset/active@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luosheng/OpenSim/6cf596c7dc1b1050f03905107926a61110f32d8f/OpenSim/Assets.xcassets/active.imageset/active@2x.png -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/active.imageset/active@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luosheng/OpenSim/6cf596c7dc1b1050f03905107926a61110f32d8f/OpenSim/Assets.xcassets/active.imageset/active@3x.png -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/inactive.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "inactive.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "inactive@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "inactive@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/inactive.imageset/inactive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luosheng/OpenSim/6cf596c7dc1b1050f03905107926a61110f32d8f/OpenSim/Assets.xcassets/inactive.imageset/inactive.png -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/inactive.imageset/inactive@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luosheng/OpenSim/6cf596c7dc1b1050f03905107926a61110f32d8f/OpenSim/Assets.xcassets/inactive.imageset/inactive@2x.png -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/inactive.imageset/inactive@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luosheng/OpenSim/6cf596c7dc1b1050f03905107926a61110f32d8f/OpenSim/Assets.xcassets/inactive.imageset/inactive@3x.png -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/launch.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "launch.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "launch@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/launch.imageset/launch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luosheng/OpenSim/6cf596c7dc1b1050f03905107926a61110f32d8f/OpenSim/Assets.xcassets/launch.imageset/launch.png -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/launch.imageset/launch@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luosheng/OpenSim/6cf596c7dc1b1050f03905107926a61110f32d8f/OpenSim/Assets.xcassets/launch.imageset/launch@2x.png -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/menubar.imageset/1447255133_electro_devices-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luosheng/OpenSim/6cf596c7dc1b1050f03905107926a61110f32d8f/OpenSim/Assets.xcassets/menubar.imageset/1447255133_electro_devices-1.png -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/menubar.imageset/1447255133_electro_devices.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luosheng/OpenSim/6cf596c7dc1b1050f03905107926a61110f32d8f/OpenSim/Assets.xcassets/menubar.imageset/1447255133_electro_devices.png -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/menubar.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "1447255133_electro_devices-1.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "1447255133_electro_devices.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/reveal.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "reveal.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "reveal@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/reveal.imageset/reveal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luosheng/OpenSim/6cf596c7dc1b1050f03905107926a61110f32d8f/OpenSim/Assets.xcassets/reveal.imageset/reveal.png -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/reveal.imageset/reveal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luosheng/OpenSim/6cf596c7dc1b1050f03905107926a61110f32d8f/OpenSim/Assets.xcassets/reveal.imageset/reveal@2x.png -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/share.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "share.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "share@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/share.imageset/share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luosheng/OpenSim/6cf596c7dc1b1050f03905107926a61110f32d8f/OpenSim/Assets.xcassets/share.imageset/share.png -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/share.imageset/share@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luosheng/OpenSim/6cf596c7dc1b1050f03905107926a61110f32d8f/OpenSim/Assets.xcassets/share.imageset/share@2x.png -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/terminal.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "term.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "term@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/terminal.imageset/term.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luosheng/OpenSim/6cf596c7dc1b1050f03905107926a61110f32d8f/OpenSim/Assets.xcassets/terminal.imageset/term.png -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/terminal.imageset/term@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luosheng/OpenSim/6cf596c7dc1b1050f03905107926a61110f32d8f/OpenSim/Assets.xcassets/terminal.imageset/term@2x.png -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/uninstall.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "uninstall.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "uninstall@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/uninstall.imageset/uninstall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luosheng/OpenSim/6cf596c7dc1b1050f03905107926a61110f32d8f/OpenSim/Assets.xcassets/uninstall.imageset/uninstall.png -------------------------------------------------------------------------------- /OpenSim/Assets.xcassets/uninstall.imageset/uninstall@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luosheng/OpenSim/6cf596c7dc1b1050f03905107926a61110f32d8f/OpenSim/Assets.xcassets/uninstall.imageset/uninstall@2x.png -------------------------------------------------------------------------------- /OpenSim/Base.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "Menu.Quit" = "Quit"; 2 | "Menu.LaunchAtLogin" = "Launch at Login"; 3 | "Menu.FocusedMode" = "Focused Mode"; 4 | "Menu.Refresh" = "Refresh"; 5 | "Menu.Version" = "Version"; 6 | "Menu.Launch" = "Launch"; 7 | "Menu.Shutdown" = "Shutdown"; 8 | "Menu.FactoryReset" = "Factory Reset"; 9 | "Menu.FactoryResetAll" = "Factory Reset ALL Simulators"; 10 | "Menu.DeleteSimulator" = "Delete Simulator"; 11 | "Menu.FactoryResetOnlyShutdown" = "Factory Reset Only Shutdown Simulators"; 12 | "Action.RevealInFinder" = "Reveal Sandbox in Finder"; 13 | "Action.CopyPathPasteboard" = "Copy Sandbox Path to Pasteboard"; 14 | "Action.OpenInTerminal" = "Open Sandbox in Terminal"; 15 | "Action.Launch" = "Launch App"; 16 | "Action.Uninstall" = "Uninstall"; 17 | "Action.UninstallAlertConfirmButton" = "Uninstall"; 18 | "Action.UninstallAlertCancelButton" = "Cancel"; 19 | "Action.UninstallAlertMessage" = "Are you sure you want to uninstall %@ from %@?"; 20 | "Action.FactoryResetAlertConfirmButton" = "Factory Reset"; 21 | "Action.FactoryResetAlertCancelButton" = "Cancel"; 22 | "Action.FactoryResetAlertMessage" = "Are you sure you want to Factory Reset the %@? This will remove all apps and data."; 23 | "Action.FactoryResetAllSimulatorsMessage" = "Are you sure you want to Factory Reset All Simulators? This will remove all apps and data."; 24 | "Action.FactoryResetAllShutdownSimulatorsMessage" = "Are you sure you want to Factory Reset All Shutdown Simulators? This will remove all apps and data. This will not erase any simulators currently booted."; 25 | "Action.EraseSimulatorAlertConfirmButton" = "Erase Simulator"; 26 | "Action.EraseSimulatorAlertCancelButton" = "Cancel"; 27 | "Action.EraseSimulatorAlertMessage" = "Are you sure you want to erase the %@ Simulator? This will remove the simulator from your system, but a new simulator can still be created."; 28 | "Extension.OpenInIterm" = "Open Sandbox in iTerm"; 29 | "Extension.OpenRealmDatabase" = "Open Realm Database"; 30 | "AppInfo.Version" = "Version"; 31 | "AppInfo.Size" = "Size"; 32 | "MenuHeader.Actions" = "Actions"; 33 | "MenuHeader.Extensions" = "Extensions"; 34 | "MenuHeader.AppInformation" = "App Information"; 35 | -------------------------------------------------------------------------------- /OpenSim/Base.lproj/MainMenu.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | Default 536 | 537 | 538 | 539 | 540 | 541 | 542 | Left to Right 543 | 544 | 545 | 546 | 547 | 548 | 549 | Right to Left 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | Default 561 | 562 | 563 | 564 | 565 | 566 | 567 | Left to Right 568 | 569 | 570 | 571 | 572 | 573 | 574 | Right to Left 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | -------------------------------------------------------------------------------- /OpenSim/CancelBlocks.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CancelBlocks.swift 3 | // Smooth 4 | // 5 | // Created by Evgenii Rtishchev on 16/02/15. 6 | // Copyright (c) 2015 Evgenii Rtishchev. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | typealias dispatch_cancelable_block_t = (_ cancelled: Bool) -> () 12 | 13 | func dispatch_block_t(_ delay: TimeInterval, block: @escaping () -> Void) -> dispatch_cancelable_block_t { 14 | var cancelableBlock: dispatch_cancelable_block_t? = nil 15 | let delayBlock: dispatch_cancelable_block_t = { (cancelled: Bool) in 16 | if (!cancelled) { 17 | DispatchQueue.main.async(execute: block) 18 | } 19 | cancelableBlock = nil 20 | } 21 | cancelableBlock = delayBlock 22 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)) { 23 | if let cancelableBlock = cancelableBlock { 24 | cancelableBlock(false) 25 | } 26 | } 27 | return delayBlock 28 | } 29 | 30 | func dispatch_cancel_block_t(_ block: dispatch_cancelable_block_t?) { 31 | if let block = block { 32 | block(true) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /OpenSim/Constants.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | struct UIConstants { 5 | 6 | struct strings { 7 | static let menuQuitButton = NSLocalizedString("Menu.Quit", comment: "Quit menu button") 8 | static let menuLaunchAtLoginButton = NSLocalizedString("Menu.LaunchAtLogin", comment: "Launch at login menu button") 9 | static let menuRefreshButton = NSLocalizedString("Menu.Refresh", comment: "Refresh menu button") 10 | static let menuFocusedModeButton = NSLocalizedString("Menu.FocusedMode", comment: "Focused Mode menu button") 11 | static let menuVersionLabel = NSLocalizedString("Menu.Version", comment: "Version label") 12 | static let menuLaunchSimulatorButton = NSLocalizedString("Menu.Launch", comment: "Launch") 13 | static let menuShutdownSimulatorButton = NSLocalizedString("Menu.Shutdown", comment: "Shutdown") 14 | static let menuDeleteSimulatorButton = NSLocalizedString("Menu.DeleteSimulator", comment: "Delete") 15 | static let menuResetSimulatorButton = NSLocalizedString("Menu.FactoryReset", comment: "Factory Reset") 16 | static let menuShutDownAllSimulators = NSLocalizedString("Menu.FactoryResetAll", comment: "Factory Reset All Simulators") 17 | static let menuShutDownAllBootedSimulators = NSLocalizedString("Menu.FactoryResetOnlyShutdown", comment: "Factory Reset Only Shutdown Simulators") 18 | static let actionRevealInFinder = NSLocalizedString("Action.RevealInFinder", comment: "Reveal in Finder label") 19 | static let actionCopyPathPasteboard = NSLocalizedString("Action.CopyPathPasteboard", comment: "Copy Sandbox path to pasteboard label") 20 | static let actionOpenInTerminal = NSLocalizedString("Action.OpenInTerminal", comment: "Open in Terminal label") 21 | static let actionLaunch = NSLocalizedString("Action.Launch", comment: "Launch label") 22 | static let actionUninstall = NSLocalizedString("Action.Uninstall", comment: "Uninstall label") 23 | static let actionUninstallAlertConfirmButton = NSLocalizedString("Action.UninstallAlertConfirmButton", comment: "Uninstall confirm button") 24 | static let actionUninstallAlertCancelButton = NSLocalizedString("Action.UninstallAlertCancelButton", comment: "Uninstall cancel button") 25 | static let actionUninstallAlertMessage = NSLocalizedString("Action.UninstallAlertMessage", comment: "Uninstall confirmation message") 26 | static let actionDeleteSimulatorAlertConfirmButton = NSLocalizedString("Action.EraseSimulatorAlertConfirmButton", comment: "Delete Simulator") 27 | static let actionDeleteSimulatorAlertCancelButton = NSLocalizedString("Action.EraseSimulatorAlertCancelButton", comment: "Cancel") 28 | static let actionDeleteSimulatorAlertMessage = NSLocalizedString("Action.EraseSimulatorAlertMessage", comment: "Delete Simulator confirmation message") 29 | static let actionFactoryResetAlertConfirmButton = NSLocalizedString("Action.FactoryResetAlertConfirmButton", comment: "Factory Reset") 30 | static let actionFactoryResetAlertCancelButton = NSLocalizedString("Action.FactoryResetAlertCancelButton", comment: "Cancel") 31 | static let actionFactoryResetAlertMessage = NSLocalizedString("Action.FactoryResetAlertMessage", comment: "Factory reset confirmation message") 32 | static let actionFactoryResetAllSimulatorsMessage = NSLocalizedString("Action.FactoryResetAllSimulatorsMessage", comment: "Factory Reset All Simulators") 33 | static let actionFactoryResetAllShutdownSimulatorsMessage = NSLocalizedString("Action.FactoryResetAllShutdownSimulatorsMessage", comment: "Factory Reset All Shutdown Simulators") 34 | static let extensionOpenInIterm = NSLocalizedString("Extension.OpenInIterm", comment: "Open in iTerm label") 35 | static let extensionOpenRealmDatabase = NSLocalizedString("Extension.OpenRealmDatabase", comment: "Open Realm Database label") 36 | static let appInfoVersion = NSLocalizedString("AppInfo.Version", comment: "App Info Version Label") 37 | static let appInfoSize = NSLocalizedString("AppInfo.Size", comment: "App Info Size Label") 38 | static let menuHeaderActions = NSLocalizedString("MenuHeader.Actions", comment: "Actions header label") 39 | static let menuHeaderExtensions = NSLocalizedString("MenuHeader.Extensions", comment: "Extensions header label") 40 | static let menuHeaderAppInformation = NSLocalizedString("MenuHeader.AppInformation", comment: "App Information header label") 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /OpenSim/CopyToPasteboardAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CopyToPasteboard.swift 3 | // OpenSim 4 | // 5 | // Created by Luo Sheng on 07/05/2017. 6 | // Copyright © 2017 Luo Sheng. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | final class CopyToPasteboardAction: ApplicationActionable { 12 | 13 | var application: Application? 14 | 15 | let title = UIConstants.strings.actionCopyPathPasteboard 16 | 17 | let icon = templatize(#imageLiteral(resourceName: "share")) 18 | 19 | let isAvailable: Bool = true 20 | 21 | init(application: Application) { 22 | self.application = application 23 | } 24 | 25 | func perform() { 26 | if let url = application?.sandboxUrl { 27 | let pasteboard = NSPasteboard.general 28 | pasteboard.declareTypes([NSPasteboard.PasteboardType.string], owner: nil) 29 | pasteboard.setString(url.path, forType: NSPasteboard.PasteboardType.string) 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /OpenSim/Device.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Device.swift 3 | // SimPholders 4 | // 5 | // Created by Luo Sheng on 11/9/15. 6 | // Copyright © 2015 Luo Sheng. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum State { 12 | case shutdown 13 | case booted 14 | case unknown 15 | } 16 | 17 | struct Device { 18 | private let stateValue: String 19 | public let name: String 20 | public let UDID: String 21 | } 22 | 23 | extension Device { 24 | public var applications: [Application]? { 25 | let applicationPath = URLHelper.deviceURLForUDID(self.UDID).appendingPathComponent("data/Containers/Bundle/Application") 26 | let contents = try? FileManager.default.contentsOfDirectory(at: applicationPath, includingPropertiesForKeys: [.isDirectoryKey], options: [.skipsSubdirectoryDescendants, .skipsHiddenFiles]) 27 | return contents? 28 | .filter({ (url) -> Bool in 29 | var isDirectoryObj: AnyObject? 30 | try? (url as NSURL).getResourceValue(&isDirectoryObj, forKey: URLResourceKey.isDirectoryKey) 31 | guard let isDirectory = isDirectoryObj as? Bool else { 32 | return false 33 | } 34 | return isDirectory 35 | }) 36 | .map { Application(device: self, url: $0) } 37 | .filter { $0 != nil } 38 | .map { $0! } 39 | } 40 | 41 | public var state: State { 42 | switch stateValue { 43 | case "Booted": 44 | return .booted 45 | case "Shutdown": 46 | return .shutdown 47 | default: 48 | return .unknown 49 | } 50 | } 51 | 52 | public func containerURLForApplication(_ application: Application) -> URL? { 53 | let URL = URLHelper.containersURLForUDID(UDID) 54 | let directories = try? FileManager.default.contentsOfDirectory(at: URL, includingPropertiesForKeys: nil, options: .skipsSubdirectoryDescendants) 55 | return directories?.filter({ (dir) -> Bool in 56 | if let contents = NSDictionary(contentsOf: dir.appendingPathComponent(".com.apple.mobile_container_manager.metadata.plist")), 57 | let identifier = contents["MCMMetadataIdentifier"] as? String, identifier == application.bundleID { 58 | return true 59 | } 60 | return false 61 | }).first 62 | } 63 | 64 | func launch() { 65 | if state != .booted { 66 | SimulatorController.boot(self) 67 | } 68 | SimulatorController.open() 69 | } 70 | 71 | func shutDown() { 72 | if state == .booted { 73 | SimulatorController.shutdown(self) 74 | } 75 | } 76 | 77 | func factoryReset() { 78 | if state != .shutdown { 79 | SimulatorController.shutdown(self) 80 | } 81 | SimulatorController.factoryReset(self) 82 | } 83 | } 84 | 85 | extension Device: Decodable { 86 | enum CodingKeys: String, CodingKey { 87 | case UDID = "udid" 88 | case name 89 | case stateValue = "state" 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /OpenSim/DeviceManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeviceMapping.swift 3 | // SimPholders 4 | // 5 | // Created by Luo Sheng on 11/9/15. 6 | // Copyright © 2015 Luo Sheng. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | final class DeviceManager { 12 | 13 | static let devicesKey = "DefaultDevices" 14 | static let deviceRuntimePrefix = "com.apple.CoreSimulator.SimRuntime" 15 | 16 | static let defaultManager = DeviceManager() 17 | 18 | var runtimes = [Runtime]() 19 | 20 | func reload(callback: @escaping ([Runtime]) -> ()) { 21 | SimulatorController.listDevices { (runtimes) in 22 | self.runtimes = runtimes 23 | callback(runtimes) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /OpenSim/DirectoryWatcher.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DirectoryWatcher.swift 3 | // Markdown 4 | // 5 | // Created by Luo Sheng on 15/10/31. 6 | // Copyright © 2015年 Pop Tap. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class DirectoryWatcher { 12 | 13 | enum IOError: Error { 14 | case cannotOpenPath 15 | } 16 | 17 | public typealias CompletionCallback = () -> () 18 | 19 | var watchedURL: URL 20 | let eventMask: DispatchSource.FileSystemEvent 21 | public var completionCallback: CompletionCallback? 22 | private var source: DispatchSourceFileSystemObject? 23 | private var directoryChanging = false 24 | private var oldDirectoryInfo = [FileInfo?]() 25 | 26 | init(in watchedURL: URL, eventMask: DispatchSource.FileSystemEvent = .write) { 27 | self.watchedURL = watchedURL 28 | self.eventMask = eventMask 29 | } 30 | 31 | deinit { 32 | self.stop() 33 | } 34 | 35 | public func start() throws { 36 | guard source == nil else { 37 | return 38 | } 39 | 40 | let path = watchedURL.path 41 | 42 | let fd = open((path as NSString).fileSystemRepresentation, O_EVTONLY) 43 | guard fd >= 0 else { 44 | throw IOError.cannotOpenPath 45 | } 46 | 47 | source = DispatchSource.makeFileSystemObjectSource(fileDescriptor: fd, eventMask: eventMask) 48 | source?.setEventHandler { [weak self] in 49 | self?.waitForDirectoryToFinishChanging() 50 | } 51 | 52 | source?.setCancelHandler { 53 | close(fd) 54 | } 55 | 56 | source?.resume() 57 | } 58 | 59 | public func stop() { 60 | source?.cancel() 61 | source = nil 62 | } 63 | 64 | private func waitForDirectoryToFinishChanging() { 65 | if (!directoryChanging) { 66 | directoryChanging = true 67 | 68 | oldDirectoryInfo = self.directoryInfo() 69 | 70 | let timer = Timer(timeInterval: 0.5, target: self, selector: #selector(checkDirectoryInfo(_:)), userInfo: nil, repeats: true) 71 | RunLoop.main.add(timer, forMode: RunLoopMode.commonModes) 72 | } 73 | } 74 | 75 | private func directoryInfo() -> [FileInfo?] { 76 | let contents = try? FileManager.default.contentsOfDirectory(at: watchedURL, includingPropertiesForKeys: FileInfo.prefetchedProperties, options: .skipsSubdirectoryDescendants) 77 | return contents?.map { FileInfo(URL: $0) } ?? [] 78 | } 79 | 80 | @objc private func checkDirectoryInfo(_ timer: Timer) { 81 | let directoryInfo = self.directoryInfo() 82 | directoryChanging = directoryInfo != oldDirectoryInfo 83 | if directoryChanging { 84 | oldDirectoryInfo = directoryInfo 85 | } else { 86 | timer.invalidate() 87 | if let completion = completionCallback { 88 | completion() 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /OpenSim/ExtraApplicationActionable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExtraApplicationActionable.swift 3 | // OpenSim 4 | // 5 | // Created by Luo Sheng on 07/05/2017. 6 | // Copyright © 2017 Luo Sheng. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | protocol ExtraApplicationActionable: ApplicationActionable { 12 | 13 | var appBundleIdentifier: String { get } 14 | 15 | } 16 | 17 | extension ExtraApplicationActionable { 18 | 19 | var appPath: String? { 20 | return NSWorkspace.shared.absolutePathForApplication(withBundleIdentifier: appBundleIdentifier) 21 | } 22 | 23 | var icon: NSImage? { 24 | return appPath.flatMap { (path) -> NSImage? in 25 | let image = NSWorkspace.shared.icon(forFile: path) 26 | image.size = NSSize(width: 16, height: 16) 27 | return image 28 | } 29 | } 30 | 31 | var isAvailable: Bool { 32 | return appPath != nil 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /OpenSim/FileInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileInfo.swift 3 | // Markdown 4 | // 5 | // Created by Luo Sheng on 15/11/1. 6 | // Copyright © 2015年 Pop Tap. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct FileInfo { 12 | 13 | static let prefetchedProperties: [URLResourceKey] = [ 14 | .nameKey, 15 | .isDirectoryKey, 16 | .creationDateKey, 17 | .contentModificationDateKey, 18 | .fileSizeKey, 19 | ] 20 | 21 | private enum FileInfoError: Error { 22 | case invalidProperty 23 | } 24 | 25 | let name: String 26 | let isDirectory: Bool 27 | let creationDate: Date 28 | let modificationDate: Date 29 | let fileSize: Int 30 | 31 | init?(URL: Foundation.URL) { 32 | var nameObj: AnyObject? 33 | try? (URL as NSURL).getResourceValue(&nameObj, forKey: URLResourceKey.nameKey) 34 | 35 | var isDirectoryObj: AnyObject? 36 | try? (URL as NSURL).getResourceValue(&isDirectoryObj, forKey: URLResourceKey.isDirectoryKey) 37 | 38 | var creationDateObj: AnyObject? 39 | try? (URL as NSURL).getResourceValue(&creationDateObj, forKey: URLResourceKey.creationDateKey) 40 | 41 | var modificationDateObj: AnyObject? 42 | try? (URL as NSURL).getResourceValue(&modificationDateObj, forKey: URLResourceKey.contentModificationDateKey) 43 | 44 | var fileSizeObj: AnyObject? 45 | try? (URL as NSURL).getResourceValue(&fileSizeObj, forKey: URLResourceKey.fileSizeKey) 46 | 47 | guard let name = nameObj as? String, 48 | let isDirectory = isDirectoryObj as? Bool, 49 | let creationDate = creationDateObj as? Date, 50 | let modificationDate = modificationDateObj as? Date, 51 | let fileSize = isDirectory ? 0 : fileSizeObj as? Int else { 52 | return nil 53 | } 54 | self.name = name 55 | self.isDirectory = isDirectory 56 | self.creationDate = creationDate 57 | self.modificationDate = modificationDate 58 | self.fileSize = fileSize 59 | } 60 | 61 | } 62 | 63 | extension FileInfo: Equatable {} 64 | 65 | func ==(lhs: FileInfo, rhs: FileInfo) -> Bool { 66 | return lhs.name == rhs.name && 67 | lhs.isDirectory == rhs.isDirectory && 68 | lhs.creationDate == rhs.creationDate && 69 | lhs.modificationDate == rhs.modificationDate && 70 | lhs.fileSize == rhs.fileSize 71 | } 72 | 73 | func ==(lhs: FileInfo?, rhs: FileInfo?) -> Bool { 74 | switch (lhs, rhs) { 75 | case (.some(let lhs), .some(let rhs)): 76 | return lhs == rhs 77 | case (nil, nil): 78 | // When two optionals are both nil, we consider them not equal 79 | return false 80 | default: 81 | return false 82 | } 83 | } 84 | 85 | func !=(lhs: FileInfo?, rhs: FileInfo?) -> Bool { 86 | return !(lhs == rhs) 87 | } 88 | 89 | func ==(lhs: [FileInfo?], rhs: [FileInfo?]) -> Bool { 90 | return lhs.elementsEqual(rhs) { $0 == $1 } 91 | } 92 | 93 | func !=(lhs: [FileInfo?], rhs: [FileInfo?]) -> Bool { 94 | return !(lhs == rhs) 95 | } 96 | -------------------------------------------------------------------------------- /OpenSim/Helper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Helper.swift 3 | // OpenSim 4 | // 5 | // Created by Bradley Van Dyk on 3/4/16. 6 | // Copyright © 2016 Luo Sheng. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | func shell(_ launchPath: String, arguments: [String]) -> String { 12 | let progress = Process() 13 | progress.launchPath = launchPath 14 | progress.arguments = arguments 15 | 16 | let pipe = Pipe() 17 | progress.standardOutput = pipe 18 | progress.standardError = Pipe() 19 | progress.launch() 20 | 21 | let data = pipe.fileHandleForReading.readDataToEndOfFile() 22 | let output = String(data: data, encoding: String.Encoding.utf8) 23 | 24 | return output ?? "" 25 | } 26 | -------------------------------------------------------------------------------- /OpenSim/ImageExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageExtension.swift 3 | // OpenSim 4 | // 5 | // Created by Luo Sheng on 6/22/16. 6 | // Copyright © 2016 Luo Sheng. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Cocoa 11 | 12 | struct IconImageConstants { 13 | static let size = NSSize(width: 32, height: 32) 14 | static let cornerRadius: CGFloat = 5 15 | } 16 | 17 | func templatize(_ image: NSImage) -> NSImage? { 18 | image.isTemplate = true 19 | return image 20 | } 21 | 22 | extension NSImage { 23 | 24 | func appIcon() -> NSImage? { 25 | guard self.isValid else { 26 | return nil 27 | } 28 | let newImage = NSImage(size: IconImageConstants.size) 29 | newImage.lockFocus() 30 | self.size = IconImageConstants.size 31 | NSGraphicsContext.current?.imageInterpolation = .high 32 | 33 | NSGraphicsContext.saveGraphicsState() 34 | let path = NSBezierPath(roundedRect: NSRect(origin: NSPoint.zero, size: size), xRadius: IconImageConstants.cornerRadius, yRadius: IconImageConstants.cornerRadius) 35 | path.addClip() 36 | self.draw(at: NSPoint.zero, from: NSRect(origin: NSPoint.zero, size: size), operation: .copy, fraction: 1.0) 37 | NSGraphicsContext.restoreGraphicsState() 38 | 39 | newImage.unlockFocus() 40 | return newImage 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /OpenSim/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 0.4.3 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | LSUIElement 28 | 29 | NSHumanReadableCopyright 30 | Copyright © 2015 Luo Sheng. All rights reserved. 31 | NSMainNibFile 32 | MainMenu 33 | NSPrincipalClass 34 | NSApplication 35 | 36 | 37 | -------------------------------------------------------------------------------- /OpenSim/LaunchAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LaunchAction.swift 3 | // OpenSim 4 | // 5 | // Created by Arthur da Paz on 30/04/19. 6 | // Copyright © 2019 Luo Sheng. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | final class LaunchAction: ApplicationActionable { 12 | var application: Application? 13 | 14 | let title = UIConstants.strings.actionLaunch 15 | 16 | let icon = templatize(#imageLiteral(resourceName: "launch")) 17 | 18 | let isAvailable = true 19 | 20 | init(application: Application) { 21 | self.application = application 22 | } 23 | 24 | func perform() { 25 | guard let application = application else { 26 | return 27 | } 28 | application.launch() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /OpenSim/LaunchAtLoginHelper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LaunchAtLoginHelper.swift 3 | // OpenSim 4 | // 5 | // Created by Luo Sheng on 07/05/2017. 6 | // Copyright © 2017 Luo Sheng. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | func getLoginItems() -> LSSharedFileList? { 12 | let allocator = CFAllocatorGetDefault().takeRetainedValue() 13 | let kLoginItems = kLSSharedFileListSessionLoginItems.takeUnretainedValue() 14 | guard let loginItems = LSSharedFileListCreate(allocator, kLoginItems, nil) else { 15 | return nil 16 | } 17 | return loginItems.takeRetainedValue() 18 | } 19 | 20 | func existingItem(itemUrl: URL) -> LSSharedFileListItem? { 21 | guard let loginItems = getLoginItems() else { 22 | return nil 23 | } 24 | 25 | var seed: UInt32 = 0 26 | if let currentItems = LSSharedFileListCopySnapshot(loginItems, &seed)?.takeRetainedValue() as? [LSSharedFileListItem] { 27 | for item in currentItems { 28 | let resolutionFlags = UInt32(kLSSharedFileListNoUserInteraction | kLSSharedFileListDoNotMountVolumes) 29 | if let cfurl = LSSharedFileListItemCopyResolvedURL(item, resolutionFlags, nil) { 30 | let url = cfurl.takeRetainedValue() as URL 31 | if itemUrl == url { 32 | return item 33 | } 34 | } 35 | 36 | } 37 | } 38 | return nil 39 | } 40 | 41 | func setLaunchAtLogin(itemUrl: URL, enabled: Bool) { 42 | guard let loginItems = getLoginItems() else { 43 | return 44 | } 45 | if let item = existingItem(itemUrl: itemUrl) { 46 | if (!enabled) { 47 | LSSharedFileListItemRemove(loginItems, item) 48 | } 49 | } else { 50 | if (enabled) { 51 | LSSharedFileListInsertItemURL(loginItems, kLSSharedFileListItemBeforeFirst.takeUnretainedValue(), nil, nil, itemUrl as CFURL, nil, nil) 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /OpenSim/MenuManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MenuManager.swift 3 | // OpenSim 4 | // 5 | // Created by Luo Sheng on 16/3/24. 6 | // Copyright © 2016年 Luo Sheng. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Cocoa 11 | 12 | protocol MenuManagerDelegate { 13 | func shouldQuitApp() 14 | } 15 | 16 | @objc final class MenuManager: NSObject, NSMenuDelegate { 17 | 18 | let statusItem: NSStatusItem 19 | var focusedMode: Bool = true 20 | 21 | var watcher: DirectoryWatcher! 22 | 23 | var subWatchers: [DirectoryWatcher?]? 24 | 25 | var block: dispatch_cancelable_block_t? 26 | 27 | var delegate: MenuManagerDelegate? 28 | 29 | var menuObserver: CFRunLoopObserver? 30 | 31 | override init() { 32 | statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) 33 | statusItem.image = NSImage(named: NSImage.Name(rawValue: "menubar")) 34 | statusItem.image!.isTemplate = true 35 | 36 | super.init() 37 | 38 | buildMenu() 39 | } 40 | 41 | deinit { 42 | stop() 43 | } 44 | 45 | func start() { 46 | buildWatcher() 47 | buildSubWatchers() 48 | } 49 | 50 | func stop() { 51 | watcher.stop() 52 | subWatchers?.forEach { $0?.stop() } 53 | } 54 | 55 | private func buildMenu() { 56 | let menu = NSMenu() 57 | 58 | menu.addItem(NSMenuItem.separator()) 59 | 60 | let refreshMenuItem = menu.addItem(withTitle: UIConstants.strings.menuRefreshButton, action: #selector(self.refreshItemClicked(_:)), keyEquivalent: "r") 61 | refreshMenuItem.target = self 62 | 63 | let focusedModeMenuItem = menu.addItem(withTitle: UIConstants.strings.menuFocusedModeButton, action: #selector(self.toggleFocusedMode), keyEquivalent: "") 64 | focusedModeMenuItem.target = self 65 | focusedModeMenuItem.state = self.focusedMode ? .on : .off 66 | 67 | let launchAtLoginMenuItem = menu.addItem(withTitle: UIConstants.strings.menuLaunchAtLoginButton, action: #selector(self.launchItemClicked(_:)), keyEquivalent: "") 68 | launchAtLoginMenuItem.target = self 69 | if existingItem(itemUrl: Bundle.main.bundleURL) != nil { 70 | launchAtLoginMenuItem.state = .on 71 | } else { 72 | launchAtLoginMenuItem.state = .off 73 | } 74 | 75 | DeviceManager.defaultManager.reload { (runtimes) in 76 | 77 | var sortedList = [Runtime]() 78 | _ = Dictionary(grouping: runtimes, by: { (runtime: Runtime) in 79 | return runtime.platform 80 | }).values.map({ (runtimeList: [Runtime]) -> [Runtime] in 81 | return runtimeList.sorted { $0.version ?? 0.0 > $1.version ?? 0.0 } 82 | }).forEach({ (list) in 83 | sortedList.append(contentsOf: list) 84 | }) 85 | sortedList.forEach { (runtime) in 86 | var devices = runtime.devices 87 | if self.focusedMode { 88 | devices = devices.filter { $0.state == .booted || $0.applications?.count ?? 0 > 0 } 89 | } 90 | if devices.count == 0 { 91 | return 92 | } 93 | menu.addItem(NSMenuItem.separator()) 94 | let titleItem = NSMenuItem(title: "\(runtime)", action: nil, keyEquivalent: "") 95 | titleItem.isEnabled = false 96 | menu.addItem(titleItem) 97 | 98 | devices.forEach({ (device) in 99 | let deviceMenuItem = menu.addItem(withTitle: device.name, action: nil, keyEquivalent: "") 100 | deviceMenuItem.onStateImage = NSImage(named: NSImage.Name(rawValue: "active")) 101 | deviceMenuItem.offStateImage = NSImage(named: NSImage.Name(rawValue: "inactive")) 102 | deviceMenuItem.state = device.state == .booted ? .on : .off 103 | 104 | let submenu = NSMenu() 105 | submenu.delegate = self 106 | 107 | // Launch Simulator 108 | let simulatorItem = SimulatorMenuItem(runtime:runtime, device:device) 109 | submenu.addItem(simulatorItem) 110 | submenu.addItem(NSMenuItem.separator()) 111 | 112 | // Sort applications by name 113 | let sortApplications = device.applications?.sorted(by: { (app1, app2) -> Bool in 114 | app1.bundleDisplayName.lowercased() < app2.bundleDisplayName.lowercased() 115 | }) 116 | 117 | sortApplications?.forEach { app in 118 | let appMenuItem = AppMenuItem(application: app) 119 | appMenuItem.submenu = ActionMenu(device: device, application: app) 120 | submenu.addItem(appMenuItem) 121 | } 122 | deviceMenuItem.submenu = submenu 123 | 124 | // Simulator Shutdown/Reset 125 | submenu.addItem(NSMenuItem.separator()) 126 | if device.state == .booted { 127 | submenu.addItem(SimulatorShutdownMenuItem(device: device)) 128 | } 129 | if device.applications?.count ?? 0 > 0 { 130 | submenu.addItem(SimulatorResetMenuItem(device: device)) 131 | } 132 | submenu.addItem(SimulatorEraseMenuItem(device: device)) 133 | }) 134 | 135 | } 136 | 137 | menu.addItem(NSMenuItem.separator()) 138 | 139 | let eraseAllSimulators = menu.addItem(withTitle: UIConstants.strings.menuShutDownAllSimulators, action: #selector(self.factoryResetAllSimulators), keyEquivalent: "") 140 | eraseAllSimulators.target = self 141 | 142 | let eraseAllShutdownSimulators = menu.addItem(withTitle: UIConstants.strings.menuShutDownAllBootedSimulators, action: #selector(self.factoryResetAllShutdownSimulators), keyEquivalent: "") 143 | eraseAllShutdownSimulators.target = self 144 | 145 | menu.addItem(NSMenuItem.separator()) 146 | 147 | let quitMenu = menu.addItem(withTitle: UIConstants.strings.menuQuitButton, action: #selector(self.quitItemClicked(_:)), keyEquivalent: "q") 148 | quitMenu.target = self 149 | 150 | if let versionNumber = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String { 151 | menu.addItem(NSMenuItem.separator()) 152 | menu.addItem(withTitle: "\(UIConstants.strings.menuVersionLabel) \(versionNumber)", action: nil, keyEquivalent: "") 153 | } 154 | 155 | self.statusItem.menu = menu 156 | } 157 | } 158 | 159 | private func buildWatcher() { 160 | watcher = DirectoryWatcher(in: URLHelper.deviceURL) 161 | watcher.completionCallback = { [weak self] in 162 | self?.reloadWhenReady(delay: 5) 163 | self?.buildSubWatchers() 164 | } 165 | try? watcher.start() 166 | } 167 | 168 | private func buildSubWatchers() { 169 | subWatchers?.forEach { $0?.stop() } 170 | let deviceDirectories = try? FileManager.default.contentsOfDirectory(at: URLHelper.deviceURL as URL, includingPropertiesForKeys: FileInfo.prefetchedProperties, options: .skipsSubdirectoryDescendants) 171 | subWatchers = deviceDirectories?.map(createSubWatcherForURL) 172 | } 173 | 174 | private func createSubWatcherForURL(_ URL: Foundation.URL) -> DirectoryWatcher? { 175 | guard let info = FileInfo(URL: URL), info.isDirectory else { 176 | return nil 177 | } 178 | let watcher = DirectoryWatcher(in: URL) 179 | watcher.completionCallback = { [weak self] in 180 | self?.reloadWhenReady() 181 | } 182 | try? watcher.start() 183 | return watcher 184 | } 185 | 186 | @objc private func toggleFocusedMode() { 187 | focusedMode = !focusedMode 188 | reloadWhenReady(delay: 0) 189 | } 190 | 191 | private func reloadWhenReady(delay: TimeInterval = 1) { 192 | dispatch_cancel_block_t(self.block) 193 | self.block = dispatch_block_t(delay) { [weak self] in 194 | self?.watcher.stop() 195 | self?.buildMenu() 196 | try? self?.watcher.start() 197 | } 198 | } 199 | 200 | @objc func quitItemClicked(_ sender: AnyObject) { 201 | delegate?.shouldQuitApp() 202 | } 203 | 204 | @objc func refreshItemClicked(_ sender: AnyObject) { 205 | reloadWhenReady() 206 | } 207 | 208 | @objc func launchItemClicked(_ sender: NSMenuItem) { 209 | let wasOn = sender.state == .on 210 | sender.state = (wasOn ? .off : .on) 211 | setLaunchAtLogin(itemUrl: Bundle.main.bundleURL, enabled: !wasOn) 212 | } 213 | 214 | private func resetAllSimulators() { 215 | DeviceManager.defaultManager.reload { (runtimes) in 216 | runtimes.forEach({ (runtime) in 217 | let devices = runtime.devices.filter { $0.applications?.count ?? 0 > 0 } 218 | self.resetSimulators(devices) 219 | }) 220 | } 221 | } 222 | 223 | private func resetShutdownSimulators() { 224 | DeviceManager.defaultManager.reload { (runtimes) in 225 | runtimes.forEach({ (runtime) in 226 | var devices = runtime.devices.filter { $0.applications?.count ?? 0 > 0 } 227 | devices = devices.filter { $0.state == .shutdown } 228 | self.resetSimulators(devices) 229 | }) 230 | } 231 | } 232 | 233 | private func resetSimulators(_ devices: [Device]) { 234 | devices.forEach { (device) in 235 | if device.state == .booted { 236 | device.shutDown() 237 | } 238 | device.factoryReset() 239 | } 240 | } 241 | 242 | @objc func factoryResetAllSimulators() { 243 | let alert: NSAlert = NSAlert() 244 | alert.messageText = String(format: UIConstants.strings.actionFactoryResetAllSimulatorsMessage) 245 | alert.alertStyle = .critical 246 | alert.addButton(withTitle: UIConstants.strings.actionFactoryResetAlertConfirmButton) 247 | alert.addButton(withTitle: UIConstants.strings.actionFactoryResetAlertCancelButton) 248 | let response = alert.runModal() 249 | if response == NSApplication.ModalResponse.alertFirstButtonReturn { 250 | resetAllSimulators() 251 | } 252 | } 253 | 254 | @objc func factoryResetAllShutdownSimulators() { 255 | let alert: NSAlert = NSAlert() 256 | alert.messageText = String(format: UIConstants.strings.actionFactoryResetAllShutdownSimulatorsMessage) 257 | alert.alertStyle = .critical 258 | alert.addButton(withTitle: UIConstants.strings.actionFactoryResetAlertConfirmButton) 259 | alert.addButton(withTitle: UIConstants.strings.actionFactoryResetAlertCancelButton) 260 | let response = alert.runModal() 261 | if response == NSApplication.ModalResponse.alertFirstButtonReturn { 262 | resetShutdownSimulators() 263 | } 264 | } 265 | 266 | } 267 | -------------------------------------------------------------------------------- /OpenSim/OpenInItermAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OpenInItermAction.swift 3 | // OpenSim 4 | // 5 | // Created by Luo Sheng on 07/05/2017. 6 | // Copyright © 2017 Luo Sheng. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class OpenInItermAction: ExtraApplicationActionable { 12 | 13 | var application: Application? 14 | 15 | let appBundleIdentifier = "com.googlecode.iterm2" 16 | 17 | let title = UIConstants.strings.extensionOpenInIterm 18 | 19 | required init(application: Application) { 20 | self.application = application 21 | } 22 | 23 | func perform() { 24 | if let url = application?.sandboxUrl { 25 | NSWorkspace.shared.openFile(url.path, withApplication: "iTerm") 26 | } 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /OpenSim/OpenInTerminalAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OpenInTerminalAction.swift 3 | // OpenSim 4 | // 5 | // Created by Luo Sheng on 07/05/2017. 6 | // Copyright © 2017 Luo Sheng. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | final class OpenInTerminalAction: ApplicationActionable { 12 | 13 | var application: Application? 14 | 15 | let title = UIConstants.strings.actionOpenInTerminal 16 | 17 | let icon = templatize(#imageLiteral(resourceName: "terminal")) 18 | 19 | let isAvailable = true 20 | 21 | init(application: Application) { 22 | self.application = application 23 | } 24 | 25 | func perform() { 26 | if let url = application?.sandboxUrl { 27 | NSWorkspace.shared.openFile(url.path, withApplication: "Terminal") 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /OpenSim/OpenRealmAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OpenRealmAction.swift 3 | // OpenSim 4 | // 5 | // Created by Luo Sheng on 07/05/2017. 6 | // Copyright © 2017 Luo Sheng. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | final class OpenRealmAction: ExtraApplicationActionable { 12 | 13 | var application: Application? 14 | 15 | let appBundleIdentifier = "io.realm.realmbrowser" 16 | 17 | let title = UIConstants.strings.extensionOpenRealmDatabase 18 | 19 | var isAvailable: Bool { 20 | return appPath != nil && realmPath != nil 21 | } 22 | 23 | var realmPath: String? 24 | 25 | init(application: Application) { 26 | self.application = application 27 | 28 | if let sandboxUrl = application.sandboxUrl, 29 | let enumerator = FileManager.default.enumerator(at: sandboxUrl, includingPropertiesForKeys: nil) { 30 | while let fileUrl = enumerator.nextObject() as? URL { 31 | if fileUrl.pathExtension.lowercased() == "realm" { 32 | realmPath = fileUrl.path 33 | } 34 | } 35 | } 36 | } 37 | 38 | func perform() { 39 | if let realmPath = realmPath { 40 | NSWorkspace.shared.openFile(realmPath, withApplication: "Realm Browser") 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /OpenSim/RevealInFinderAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RevealInFinderAction.swift 3 | // OpenSim 4 | // 5 | // Created by Luo Sheng on 07/05/2017. 6 | // Copyright © 2017 Luo Sheng. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | final class RevealInFinderAction: ApplicationActionable { 12 | 13 | var application: Application? 14 | 15 | let title = UIConstants.strings.actionRevealInFinder 16 | 17 | let icon = templatize(#imageLiteral(resourceName: "reveal")) 18 | 19 | let isAvailable: Bool = true 20 | 21 | init(application: Application) { 22 | self.application = application 23 | } 24 | 25 | func perform() { 26 | if let url = application?.sandboxUrl { 27 | NSWorkspace.shared.open(url) 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /OpenSim/Runtime.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Runtime.swift 3 | // OpenSim 4 | // 5 | // Created by Luo Sheng on 11/12/15. 6 | // Copyright © 2015 Luo Sheng. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Runtime: Decodable { 12 | public let name: String 13 | public let devices: [Device] 14 | } 15 | 16 | extension Runtime: CustomStringConvertible { 17 | var description: String { 18 | // current version is format "iOS major.minir" 19 | // old versions of iOS are com.Apple.CoreSimulator.SimRuntime.iOS-major-minor 20 | 21 | let characterSet = CharacterSet(charactersIn: " -.") 22 | let components = name.components(separatedBy: characterSet) 23 | 24 | guard components.count > 2 else { 25 | return name 26 | } 27 | 28 | return "\(components[components.count - 3]) \(components[components.count - 2]).\(components[components.count - 1])" 29 | } 30 | 31 | var platform: String { 32 | return String(description.split(separator: " ").first ?? "") 33 | } 34 | 35 | var version: Float? { 36 | let versionString = String(description.split(separator: " ").last ?? "") 37 | return Float(versionString) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /OpenSim/Simulator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Simulator.swift 3 | // OpenSim 4 | // 5 | // Created by Fernando Bunn on 13/11/18. 6 | // Copyright © 2018 Luo Sheng. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Simulator: Decodable { 12 | private let rawData: [String: [Device]] 13 | public let runtimes: [Runtime] 14 | 15 | enum CodingKeys: String, CodingKey { 16 | case rawData = "devices" 17 | case runtimes 18 | } 19 | 20 | init(from decoder: Decoder) throws { 21 | let values = try decoder.container(keyedBy: CodingKeys.self) 22 | rawData = try values.decode([String: [Device]].self, forKey: .rawData) 23 | 24 | var runtimeList: [Runtime] = [] 25 | for (key, devices) in rawData { 26 | runtimeList.append(Runtime(name: key, devices: devices)) 27 | } 28 | runtimes = runtimeList 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /OpenSim/SimulatorController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SimulatorController.swift 3 | // OpenSim 4 | // 5 | // Created by Bradley Van Dyk on 6/20/16. 6 | // Copyright © 2016 Luo Sheng. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Cocoa 11 | 12 | struct SimulatorController { 13 | 14 | static func boot(_ application: Application) { 15 | _ = shell("/usr/bin/xcrun", arguments: ["simctl", "boot", application.device.UDID]) 16 | } 17 | 18 | static func boot(_ device: Device) { 19 | _ = shell("/usr/bin/xcrun", arguments: ["simctl", "boot", device.UDID]) 20 | } 21 | 22 | static func open() { 23 | _ = shell("/usr/bin/env", arguments: ["open", "/Applications/Xcode.app/Contents/Developer/Applications/Simulator.app/"]) 24 | } 25 | 26 | static func run(_ application: Application) { 27 | _ = shell("/usr/bin/open", arguments: ["-a", "Simulator"]) 28 | } 29 | 30 | static func launch(_ application: Application) { 31 | _ = shell("/usr/bin/xcrun", arguments: ["simctl", "launch", application.device.UDID, application.bundleID]) 32 | } 33 | 34 | static func uninstall(_ application: Application) { 35 | _ = shell("/usr/bin/xcrun", arguments: ["simctl", "uninstall", application.device.UDID, application.bundleID]) 36 | } 37 | 38 | static func shutdown(_ device: Device) { 39 | _ = shell("/usr/bin/xcrun", arguments: ["simctl", "shutdown", device.UDID]) 40 | } 41 | 42 | static func delete(_ device: Device) { 43 | _ = shell("/usr/bin/xcrun", arguments: ["simctl", "delete", device.UDID]) 44 | } 45 | 46 | static func factoryReset(_ device: Device) { 47 | _ = shell("/usr/bin/xcrun", arguments: ["simctl", "erase", device.UDID]) 48 | } 49 | 50 | static func listDevices(callback: @escaping ([Runtime]) -> ()) { 51 | getDevicesJson(currentAttempt: 0) { (jsonString) in 52 | guard let data = jsonString.data(using: String.Encoding.utf8) else { 53 | callback([]) 54 | return 55 | } 56 | do { 57 | let decoder = JSONDecoder() 58 | let simulator = try decoder.decode(Simulator.self, from: data) 59 | let filteredRuntime = simulator.runtimes.filter { $0.devices.count > 0 } 60 | callback(filteredRuntime) 61 | } catch { 62 | callback([]) 63 | } 64 | } 65 | } 66 | 67 | private static let maxAttempt = 8 68 | 69 | private static func getDevicesJson(currentAttempt: Int, callback: @escaping (String) -> ()) { 70 | let jsonString = shell("/usr/bin/xcrun", arguments: ["simctl", "list", "-j", "devices"]) 71 | if jsonString.count > 0 || currentAttempt >= maxAttempt { 72 | callback(jsonString) 73 | return 74 | } 75 | DispatchQueue.global().asyncAfter(deadline: .now() + 1) { 76 | getDevicesJson(currentAttempt: currentAttempt + 1, callback: callback) 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /OpenSim/SimulatorEraseMenuItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SimulatorEraseMenuItem.swift 3 | // OpenSim 4 | // 5 | // Created by Craig Peebles on 6/12/19. 6 | // Copyright © 2019 Luo Sheng. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class SimulatorEraseMenuItem: NSMenuItem { 12 | 13 | var device: Device! 14 | 15 | init(device:Device) { 16 | self.device = device 17 | 18 | let title = "\(UIConstants.strings.menuDeleteSimulatorButton) \(device.name)" 19 | 20 | super.init(title: title, action: #selector(self.deleteSimulator(_:)), keyEquivalent: "") 21 | 22 | target = self 23 | } 24 | 25 | required init(coder decoder: NSCoder) { 26 | fatalError("init(coder:) has not been implemented") 27 | } 28 | 29 | @objc func deleteSimulator(_ sender: AnyObject) { 30 | let alert: NSAlert = NSAlert() 31 | alert.messageText = String(format: UIConstants.strings.actionDeleteSimulatorAlertMessage, device.name) 32 | alert.alertStyle = .critical 33 | alert.addButton(withTitle: UIConstants.strings.actionDeleteSimulatorAlertConfirmButton) 34 | alert.addButton(withTitle: UIConstants.strings.actionDeleteSimulatorAlertCancelButton) 35 | let response = alert.runModal() 36 | if response == NSApplication.ModalResponse.alertFirstButtonReturn { 37 | SimulatorController.delete(device) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /OpenSim/SimulatorMenuItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SimulatorMenuItem.swift 3 | // OpenSim 4 | // 5 | // Created by Benoit Jadinon on 16/05/2019. 6 | // Copyright © 2019 Luo Sheng. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class SimulatorMenuItem: NSMenuItem { 12 | 13 | var runtime: Runtime! 14 | var device: Device! 15 | 16 | init(runtime: Runtime, device:Device) { 17 | self.runtime = runtime 18 | self.device = device 19 | 20 | let title = "\(UIConstants.strings.menuLaunchSimulatorButton) \(device.name) (\(runtime))" 21 | 22 | super.init(title: title, action: #selector(self.openSimulator(_:)), keyEquivalent: "") 23 | 24 | target = self 25 | 26 | // Default image 27 | //self.image = #imageLiteral(resourceName: "DefaultAppIcon").appIcon() 28 | } 29 | 30 | required init(coder decoder: NSCoder) { 31 | fatalError("init(coder:) has not been implemented") 32 | } 33 | 34 | @objc func openSimulator(_ sender: AnyObject) { 35 | self.device.launch() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /OpenSim/SimulatorResetMenuItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SimulatorResetMenuItem.swift 3 | // OpenSim 4 | // 5 | // Created by Craig Peebles on 14/10/19. 6 | // Copyright © 2019 Luo Sheng. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class SimulatorResetMenuItem: NSMenuItem { 12 | 13 | var device: Device! 14 | 15 | init(device:Device) { 16 | self.device = device 17 | 18 | let title = "\(UIConstants.strings.menuResetSimulatorButton) \(device.name)" 19 | 20 | super.init(title: title, action: #selector(self.resetSimulator(_:)), keyEquivalent: "") 21 | 22 | target = self 23 | } 24 | 25 | required init(coder decoder: NSCoder) { 26 | fatalError("init(coder:) has not been implemented") 27 | } 28 | 29 | @objc func resetSimulator(_ sender: AnyObject) { 30 | let alert: NSAlert = NSAlert() 31 | alert.messageText = String(format: UIConstants.strings.actionFactoryResetAlertMessage, device.name) 32 | alert.alertStyle = .critical 33 | alert.addButton(withTitle: UIConstants.strings.actionFactoryResetAlertConfirmButton) 34 | alert.addButton(withTitle: UIConstants.strings.actionFactoryResetAlertCancelButton) 35 | let response = alert.runModal() 36 | if response == NSApplication.ModalResponse.alertFirstButtonReturn { 37 | SimulatorController.factoryReset(device) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /OpenSim/SimulatorShutdownMenuItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SimulatorShutdownMenuItem.swift 3 | // OpenSim 4 | // 5 | // Created by Craig Peebles on 14/10/19. 6 | // Copyright © 2019 Luo Sheng. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class SimulatorShutdownMenuItem: NSMenuItem { 12 | 13 | var device: Device! 14 | 15 | init(device:Device) { 16 | self.device = device 17 | 18 | let title = "\(UIConstants.strings.menuShutdownSimulatorButton) \(device.name)" 19 | 20 | super.init(title: title, action: #selector(self.shutdownSimulator(_:)), keyEquivalent: "") 21 | 22 | target = self 23 | } 24 | 25 | required init(coder decoder: NSCoder) { 26 | fatalError("init(coder:) has not been implemented") 27 | } 28 | 29 | @objc func shutdownSimulator(_ sender: AnyObject) { 30 | device.shutDown() 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /OpenSim/URLHelper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeviceHelper.swift 3 | // SimPholders 4 | // 5 | // Created by Luo Sheng on 11/9/15. 6 | // Copyright © 2015 Luo Sheng. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct URLHelper { 12 | 13 | static let devicesPathComponent = "Developer/CoreSimulator/Devices/" 14 | static let applicationStatesComponent = "data/Library/FrontBoard/applicationState.plist" 15 | static let containersComponent = "data/Containers/Data/Application" 16 | 17 | static let deviceSetFileName = "device_set.plist" 18 | static let deviceFileName = "device.plist" 19 | 20 | static var deviceURL: URL { 21 | get { 22 | guard let libraryPath = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true).first else { 23 | // FIXME: Should throw an error instead 24 | return URL(fileURLWithPath: "/") 25 | } 26 | return URL(fileURLWithPath: libraryPath).appendingPathComponent(devicesPathComponent) 27 | } 28 | } 29 | 30 | static var deviceSetURL: URL { 31 | return self.deviceURL.appendingPathComponent(deviceSetFileName) 32 | } 33 | 34 | static func deviceURLForUDID(_ UDID: String) -> URL { 35 | return deviceURL.appendingPathComponent(UDID) 36 | } 37 | 38 | static func applicationStateURLForUDID(_ UDID: String) -> URL { 39 | return deviceURLForUDID(UDID).appendingPathComponent(applicationStatesComponent) 40 | } 41 | 42 | static func containersURLForUDID(_ UDID: String) -> URL { 43 | return deviceURLForUDID(UDID).appendingPathComponent(containersComponent, isDirectory: true) 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /OpenSim/UninstallAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UninstallAction.swift 3 | // OpenSim 4 | // 5 | // Created by Luo Sheng on 07/05/2017. 6 | // Copyright © 2017 Luo Sheng. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | final class UninstallAction: ApplicationActionable { 12 | 13 | var application: Application? 14 | 15 | let title = UIConstants.strings.actionUninstall 16 | 17 | let icon = templatize(#imageLiteral(resourceName: "uninstall")) 18 | 19 | let isAvailable = true 20 | 21 | init(application: Application) { 22 | self.application = application 23 | } 24 | 25 | func perform() { 26 | guard let application = application else { 27 | return 28 | } 29 | let alert: NSAlert = NSAlert() 30 | alert.messageText = String(format: UIConstants.strings.actionUninstallAlertMessage, application.bundleDisplayName, application.device.name) 31 | alert.alertStyle = .critical 32 | alert.addButton(withTitle: UIConstants.strings.actionUninstallAlertConfirmButton) 33 | alert.addButton(withTitle: UIConstants.strings.actionUninstallAlertCancelButton) 34 | let response = alert.runModal() 35 | if response == NSApplication.ModalResponse.alertFirstButtonReturn { 36 | application.uninstall() 37 | } 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /OpenSim/pt-BR.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "Menu.Quit" = "Sair"; 2 | "Menu.LaunchAtLogin" = "Iniciar junto com sistema"; 3 | "Menu.Refresh" = "Atualizar"; 4 | "Menu.Version" = "Versão"; 5 | "Menu.Launch" = "Inicie o"; 6 | "Action.RevealInFinder" = "Abrir Sandbox no Finder"; 7 | "Action.CopyPathPasteboard" = "Copiar caminho do Sandbox para o Pasteboard"; 8 | "Action.OpenInTerminal" = "Abrir Sandbox no Terminal"; 9 | "Action.Launch" = "Abrir App"; 10 | "Action.Uninstall" = "Desinstalar"; 11 | "Action.UninstallAlertConfirmButton" = "Desinstalar"; 12 | "Action.UninstallAlertCancelButton" = "Cancelar"; 13 | "Action.UninstallAlertMessage" = "Você tem certeza que quer desinstalar %@ do %@?"; 14 | "Extension.OpenInIterm" = "Abrir Sandbox no iTerm"; 15 | "Extension.OpenRealmDatabase" = "Abrir Realm Database"; 16 | "AppInfo.Version" = "Versão"; 17 | "AppInfo.Size" = "Tamanho"; 18 | "MenuHeader.Actions" = "Ações"; 19 | "MenuHeader.Extensions" = "Extensões"; 20 | "MenuHeader.AppInformation" = "Informação do App"; 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenSim [![Travis](https://img.shields.io/travis/luosheng/OpenSim.svg)]() 2 | 3 | OpenSim is an open source alternative to [SimPholders](https://simpholders.com), written in Swift 4. 4 | 5 | You can visit the latest [release](https://github.com/luosheng/OpenSim/releases) to grab a compiled version. (Warning: It's not code-signed.) 6 | 7 | ## Install with Homebrew Cask 8 | 9 | 1. Install Homebrew: `/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"` 10 | 2. Install Cask: `brew install cask` 11 | 3. Install OpenSim: `brew cask install opensim` 12 | 13 | ## TODO 14 | 15 | - [x] Parsing results from `xcrun` command rather than `device_set.plist` file (thank @bradvandyk) 16 | - [x] Watch for changes from simulators directory and reload dynamically 17 | - [x] Filter out empty simulators 18 | - [x] Better UI 19 | - [ ] Other functionalities like uninstalling apps or resetting data 20 | --------------------------------------------------------------------------------