├── .github
└── workflows
│ └── swift.yml
├── .gitignore
├── .swiftlint.yml
├── .travis.yml
├── CHANGELOG.md
├── Cartfile
├── Cartfile.private
├── Cartfile.resolved
├── LICENSE
├── Makefile
├── Podfile
├── Podfile.lock
├── README.md
├── TySimulator.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
└── xcshareddata
│ └── xcschemes
│ └── TySimulator.xcscheme
├── TySimulator.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ ├── IDEWorkspaceChecks.plist
│ └── WorkspaceSettings.xcsettings
├── TySimulator
├── ActivationWindow.xib
├── AppDelegate.swift
├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── Icon-128.png
│ │ ├── Icon-128@2x.png
│ │ ├── Icon-16.png
│ │ ├── Icon-16@2x.png
│ │ ├── Icon-256.png
│ │ ├── Icon-256@2x.png
│ │ ├── Icon-32.png
│ │ ├── Icon-32@2x.png
│ │ ├── Icon-512.png
│ │ └── Icon-512@2x.png
│ ├── Contents.json
│ ├── MenuIcon.imageset
│ │ ├── Contents.json
│ │ ├── Icon-18.png
│ │ ├── Icon-18@2x.png
│ │ └── Icon-18@3x.png
│ ├── empty.imageset
│ │ ├── Contents.json
│ │ ├── empty@2x.png
│ │ └── empty@3x.png
│ ├── finder.imageset
│ │ ├── Contents.json
│ │ ├── finder@2x.png
│ │ └── finder@3x.png
│ ├── icon-off.imageset
│ │ ├── Contents.json
│ │ ├── icon-off@2x.png
│ │ └── icon-off@3x.png
│ ├── icon-on.imageset
│ │ ├── Contents.json
│ │ ├── icon-on@2x.png
│ │ └── icon-on@3x.png
│ ├── settings.imageset
│ │ ├── Contents.json
│ │ ├── settings@2x.png
│ │ └── settings@3x.png
│ └── tmp-logo.imageset
│ │ ├── Contents.json
│ │ ├── tmp-logo@2x.png
│ │ └── tmp-logo@3x.png
├── Base.lproj
│ └── Localizable.strings
├── BaseDetailCollectionItem.xib
├── CommandTransformer.swift
├── DirectoryWatcher.swift
├── Info.plist
├── LRU-K
│ ├── LRU.swift
│ ├── LRUCache.swift
│ └── LRUK.swift
├── MainMenu.storyboard
├── MainMenuController.swift
├── MainViewController.swift
├── MainViewController.xib
├── Model
│ ├── AppGroupModel.swift
│ ├── ApplicationModel.swift
│ ├── CommandModel.swift
│ ├── DeviceModel.swift
│ └── MediaModel.swift
├── Preferences
│ ├── CommandViewController.swift
│ ├── CommandViewController.xib
│ ├── GeneralPreferencesViewController.swift
│ ├── GeneralPreferencesViewController.xib
│ ├── KeyBindingsPreferencesViewController.swift
│ ├── KeyBindingsPreferencesViewController.xib
│ ├── Preference.swift
│ └── ShortcutTableCellView.swift
├── Resource
│ ├── dsa_pub.pem
│ └── tmp-logo.png
├── Script.swift
├── Simulator.swift
├── TySimulator-Bridging-Header.h
├── TySimulator.entitlements
├── Utils
│ ├── Application.swift
│ ├── FileManager.swift
│ ├── MD5.swift
│ ├── Process.swift
│ ├── ShortcutMonitor.swift
│ ├── TextView.swift
│ ├── URL.swift
│ └── ViewController.swift
├── main.swift
├── zh-Hans.lproj
│ └── Localizable.strings
└── zh-Hant.lproj
│ └── Localizable.strings
├── TySimulatorTests
├── Info.plist
├── RecentTests.swift
├── Resource
│ └── script-format.txt
├── ScriptTests.swift
├── SimulatorTests.swift
├── TestFinder
│ ├── Empty
│ ├── SubFinder
│ │ ├── Empty
│ │ └── SubSubFinder
│ │ │ └── .gitkeep
│ └── SubFinder2
│ │ └── .gitkeep
└── UtilTests.swift
├── codecov.yml
├── resources
├── DMG Canvas.dmgCanvas
│ ├── Disk Image
│ └── QuickLook
│ │ └── Preview.jpg
├── dmg-background.png
├── icon-off.png
├── icon-on.png
├── logo.png
├── menu-icon.png
├── tysimulator-logo.png
└── tysimulator-logo.psd
└── scripts
├── ExportOptions.plist
├── build
├── export_archive
├── generate_app_icon
├── generate_menu_icon
├── i18n
└── pack
/.github/workflows/swift.yml:
--------------------------------------------------------------------------------
1 | name: Swift
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: macOS-latest
9 |
10 | steps:
11 | - uses: actions/checkout@v1
12 | - name: Soft Env
13 | run: |
14 | carthage version
15 | pod --version
16 | xcodebuild -version
17 | xcpretty -v
18 | - name: Install Dependencies
19 | run: |
20 | carthage bootstrap --platform osx
21 | pod install --repo-update
22 | - name: Test
23 | run: xcodebuild -workspace TySimulator.xcworkspace -scheme TySimulator -sdk macosx ONLY_ACTIVE_ARCH=NO CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO -enableCodeCoverage YES test
24 | - uses: codecov/codecov-action@v1.0.3
25 | with:
26 | token: ${{secrets.CODECOV_TOKEN}}
27 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xcuserstate
23 |
24 | ## Obj-C/Swift specific
25 | *.hmap
26 | *.ipa
27 | *.dSYM.zip
28 | *.dSYM
29 |
30 | # CocoaPods
31 | #
32 | # We recommend against adding the Pods directory to your .gitignore. However
33 | # you should judge for yourself, the pros and cons are mentioned at:
34 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
35 | #
36 | Pods/
37 |
38 | # Carthage
39 | #
40 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
41 | Carthage/Checkouts
42 |
43 | Carthage/Build
44 |
45 | # fastlane
46 | #
47 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
48 | # screenshots whenever they are needed.
49 | # For more information about the recommended setup visit:
50 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
51 |
52 | fastlane/report.xml
53 | fastlane/Preview.html
54 | fastlane/screenshots
55 | fastlane/test_output
56 |
57 | # Code Injection
58 | #
59 | # After new code Injection tools there's a generated folder /iOSInjectionProject
60 | # https://github.com/johnno1962/injectionforxcode
61 |
62 | iOSInjectionProject/
63 |
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | disabled_rules: # rule identifiers to exclude from running
2 | - trailing_whitespace
3 | - force_cast
4 | - force_try
5 | # - colon
6 | # - comma
7 | # - control_statement
8 | # opt_in_rules: # some rules are only opt-in
9 | # - empty_count
10 | # Find all the available rules by running:
11 | # swiftlint rules
12 | included: # paths to include during linting. `--path` is ignored if present.
13 | - TySimulator
14 | excluded: # paths to ignore during linting. Takes precedence over `included`.
15 | - Carthage
16 | - Pods
17 | # - Source/ExcludedFolder
18 | # - Source/ExcludedFile.swift
19 |
20 | # configurable rules can be customized from this configuration file
21 | # binary rules can set their severity level
22 | # force_cast: warning # implicitly
23 | # force_try:
24 | # severity: warning # explicitly
25 | # rules that have both warning and error levels, can set just the warning level
26 | # implicitly
27 | line_length: 500
28 | # they can set both implicitly with an array
29 | # type_body_length:
30 | # - 300 # warning
31 | # - 400 # error
32 | # or they can set both explicitly
33 | file_length:
34 | warning: 500
35 | error: 1200
36 | # naming rules can set warnings/errors for min_length and max_length
37 | # additionally they can set excluded names
38 | type_name:
39 | min_length: 3
40 | max_length:
41 | warning: 40
42 | error: 50
43 | excluded:
44 | - OS
45 | identifier_name:
46 | # min_length: # only min_length
47 | # error: 4 # only error
48 | excluded:
49 | - id
50 | - os
51 | - kq
52 | - _defaults
53 | - _defaultsArray
54 | # - GlobalAPIKey
55 | reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, junit, html, emoji)
56 |
57 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: objective-c
2 | osx_image: xcode11.2
3 | cache:
4 | cocoapods: true
5 | before_install:
6 | - gem install cocoapods
7 | - brew update
8 | - brew outdated carthage || brew upgrade carthage
9 | before_script:
10 | - carthage bootstrap --platform osx
11 | - pod install --repo-update
12 | script:
13 | - xcodebuild -workspace TySimulator.xcworkspace -scheme TySimulator -sdk macosx ONLY_ACTIVE_ARCH=NO CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO -enableCodeCoverage YES test
14 | after_success:
15 | - bash <(curl -s https://codecov.io/bash) -cF osx
16 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | 0.6.0
2 | ===
3 | 去除 License, TySimulator 定为免费
4 |
5 | 0.7.0
6 | ===
7 | #### Enhancement
8 | 1. 使用新的logo
9 | 2. 添加 i18n, 支持 简繁英 三种语言
10 | 3. 没有icon的App, 将显示默认的空logo
11 |
12 | 0.7.1
13 | ===
14 | #### Fix Bug
15 | 1. [#1 在Mac上使用暗色菜单栏时, 看不到图标](https://github.com/luckytianyiyan/TySimulator/issues/1)
16 | 2. 自定义脚本解析错误
17 |
18 | 0.8.0
19 | ===
20 | #### New Feature
21 | 1. 新增"最近使用的App"
22 |
23 | #### Enhancement
24 | 1. App菜单项的样式
25 |
26 | 0.8.1
27 | ===
28 | #### Fix Bug
29 | 1. Dock Icon 显示错误
30 |
31 | 0.9.0
32 | ===
33 | 最低支持 macOS 10.11
34 |
35 | #### New Feature
36 | 1. 支持 Mojave
37 | 2. 新的UI
38 |
39 | 0.10.0
40 | ===
41 | 最低支持 macOS 10.12
42 |
43 | #### New Feature
44 | 1. 支持 Catalina
45 | 2. 支持 XCode 11
46 |
47 | #### Fix Bug
48 | 1. 菜单栏中, "设置"无法点击
--------------------------------------------------------------------------------
/Cartfile:
--------------------------------------------------------------------------------
1 | github "SwiftyBeaver/SwiftyBeaver"
2 | github "radex/SwiftyUserDefaults"
3 | github "SwiftyJSON/SwiftyJSON"
4 | github "SnapKit/SnapKit"
5 |
--------------------------------------------------------------------------------
/Cartfile.private:
--------------------------------------------------------------------------------
1 | github "Quick/Quick"
2 | github "Quick/Nimble"
--------------------------------------------------------------------------------
/Cartfile.resolved:
--------------------------------------------------------------------------------
1 | github "Quick/Nimble" "v8.0.4"
2 | github "Quick/Quick" "v2.2.0"
3 | github "SnapKit/SnapKit" "5.0.1"
4 | github "SwiftyBeaver/SwiftyBeaver" "1.8.3"
5 | github "SwiftyJSON/SwiftyJSON" "4.2.0"
6 | github "radex/SwiftyUserDefaults" "4.0.0"
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 luckytianyiyan
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.
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: help build
2 |
3 | help: ## show this help message and exit
4 | @echo "usage: make [target]"
5 | @echo
6 | @echo "targets:"
7 | @egrep '^(.+)\:\ ##\ (.+)' ${MAKEFILE_LIST} | column -t -c 2 -s ':#'
8 |
9 | icon: ## generate app icons
10 | @scripts/generate_app_icon resources/logo.png TySimulator/Assets.xcassets/AppIcon.appiconset
11 |
12 | menu-icon: ## generate menu icons
13 | @scripts/generate_menu_icon resources/menu-icon.png TySimulator/Assets.xcassets/MenuIcon.imageset
14 |
15 | bootstrap:
16 | @pod install
17 | @carthage bootstrap --platform osx
18 |
19 | build: ## build TySimulator
20 | @./scripts/build CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO
21 | @open build
22 |
23 | archive: ## archive TySimulator
24 | @rm -rf build
25 | @./scripts/build
26 | @./scripts/export_archive
27 | @open build
28 |
29 | clean: ## clean
30 | @rm -rf build
31 |
32 | pack:
33 | @./scripts/pack
34 |
35 | i18n:
36 | @./scripts/i18n
37 |
38 | %:
39 | @:
40 |
--------------------------------------------------------------------------------
/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment the next line to define a global platform for your project
2 | platform :osx, '10.10'
3 |
4 | target 'TySimulator' do
5 | # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
6 | use_frameworks!
7 |
8 | # Pods for TySimulator
9 | pod 'MASShortcut'
10 | pod 'MASPreferences'
11 | pod 'DevMateKit'
12 | pod 'ACEViewSwift', :git => 'https://github.com/ty0x2333/ACEViewSwift.git', :branch => 'fix_jsCall_error', :submodules => true
13 | pod 'Fabric'
14 | pod 'Crashlytics'
15 |
16 | target 'TySimulatorTests' do
17 | inherit! :search_paths
18 | end
19 |
20 | end
21 |
--------------------------------------------------------------------------------
/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - ACEViewSwift (2.1.0)
3 | - Crashlytics (3.14.0):
4 | - Fabric (~> 1.10.2)
5 | - DevMateKit (1.9.3)
6 | - Fabric (1.10.2)
7 | - MASPreferences (1.3)
8 | - MASShortcut (2.4.0)
9 |
10 | DEPENDENCIES:
11 | - ACEViewSwift (from `https://github.com/ty0x2333/ACEViewSwift.git`, branch `fix_jsCall_error`)
12 | - Crashlytics
13 | - DevMateKit
14 | - Fabric
15 | - MASPreferences
16 | - MASShortcut
17 |
18 | SPEC REPOS:
19 | trunk:
20 | - Crashlytics
21 | - DevMateKit
22 | - Fabric
23 | - MASPreferences
24 | - MASShortcut
25 |
26 | EXTERNAL SOURCES:
27 | ACEViewSwift:
28 | :branch: fix_jsCall_error
29 | :git: https://github.com/ty0x2333/ACEViewSwift.git
30 | :submodules: true
31 |
32 | CHECKOUT OPTIONS:
33 | ACEViewSwift:
34 | :commit: a126c3c9185103e9fbb3ae011cdfadbb137f78e3
35 | :git: https://github.com/ty0x2333/ACEViewSwift.git
36 | :submodules: true
37 |
38 | SPEC CHECKSUMS:
39 | ACEViewSwift: e9488695c68807a24a872296cb9dba40bd444694
40 | Crashlytics: 540b7e5f5da5a042647227a5e3ac51d85eed06df
41 | DevMateKit: a7a46f498d4c549125109d826e858a49d508cbb8
42 | Fabric: 706c8b8098fff96c33c0db69cbf81f9c551d0d74
43 | MASPreferences: c08b8622dd17b47da87669e741efd7c92e970e8c
44 | MASShortcut: d9e4909e878661cc42877cc9d6efbe638273ab57
45 |
46 | PODFILE CHECKSUM: 8308debeef5349374b5b7b3aea075efaba8cf466
47 |
48 | COCOAPODS: 1.8.4
49 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | [](https://travis-ci.org/ty0x2333/TySimulator)
6 | [](https://opencollective.com/TySimulator) [](https://codecov.io/gh/ty0x2333/TySimulator)
7 | [](https://codebeat.co/projects/github-com-ty0x2333-tysimulator-master)
8 | []()
9 | []()
10 |
11 | ---
12 |
13 | Website: [https://tysimulator.com](https://tysimulator.com)
14 |
15 | Features
16 | ===
17 | - Quick access to app's **Documents** directory
18 | - Quick access to simulator's **Media** directory
19 | - Manipulating current running or specified simulator
20 | - Customized command
21 | - Global hotkey
22 | - Recent apps
23 |
24 | Prerequisites
25 | ===
26 | - macOS 10.12 or later
27 | - XCode 10 or later
28 |
29 | Installation
30 | ===
31 | Install TySimulator from Website
32 | [https://tysimulator.com](https://tysimulator.com)
33 |
34 | or via [homebrew cask](https://github.com/caskroom/homebrew-cask)
35 |
36 | ```shell
37 | $ brew update
38 | $ brew cask install tysimulator
39 | ```
40 |
41 | Building
42 | ===
43 |
44 | Requirements
45 | ---
46 | - XCode 11.2 or later
47 | - [CocoaPods](https://github.com/CocoaPods/CocoaPods)
48 | - [Carthage](https://github.com/Carthage/Carthage)
49 |
50 | Optionals
51 | ---
52 | - [xcpretty](https://github.com/supermarin/xcpretty) 0.3.0 or later
53 |
54 | Instructions
55 | ---
56 | ```shell
57 | $ git clone https://github.com/ty0x2333/TySimulator.git
58 | $ cd TySimulator
59 | $ make bootstrap
60 | $ make build
61 | ```
62 |
63 | Inspiration and UX Reference
64 | ---
65 | TySimulator is inspired by [Simulator](https://github.com/hyperoslo/Simulator), [SimPholders](https://simpholders.com), [macdown](https://github.com/MacDownApp/macdown), but with an independent legal copyright. If you don't like the implementation, please consider [Simulator](https://github.com/hyperoslo/Simulator), [SimPholders](https://simpholders.com).
66 |
67 | License
68 | ===
69 |
70 | **TySimulator** is available under the MIT license. See the LICENSE file for more info.
71 |
72 | ## Contributors
73 |
74 | ### Code Contributors
75 |
76 | This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
77 |
78 |
79 | ### Financial Contributors
80 |
81 | Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/TySimulator/contribute)]
82 |
83 | #### Individuals
84 |
85 |
86 |
87 | #### Organizations
88 |
89 | Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/TySimulator/contribute)]
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/TySimulator.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/TySimulator.xcodeproj/xcshareddata/xcschemes/TySimulator.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
38 |
39 |
40 |
41 |
43 |
49 |
50 |
51 |
52 |
53 |
63 |
65 |
71 |
72 |
73 |
74 |
78 |
79 |
80 |
81 |
87 |
89 |
95 |
96 |
97 |
98 |
100 |
101 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/TySimulator.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/TySimulator.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/TySimulator.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | BuildSystemType
6 | Original
7 | PreviewsEnabled
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/TySimulator/ActivationWindow.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 |
--------------------------------------------------------------------------------
/TySimulator/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // TySimulator
4 | //
5 | // Created by ty0x2333 on 2016/11/13.
6 | // Copyright © 2016年 ty0x2333. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import Fabric
11 | import Crashlytics
12 |
13 | class AppDelegate: NSObject, NSApplicationDelegate, DevMateKitDelegate {
14 | @IBOutlet weak var mainMenuController: MainMenuController!
15 |
16 | func applicationDidFinishLaunching(_ aNotification: Notification) {
17 | NSViewController.awake()
18 | NSTextView.awake()
19 |
20 | Fabric.with([Crashlytics.self])
21 | #if DEBUG
22 | Fabric.sharedSDK().debug = true
23 | #endif
24 | UserDefaults.standard.register(defaults: ["NSApplicationCrashOnExceptions": true])
25 |
26 | NSApplication.toggleDockIcon(showIcon: false)
27 | DevMateKit.sendTrackingReport(nil, delegate: nil)
28 | DM_SUUpdater.shared().delegate = self
29 |
30 | DispatchQueue.global().async {
31 | Simulator.shared.updateDeivces()
32 | }
33 | }
34 |
35 | func applicationWillTerminate(_ aNotification: Notification) {
36 | LRUCache.shared.save()
37 | }
38 |
39 | // MARK: SUUpdaterDelegate_DevMateInteraction
40 |
41 | public func updaterDidNotFindUpdate(_ updater: DM_SUUpdater) {
42 | log.warning("not found update: \(updater)")
43 | }
44 |
45 | @nonobjc public func updaterShouldCheck(forBetaUpdates updater: DM_SUUpdater) -> Bool {
46 | #if DEBUG
47 | return true
48 | #else
49 | return false
50 | #endif
51 | }
52 |
53 | @objc public func isUpdater(inTestMode updater: DM_SUUpdater) -> Bool {
54 | #if DEBUG
55 | return true
56 | #else
57 | return false
58 | #endif
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/TySimulator/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "16x16",
5 | "idiom" : "mac",
6 | "filename" : "Icon-16.png",
7 | "scale" : "1x"
8 | },
9 | {
10 | "size" : "16x16",
11 | "idiom" : "mac",
12 | "filename" : "Icon-16@2x.png",
13 | "scale" : "2x"
14 | },
15 | {
16 | "size" : "32x32",
17 | "idiom" : "mac",
18 | "filename" : "Icon-32.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "32x32",
23 | "idiom" : "mac",
24 | "filename" : "Icon-32@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "128x128",
29 | "idiom" : "mac",
30 | "filename" : "Icon-128.png",
31 | "scale" : "1x"
32 | },
33 | {
34 | "size" : "128x128",
35 | "idiom" : "mac",
36 | "filename" : "Icon-128@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "256x256",
41 | "idiom" : "mac",
42 | "filename" : "Icon-256.png",
43 | "scale" : "1x"
44 | },
45 | {
46 | "size" : "256x256",
47 | "idiom" : "mac",
48 | "filename" : "Icon-256@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "512x512",
53 | "idiom" : "mac",
54 | "filename" : "Icon-512.png",
55 | "scale" : "1x"
56 | },
57 | {
58 | "size" : "512x512",
59 | "idiom" : "mac",
60 | "filename" : "Icon-512@2x.png",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
--------------------------------------------------------------------------------
/TySimulator/Assets.xcassets/AppIcon.appiconset/Icon-128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/TySimulator/Assets.xcassets/AppIcon.appiconset/Icon-128.png
--------------------------------------------------------------------------------
/TySimulator/Assets.xcassets/AppIcon.appiconset/Icon-128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/TySimulator/Assets.xcassets/AppIcon.appiconset/Icon-128@2x.png
--------------------------------------------------------------------------------
/TySimulator/Assets.xcassets/AppIcon.appiconset/Icon-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/TySimulator/Assets.xcassets/AppIcon.appiconset/Icon-16.png
--------------------------------------------------------------------------------
/TySimulator/Assets.xcassets/AppIcon.appiconset/Icon-16@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/TySimulator/Assets.xcassets/AppIcon.appiconset/Icon-16@2x.png
--------------------------------------------------------------------------------
/TySimulator/Assets.xcassets/AppIcon.appiconset/Icon-256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/TySimulator/Assets.xcassets/AppIcon.appiconset/Icon-256.png
--------------------------------------------------------------------------------
/TySimulator/Assets.xcassets/AppIcon.appiconset/Icon-256@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/TySimulator/Assets.xcassets/AppIcon.appiconset/Icon-256@2x.png
--------------------------------------------------------------------------------
/TySimulator/Assets.xcassets/AppIcon.appiconset/Icon-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/TySimulator/Assets.xcassets/AppIcon.appiconset/Icon-32.png
--------------------------------------------------------------------------------
/TySimulator/Assets.xcassets/AppIcon.appiconset/Icon-32@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/TySimulator/Assets.xcassets/AppIcon.appiconset/Icon-32@2x.png
--------------------------------------------------------------------------------
/TySimulator/Assets.xcassets/AppIcon.appiconset/Icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/TySimulator/Assets.xcassets/AppIcon.appiconset/Icon-512.png
--------------------------------------------------------------------------------
/TySimulator/Assets.xcassets/AppIcon.appiconset/Icon-512@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/TySimulator/Assets.xcassets/AppIcon.appiconset/Icon-512@2x.png
--------------------------------------------------------------------------------
/TySimulator/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/TySimulator/Assets.xcassets/MenuIcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "Icon-18.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "Icon-18@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "Icon-18@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | },
23 | "properties" : {
24 | "template-rendering-intent" : "template"
25 | }
26 | }
--------------------------------------------------------------------------------
/TySimulator/Assets.xcassets/MenuIcon.imageset/Icon-18.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/TySimulator/Assets.xcassets/MenuIcon.imageset/Icon-18.png
--------------------------------------------------------------------------------
/TySimulator/Assets.xcassets/MenuIcon.imageset/Icon-18@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/TySimulator/Assets.xcassets/MenuIcon.imageset/Icon-18@2x.png
--------------------------------------------------------------------------------
/TySimulator/Assets.xcassets/MenuIcon.imageset/Icon-18@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/TySimulator/Assets.xcassets/MenuIcon.imageset/Icon-18@3x.png
--------------------------------------------------------------------------------
/TySimulator/Assets.xcassets/empty.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "empty@2x.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "filename" : "empty@3x.png",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "version" : 1,
20 | "author" : "xcode"
21 | }
22 | }
--------------------------------------------------------------------------------
/TySimulator/Assets.xcassets/empty.imageset/empty@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/TySimulator/Assets.xcassets/empty.imageset/empty@2x.png
--------------------------------------------------------------------------------
/TySimulator/Assets.xcassets/empty.imageset/empty@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/TySimulator/Assets.xcassets/empty.imageset/empty@3x.png
--------------------------------------------------------------------------------
/TySimulator/Assets.xcassets/finder.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "finder@2x.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "filename" : "finder@3x.png",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "version" : 1,
20 | "author" : "xcode"
21 | }
22 | }
--------------------------------------------------------------------------------
/TySimulator/Assets.xcassets/finder.imageset/finder@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/TySimulator/Assets.xcassets/finder.imageset/finder@2x.png
--------------------------------------------------------------------------------
/TySimulator/Assets.xcassets/finder.imageset/finder@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/TySimulator/Assets.xcassets/finder.imageset/finder@3x.png
--------------------------------------------------------------------------------
/TySimulator/Assets.xcassets/icon-off.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "icon-off@2x.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "filename" : "icon-off@3x.png",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "version" : 1,
20 | "author" : "xcode"
21 | }
22 | }
--------------------------------------------------------------------------------
/TySimulator/Assets.xcassets/icon-off.imageset/icon-off@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/TySimulator/Assets.xcassets/icon-off.imageset/icon-off@2x.png
--------------------------------------------------------------------------------
/TySimulator/Assets.xcassets/icon-off.imageset/icon-off@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/TySimulator/Assets.xcassets/icon-off.imageset/icon-off@3x.png
--------------------------------------------------------------------------------
/TySimulator/Assets.xcassets/icon-on.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "icon-on@2x.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "filename" : "icon-on@3x.png",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "version" : 1,
20 | "author" : "xcode"
21 | },
22 | "properties" : {
23 | "template-rendering-intent" : "template"
24 | }
25 | }
--------------------------------------------------------------------------------
/TySimulator/Assets.xcassets/icon-on.imageset/icon-on@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/TySimulator/Assets.xcassets/icon-on.imageset/icon-on@2x.png
--------------------------------------------------------------------------------
/TySimulator/Assets.xcassets/icon-on.imageset/icon-on@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/TySimulator/Assets.xcassets/icon-on.imageset/icon-on@3x.png
--------------------------------------------------------------------------------
/TySimulator/Assets.xcassets/settings.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "settings@2x.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "filename" : "settings@3x.png",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "version" : 1,
20 | "author" : "xcode"
21 | },
22 | "properties" : {
23 | "template-rendering-intent" : "template"
24 | }
25 | }
--------------------------------------------------------------------------------
/TySimulator/Assets.xcassets/settings.imageset/settings@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/TySimulator/Assets.xcassets/settings.imageset/settings@2x.png
--------------------------------------------------------------------------------
/TySimulator/Assets.xcassets/settings.imageset/settings@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/TySimulator/Assets.xcassets/settings.imageset/settings@3x.png
--------------------------------------------------------------------------------
/TySimulator/Assets.xcassets/tmp-logo.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "tmp-logo@2x.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "filename" : "tmp-logo@3x.png",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "version" : 1,
20 | "author" : "xcode"
21 | }
22 | }
--------------------------------------------------------------------------------
/TySimulator/Assets.xcassets/tmp-logo.imageset/tmp-logo@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/TySimulator/Assets.xcassets/tmp-logo.imageset/tmp-logo@2x.png
--------------------------------------------------------------------------------
/TySimulator/Assets.xcassets/tmp-logo.imageset/tmp-logo@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/TySimulator/Assets.xcassets/tmp-logo.imageset/tmp-logo@3x.png
--------------------------------------------------------------------------------
/TySimulator/Base.lproj/Localizable.strings:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/TySimulator/Base.lproj/Localizable.strings
--------------------------------------------------------------------------------
/TySimulator/BaseDetailCollectionItem.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 |
--------------------------------------------------------------------------------
/TySimulator/CommandTransformer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CommandTransformer.swift
3 | // TySimulator
4 | //
5 | // Created by ty0x2333 on 2016/11/24.
6 | // Copyright © 2016年 ty0x2333. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import MASShortcut
11 |
12 | class CommandTransformer: ValueTransformer {
13 |
14 | override class func allowsReverseTransformation() -> Bool {
15 | return true
16 | }
17 |
18 | override func reverseTransformedValue(_ value: Any?) -> Any? {
19 | let transformer = MASDictionaryTransformer()
20 | guard let value = value as? CommandModel,
21 | let shortcut = transformer.reverseTransformedValue(value.key) else {
22 | return [String: Any]()
23 | }
24 |
25 | return ["id": value.id, "name": value.name, "script": value.script, "shortcut": shortcut]
26 | }
27 |
28 | override func transformedValue(_ value: Any?) -> Any? {
29 | guard let value = value as? [String: Any] else {
30 | return nil
31 | }
32 |
33 | let transformer = MASDictionaryTransformer()
34 | var command: CommandModel
35 | if let id = value["id"] as? String {
36 | command = CommandModel(id: id)
37 | } else {
38 | command = CommandModel()
39 | }
40 |
41 | command.name = (value["name"] as? String) ?? ""
42 | if let shortcut = value["shortcut"] as? [String: Any] {
43 | command.key = transformer.transformedValue(shortcut) as? MASShortcut
44 | }
45 | command.script = value["script"] as! String
46 |
47 | return command
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/TySimulator/DirectoryWatcher.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DirectoryWatcher.swift
3 | // TySimulator
4 | //
5 | // Created by ty0x2333 on 2017/7/20.
6 | // Copyright © 2017年 ty0x2333. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// https://developer.apple.com/library/content/samplecode/DocInteraction/Listings/ReadMe_txt.html
12 | class DirectoryWatcher {
13 | var dirFD: Int32 = -1
14 | var kq: Int32 = -1
15 | var dirKQRef: CFFileDescriptor?
16 |
17 | private var didChange: (() -> Void)?
18 |
19 | deinit {
20 | invalidate()
21 | }
22 |
23 | class func watchFolder(path: String, didChange:(() -> Void)?) -> DirectoryWatcher? {
24 | let result: DirectoryWatcher = DirectoryWatcher()
25 | result.didChange = didChange
26 | if result.startMonitoring(directory: path as NSString) {
27 | return result
28 | }
29 | return nil
30 | }
31 |
32 | func invalidate() {
33 | if dirKQRef != nil {
34 | CFFileDescriptorInvalidate(dirKQRef)
35 | dirKQRef = nil
36 | // We don't need to close the kq, CFFileDescriptorInvalidate closed it instead.
37 | // Change the value so no one thinks it's still live.
38 | kq = -1
39 | }
40 |
41 | if dirFD != -1 {
42 | close(dirFD)
43 | dirFD = -1
44 | }
45 | }
46 |
47 | // MARK: Private
48 |
49 | func kqueueFired() {
50 | assert(kq >= 0)
51 |
52 | var event: kevent = kevent()
53 | var timeout: timespec = timespec(tv_sec: 0, tv_nsec: 0)
54 | let eventCount = kevent(kq, nil, 0, &event, 1, &timeout)
55 |
56 | assert((eventCount >= 0) && (eventCount < 2))
57 |
58 | didChange?()
59 |
60 | CFFileDescriptorEnableCallBacks(dirKQRef, kCFFileDescriptorReadCallBack)
61 | }
62 |
63 | func startMonitoring(directory: NSString) -> Bool {
64 | // Double initializing is not going to work...
65 | if dirKQRef == nil && dirFD == -1 && kq == -1 {
66 | // Open the directory we're going to watch
67 | dirFD = open(directory.fileSystemRepresentation, O_EVTONLY)
68 | if dirFD >= 0 {
69 | // Create a kqueue for our event messages...
70 | kq = kqueue()
71 | if kq >= 0 {
72 | var eventToAdd: kevent = kevent()
73 | eventToAdd.ident = UInt(dirFD)
74 | eventToAdd.filter = Int16(EVFILT_VNODE)
75 | eventToAdd.flags = UInt16(EV_ADD | EV_CLEAR)
76 | eventToAdd.fflags = UInt32(NOTE_WRITE)
77 | eventToAdd.data = 0
78 | eventToAdd.udata = nil
79 |
80 | let errNum = kevent(kq, &eventToAdd, 1, nil, 0, nil)
81 | if errNum == 0 {
82 | var context = CFFileDescriptorContext(version: 0, info: UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()), retain: nil, release: nil, copyDescription: nil)
83 |
84 | let callback: CFFileDescriptorCallBack = { (kqRef: CFFileDescriptor?, callBackTypes: CFOptionFlags, info: UnsafeMutableRawPointer?) in
85 | guard let info = info else {
86 | return
87 | }
88 | let obj: DirectoryWatcher = Unmanaged.fromOpaque(info).takeUnretainedValue()
89 | assert(callBackTypes == kCFFileDescriptorReadCallBack)
90 |
91 | obj.kqueueFired()
92 | }
93 |
94 | // Passing true in the third argument so CFFileDescriptorInvalidate will close kq.
95 | dirKQRef = CFFileDescriptorCreate(nil, kq, true, callback, &context)
96 | if dirKQRef != nil {
97 | if let rls = CFFileDescriptorCreateRunLoopSource(nil, dirKQRef, 0) {
98 | CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, CFRunLoopMode.defaultMode)
99 | CFFileDescriptorEnableCallBacks(dirKQRef, kCFFileDescriptorReadCallBack)
100 |
101 | // If everything worked, return early and bypass shutting things down
102 | return true
103 | }
104 | // Couldn't create a runloop source, invalidate and release the CFFileDescriptorRef
105 | CFFileDescriptorInvalidate(dirKQRef)
106 | dirKQRef = nil
107 | }
108 | }
109 | // kq is active, but something failed, close the handle...
110 | close(kq)
111 | kq = -1
112 | }
113 | // file handle is open, but something failed, close the handle...
114 | close(dirFD)
115 | dirFD = -1
116 | }
117 | }
118 | return false
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/TySimulator/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 | $(MARKETING_VERSION)
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | Fabric
24 |
25 | APIKey
26 | b8abdb192ffac1475217f74ad9b22a062de1c4ec
27 | Kits
28 |
29 |
30 | KitInfo
31 |
32 | KitName
33 | Crashlytics
34 |
35 |
36 |
37 | LSApplicationCategoryType
38 | public.app-category.developer-tools
39 | LSMinimumSystemVersion
40 | $(MACOSX_DEPLOYMENT_TARGET)
41 | LSUIElement
42 |
43 | NSHumanReadableCopyright
44 | Copyright © 2016年 ty0x2333. All rights reserved.
45 | NSMainStoryboardFile
46 | MainMenu
47 | NSPrincipalClass
48 | NSApplication
49 | SUPublicDSAKeyFile
50 | dsa_pub.pem
51 |
52 |
53 |
--------------------------------------------------------------------------------
/TySimulator/LRU-K/LRU.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LRU.swift
3 | // TySimulator
4 | //
5 | // Created by ty0x2333 on 2017/7/1.
6 | // Copyright © 2017年 ty0x2333. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class LRU {
12 | private let capacity: Int
13 | fileprivate var list = [K]()
14 | fileprivate var hashtable: [K: V]
15 | private let semaphore: DispatchSemaphore = DispatchSemaphore(value: 1)
16 |
17 | public var count: Int {
18 | return list.count
19 | }
20 |
21 | public var datas: [(key: K, value: V)] {
22 | var results: [(key: K, value: V)] = []
23 | for key in list.reversed() {
24 | if let value = hashtable[key] {
25 | results.append((key: key, value: value))
26 | }
27 | }
28 | return results
29 | }
30 |
31 | init(capacity: Int, datas: [(key: K, value: V)]? = nil) {
32 | self.capacity = capacity
33 | hashtable = [K: V](minimumCapacity: capacity)
34 |
35 | if let datas = datas {
36 | for (key, value) in datas.reversed() {
37 | list.append(key)
38 | hashtable[key] = value
39 | }
40 | }
41 | }
42 |
43 | subscript (key: K) -> V? {
44 | get {
45 | _ = semaphore.wait(timeout: .distantFuture)
46 | guard let elem = hashtable[key] else {
47 | semaphore.signal()
48 | return nil
49 | }
50 | list.remove(at: list.index(of: key)!)
51 | list.append(key)
52 | semaphore.signal()
53 | return elem
54 | }
55 |
56 | set(value) {
57 | _ = semaphore.wait(timeout: .distantFuture)
58 | repeat {
59 | if hashtable[key] != nil {
60 | list.remove(at: list.index(of: key)!)
61 | }
62 |
63 | hashtable[key] = value
64 |
65 | if value == nil {
66 | break
67 | }
68 |
69 | list.append(key)
70 |
71 | if list.count > capacity {
72 | let deletedKey = list.removeFirst()
73 | hashtable.removeValue(forKey: deletedKey)
74 | }
75 | } while false
76 |
77 | semaphore.signal()
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/TySimulator/LRU-K/LRUCache.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LRUCache.swift
3 | // TySimulator
4 | //
5 | // Created by ty0x2333 on 2017/7/2.
6 | // Copyright © 2017年 ty0x2333. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | extension Notification.Name {
13 | public struct LRUCache {
14 | public static let DidRecord = Notification.Name(rawValue: "com.tianyiyan.notification.lru.didRecord")
15 | }
16 | }
17 |
18 | class LRUCache {
19 | fileprivate var lruk: LRUK
20 | var datas: [String] {
21 | return lruk.datas.map { $0.value }
22 | }
23 | public static let shared = LRUCache(file: LRUCache.filePath)
24 |
25 | fileprivate init() {
26 | lruk = LRUK(capacity: 5, bufferSize: 10, threshold: 2)
27 | }
28 |
29 | func record(app bundleIdentifier: String) {
30 | lruk[bundleIdentifier] = bundleIdentifier
31 | NotificationCenter.default.post(name: Notification.Name.LRUCache.DidRecord, object: nil)
32 | }
33 | }
34 |
35 | extension LRUCache {
36 | static var filePath: URL {
37 | let appSupportURL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
38 | let bundleID = Bundle.main.bundleIdentifier!
39 | return appSupportURL.appendingPathComponent(bundleID, isDirectory: true).appendingPathComponent("LRUCache.json")
40 | }
41 |
42 | func jsonDescription() -> JSON {
43 | let history = lruk.history.datas.map { ["hits": $0.value.hits, "bundle_id": $0.value.value] }
44 | let datas = lruk.datas.map { $0.value }
45 |
46 | return JSON(["datas": datas, "history": history])
47 | }
48 |
49 | convenience init(json: JSON) {
50 | self.init()
51 | let datas = json["datas"].arrayValue.map { (key: $0.stringValue, value: $0.stringValue) }
52 | let history = json["history"].arrayValue.map {
53 | (key: $0["bundle_id"].stringValue, value: (hits: $0["hits"].intValue, value: $0["bundle_id"].stringValue))
54 | }
55 | lruk = LRUK(capacity: 3, bufferSize: 10, threshold: 2, datas: datas, history: history)
56 | }
57 |
58 | convenience init(file: URL) {
59 | if let content = try? String(contentsOf: LRUCache.filePath, encoding: .utf8) {
60 | self.init(json: JSON(parseJSON: content))
61 | } else {
62 | self.init()
63 | }
64 | }
65 |
66 | func save() {
67 | do {
68 | try jsonDescription().rawString([.castNilToNSNull: true])?.write(to: LRUCache.filePath, atomically: true, encoding: .utf8)
69 | log.info("save LRU cache to \(LRUCache.filePath)")
70 | } catch {
71 | log.error("save LRU cache error: \(error)")
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/TySimulator/LRU-K/LRUK.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LRUK.swift
3 | // TySimulator
4 | //
5 | // Created by ty0x2333 on 2017/7/1.
6 | // Copyright © 2017年 ty0x2333. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class LRUK {
12 | private let cache: LRU
13 | private let bufferSize: Int
14 | let history: LRU
15 | private let threshold: Int
16 | private let semaphore: DispatchSemaphore = DispatchSemaphore(value: 1)
17 |
18 | public var count: Int {
19 | return cache.count
20 | }
21 |
22 | public var datas: [(key: K, value: V)] {
23 | return cache.datas
24 | }
25 |
26 | init(capacity: Int, bufferSize: Int, threshold: Int = 2, datas: [(key: K, value: V)]? = nil, history: [(key: K, value: (hits: Int, value: V))]? = nil) {
27 | cache = LRU(capacity: capacity, datas: datas)
28 | self.history = LRU(capacity: bufferSize, datas: history)
29 | self.bufferSize = bufferSize
30 | self.threshold = threshold
31 | }
32 |
33 | subscript (key: K) -> V? {
34 | get {
35 | return cache[key]
36 | }
37 |
38 | // if value is nil, it will do nothing
39 | set(value) {
40 | guard let newValue = value else {
41 | return
42 | }
43 | _ = semaphore.wait(timeout: .distantFuture)
44 |
45 | if let his = history[key] {
46 | let hits = his.hits + 1
47 |
48 | if hits < threshold {
49 | history[key] = (hits: hits, value: newValue)
50 | } else {
51 | history[key] = nil
52 | cache[key] = newValue
53 | }
54 | } else if threshold < 2 {
55 | cache[key] = newValue
56 | } else {
57 | history[key] = (hits: 1, value: newValue)
58 | }
59 |
60 | semaphore.signal()
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/TySimulator/MainMenu.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
--------------------------------------------------------------------------------
/TySimulator/MainMenuController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MainMenuController.swift
3 | // TySimulator
4 | //
5 | // Created by ty0x2333 on 2016/11/13.
6 | // Copyright © 2016年 ty0x2333. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class MainMenuController: NSObject {
12 | let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
13 | let popover: NSPopover = NSPopover()
14 | let quitMenuItem: NSMenuItem = NSMenuItem(title: NSLocalizedString("menu.quit", comment: "menu"), action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q")
15 | let aboutItem: NSMenuItem = NSMenuItem(title: NSLocalizedString("menu.about", comment: "menu"), action: #selector(NSApplication.showAboutWindow), keyEquivalent: "")
16 | let preferenceItem: NSMenuItem = NSMenuItem(title: NSLocalizedString("menu.preference", comment: "menu"), action: #selector(NSApplication.showPreferencesWindow), keyEquivalent: ",")
17 | lazy var menu: NSMenu = {
18 | let menu = NSMenu()
19 | menu.autoenablesItems = false
20 | menu.addItem(preferenceItem)
21 | menu.addItem(aboutItem)
22 | menu.addItem(NSMenuItem.separator())
23 | menu.addItem(quitMenuItem)
24 | return menu
25 | }()
26 |
27 | var monitor: Any?
28 |
29 | override func awakeFromNib() {
30 | super.awakeFromNib()
31 |
32 | popover.contentViewController = MainViewController(nibName: "MainViewController", bundle: nil)
33 |
34 | if let button = statusItem.button {
35 | button.image = NSImage(named: "MenuIcon")
36 | button.target = self
37 | button.action = #selector(MainMenuController.togglePopver(_:))
38 | }
39 | }
40 |
41 | // MARK: Actions
42 |
43 | @IBAction func onClickMenuPreferences(_ sender: Any) {
44 | NSApplication.shared.showPreferencesWindow()
45 | }
46 |
47 | @objc func togglePopver(_ sender: Any?) {
48 | if popover.isShown {
49 | closePopover(sender: sender)
50 | } else {
51 | showPopover(sender: sender)
52 | }
53 | }
54 |
55 | // MARK: Private
56 | private func showPopover(sender: Any?) {
57 | guard let button = statusItem.button else {
58 | return
59 | }
60 | log.info("show Popover")
61 | popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
62 | guard monitor == nil else {
63 | return
64 | }
65 | monitor = NSEvent.addGlobalMonitorForEvents(matching: [.leftMouseDown, .rightMouseDown]) { [weak self] event in
66 | guard let weakSelf = self,
67 | weakSelf.popover.isShown else {
68 | return
69 | }
70 | weakSelf.closePopover(sender: event)
71 | }
72 | }
73 |
74 | func closePopover(sender: Any?) {
75 | log.info("close Popover")
76 | popover.performClose(sender)
77 | if let monitor = self.monitor {
78 | NSEvent.removeMonitor(monitor)
79 | self.monitor = nil
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/TySimulator/MainViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MainViewController.swift
3 | // TySimulator
4 | //
5 | // Created by ty0x2333 on 2018/10/15.
6 | // Copyright © 2018 ty0x2333. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SnapKit
11 |
12 | class MainViewController: NSViewController {
13 | @IBOutlet weak var deviceTableView: NSTableView!
14 | @IBOutlet weak var infoCollectionView: NSCollectionView!
15 | @IBOutlet weak var splitView: NSSplitView!
16 |
17 | @IBOutlet weak var progressView: NSProgressIndicator!
18 | @IBOutlet weak var recentView: NSView!
19 | @IBOutlet weak var recentCollectionView: NSCollectionView!
20 |
21 | @IBOutlet weak var emptyView: NSView!
22 | @IBOutlet var splitBottomConstraint: NSLayoutConstraint!
23 | @IBOutlet weak var recentContainerView: NSScrollView!
24 |
25 | private static let headerItemIdentifier = NSUserInterfaceItemIdentifier(rawValue: "infoSectionHeader")
26 | private static let appItemIdentifier = NSUserInterfaceItemIdentifier(rawValue: "defaultItem")
27 | private static let recentItemIdentifier = NSUserInterfaceItemIdentifier(rawValue: "recentItem")
28 | private var devices: [DeviceModel] = [] {
29 | didSet {
30 | recentCollectionView.reloadData()
31 | updateRecentView()
32 | }
33 | }
34 | private var recentApplications: [ApplicationModel] = []
35 | private var selectedDeviceUDID: String?
36 |
37 | private var selectedDevice: DeviceModel? {
38 | guard let udid = selectedDeviceUDID else {
39 | return nil
40 | }
41 | return devices.first(where: { $0.udid == udid })
42 | }
43 |
44 | override func viewDidLoad() {
45 | super.viewDidLoad()
46 | splitView.delegate = self
47 | deviceTableView.selectionHighlightStyle = .none
48 | deviceTableView.delegate = self
49 | deviceTableView.dataSource = self
50 |
51 | infoCollectionView.register(NSNib(nibNamed: "MainViewController", bundle: nil), forItemWithIdentifier: MainViewController.appItemIdentifier)
52 | infoCollectionView.register(InfoSectionHeaderView.self, forSupplementaryViewOfKind: NSCollectionView.elementKindSectionHeader, withIdentifier: MainViewController.headerItemIdentifier)
53 | infoCollectionView.delegate = self
54 | infoCollectionView.dataSource = self
55 |
56 | recentCollectionView.register(NSNib(nibNamed: "BaseDetailCollectionItem", bundle: nil), forItemWithIdentifier: MainViewController.recentItemIdentifier)
57 | recentCollectionView.dataSource = self
58 | recentCollectionView.delegate = self
59 |
60 | for subview in splitView.subviews {
61 | subview.wantsLayer = true
62 | subview.layer?.cornerRadius = 4.0
63 | subview.layer?.masksToBounds = true
64 | }
65 |
66 | recentContainerView.wantsLayer = true
67 | recentContainerView.layer?.cornerRadius = 4.0
68 | recentContainerView.layer?.masksToBounds = true
69 |
70 | NotificationCenter.default.addObserver(self, selector: #selector(devicesChangedNotification(sender:)), name: Notification.Name.Device.DidChange, object: nil)
71 | NotificationCenter.default.addObserver(self, selector: #selector(updateRecentApplications), name: Notification.Name.LRUCache.DidRecord, object: nil)
72 |
73 | devices = Simulator.shared.devices
74 | reloadInfos()
75 | updateRecentApplications()
76 | }
77 |
78 | deinit {
79 | NotificationCenter.default.removeObserver(self)
80 | }
81 |
82 | override func viewWillAppear() {
83 | super.viewWillAppear()
84 | progressView.startAnimation(nil)
85 | DispatchQueue.global().async {
86 | Simulator.shared.updateDeivces()
87 | DispatchQueue.main.async {
88 | self.progressView.stopAnimation(nil)
89 | }
90 | }
91 | }
92 |
93 | private func updateDeviceMenus() {
94 | log.verbose("update devices")
95 |
96 | devices = Simulator.shared.devices
97 | log.info("load devices: \(devices.count)")
98 |
99 | deviceTableView.reloadData()
100 | }
101 |
102 | private func reloadInfos() {
103 | infoCollectionView.reloadData()
104 | emptyView.isHidden = numberOfSections(in: infoCollectionView) > 0
105 | }
106 |
107 | @objc private func updateRecentApplications() {
108 | let datas = LRUCache.shared.datas
109 | guard datas.count > 0 else {
110 | recentApplications = []
111 | return
112 | }
113 | var apps: [ApplicationModel] = []
114 | let bootedDevices = Simulator.shared.bootedDevices
115 | for bundleID in datas {
116 | for device in bootedDevices {
117 | if let app = device.application(bundleIdentifier: bundleID) {
118 | apps.append(app)
119 | break
120 | }
121 | }
122 | }
123 |
124 | guard apps.count > 0 else {
125 | recentApplications = []
126 | return
127 | }
128 |
129 | log.verbose("update recent apps")
130 |
131 | var models: [ApplicationModel] = []
132 | for (idx, app) in apps.enumerated() {
133 | if idx > 2 {
134 | break
135 | }
136 | models.append(app)
137 | }
138 | recentApplications = models
139 | }
140 |
141 | @IBAction func onMenuClick(_ sender: NSButton) {
142 | guard let menu = (NSApp.delegate as? AppDelegate)?.mainMenuController?.menu,
143 | let event = NSApp.currentEvent else {
144 | return
145 | }
146 | NSMenu.popUpContextMenu(menu, with: event, for: sender)
147 | }
148 |
149 | // MARK: Notification
150 | @objc func devicesChangedNotification(sender: Notification) {
151 | log.verbose("devicesChangedNotification updateDeviceMenus")
152 | updateDeviceMenus()
153 | updateRecentApplications()
154 | }
155 |
156 | // MARK: Helper
157 |
158 | private func updateRecentView() {
159 | let isVisible = recentApplications.count > 0
160 | splitBottomConstraint.isActive = isVisible
161 | recentView.isHidden = !isVisible
162 | }
163 | }
164 |
165 | extension MainViewController: NSTableViewDelegate {
166 | func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
167 | let result: AppMenuTableCellView = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "defaultRow"), owner: self) as! AppMenuTableCellView
168 | if devices.indices.contains(row) {
169 | let device = devices[row]
170 | result.name = device.displayName
171 | result.isAvailable = device.isOpen
172 | result.isHighlight = device.udid == selectedDeviceUDID
173 | }
174 | return result
175 | }
176 |
177 | func tableViewSelectionDidChange(_ notification: Notification) {
178 | if devices.indices.contains(deviceTableView.selectedRow) {
179 | selectedDeviceUDID = devices[deviceTableView.selectedRow].udid
180 | }
181 | deviceTableView.deselectAll(nil)
182 | deviceTableView.reloadData()
183 | reloadInfos()
184 | }
185 | }
186 |
187 | extension MainViewController: NSTableViewDataSource {
188 | func numberOfRows(in tableView: NSTableView) -> Int {
189 | return devices.count
190 | }
191 | }
192 |
193 | extension MainViewController: NSSplitViewDelegate {
194 | func splitViewWillResizeSubviews(_ notification: Notification) {
195 | infoCollectionView.collectionViewLayout?.invalidateLayout()
196 | }
197 | }
198 |
199 | extension MainViewController: NSCollectionViewDataSource {
200 |
201 | func numberOfSections(in collectionView: NSCollectionView) -> Int {
202 | if collectionView == recentCollectionView {
203 | return 1
204 | }
205 | guard let device = selectedDevice else {
206 | return 0
207 | }
208 | if device.applications.isEmpty, device.appGroups.isEmpty, device.medias.isEmpty {
209 | return 0
210 | }
211 | return 3
212 | }
213 |
214 | func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
215 | guard collectionView == infoCollectionView else {
216 | return recentApplications.count
217 | }
218 | guard let device = selectedDevice else {
219 | return 0
220 | }
221 | switch section {
222 | case 0:
223 | return device.applications.count
224 | case 1:
225 | return device.appGroups.count
226 | case 2:
227 | return device.medias.count
228 | default:
229 | return 0
230 | }
231 | }
232 |
233 | func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
234 | guard collectionView == infoCollectionView else {
235 | let item = collectionView.makeItem(withIdentifier: MainViewController.recentItemIdentifier, for: indexPath) as! BaseDetailCollectionItem
236 | if recentApplications.indices.contains(indexPath.item) {
237 | let app = recentApplications[indexPath.item]
238 | if let imageView = item.imageView {
239 | imageView.wantsLayer = true
240 | imageView.layer?.cornerRadius = 10.0
241 | imageView.layer?.masksToBounds = true
242 | }
243 | item.icon = app.bundle.appIcon
244 | item.name = app.bundle.appName
245 | item.location = app.dataPath
246 | }
247 | return item
248 | }
249 | let item = collectionView.makeItem(withIdentifier: MainViewController.appItemIdentifier, for: indexPath) as! DetailCollectionItem
250 | switch indexPath.section {
251 | case 0:
252 | if let applications = selectedDevice?.applications, applications.indices.contains(indexPath.item) {
253 | let app = applications[indexPath.item]
254 | item.icon = app.bundle.appIcon
255 | item.name = app.bundle.appName
256 | item.type = .app
257 | item.location = app.dataPath
258 | }
259 | case 1:
260 | if let groups = selectedDevice?.appGroups, groups.indices.contains(indexPath.item) {
261 | let group = groups[indexPath.item]
262 | item.name = group.bundleIdentifier
263 | item.type = .group
264 | item.location = group.location
265 | }
266 | case 2:
267 | if let medias = selectedDevice?.medias, medias.indices.contains(indexPath.item) {
268 | let media = medias[indexPath.item]
269 | item.name = media.name
270 | item.type = .assets
271 | item.location = media.location
272 | }
273 | default:
274 | break
275 | }
276 | return item
277 | }
278 |
279 | func collectionView(_ collectionView: NSCollectionView, viewForSupplementaryElementOfKind kind: NSCollectionView.SupplementaryElementKind, at indexPath: IndexPath) -> NSView {
280 | if collectionView == infoCollectionView, kind == NSCollectionView.elementKindSectionHeader {
281 | let headerView = collectionView.makeSupplementaryView(ofKind: kind, withIdentifier: MainViewController.headerItemIdentifier, for: indexPath)
282 | var text = ""
283 | switch indexPath.section {
284 | case 0:
285 | text = NSLocalizedString("menu.application", comment: "menu")
286 | case 1:
287 | text = NSLocalizedString("menu.app.group", comment: "menu")
288 | case 2:
289 | text = NSLocalizedString("menu.media", comment: "menu")
290 | default:
291 | break
292 | }
293 | (headerView as? InfoSectionHeaderView)?.title = text
294 | return headerView
295 | }
296 | return NSView()
297 | }
298 |
299 | func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> NSSize {
300 | return collectionView == infoCollectionView ? CGSize(width: collectionView.bounds.width, height: InfoSectionHeaderView.defaultHeight) : .zero
301 | }
302 | }
303 |
304 | extension MainViewController: NSCollectionViewDelegateFlowLayout {
305 | func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> NSSize {
306 | return collectionView == infoCollectionView ? CGSize(width: collectionView.bounds.width, height: 49) : CGSize(width: 63.0, height: 65.0)
307 | }
308 |
309 | func collectionView(_ collectionView: NSCollectionView, didSelectItemsAt indexPaths: Set) {
310 | guard let indexPath = indexPaths.first else {
311 | return
312 | }
313 |
314 | var location: URL?
315 | if collectionView == infoCollectionView {
316 | location = detailItemLocation(indexPath: indexPath)
317 | } else if recentApplications.indices.contains(indexPath.item) {
318 | location = recentApplications[indexPath.item].dataPath
319 | }
320 |
321 | if let loc = location {
322 | DispatchQueue.main.async {
323 | NSWorkspace.shared.open(loc)
324 | }
325 | }
326 | (NSApp.delegate as? AppDelegate)?.mainMenuController?.closePopover(sender: nil)
327 | }
328 |
329 | private func detailItemLocation(indexPath: IndexPath) -> URL? {
330 | switch indexPath.section {
331 | case 0:
332 | if let applications = selectedDevice?.applications, applications.indices.contains(indexPath.item) {
333 | let app = applications[indexPath.item]
334 | LRUCache.shared.record(app: app.bundle.bundleID)
335 | return app.dataPath
336 | }
337 | case 1:
338 | if let groups = selectedDevice?.appGroups, groups.indices.contains(indexPath.item) {
339 | return groups[indexPath.item].location
340 | }
341 | case 2:
342 | if let medias = selectedDevice?.medias, medias.indices.contains(indexPath.item) {
343 | let media = medias[indexPath.item]
344 | return media.location
345 | }
346 | default:
347 | break
348 | }
349 | return nil
350 | }
351 | }
352 |
353 | class AppMenuTableCellView: NSTableCellView {
354 | var name: String? {
355 | set {
356 | textField?.stringValue = newValue ?? ""
357 | }
358 | get {
359 | return textField?.stringValue
360 | }
361 | }
362 | var isAvailable: Bool = false {
363 | didSet {
364 | imageView?.image = isAvailable ? NSImage(named: "icon-on") : NSImage(named: "icon-off")
365 | }
366 | }
367 |
368 | var isHighlight: Bool = false {
369 | didSet {
370 | needsDisplay = true
371 | }
372 | }
373 |
374 | override func prepareForReuse() {
375 | super.prepareForReuse()
376 | isAvailable = false
377 | }
378 |
379 | override func draw(_ dirtyRect: NSRect) {
380 | super.draw(dirtyRect)
381 | if isHighlight {
382 | NSColor.selectedControlColor.set()
383 | dirtyRect.fill()
384 | }
385 | }
386 | }
387 |
388 | class BaseDetailCollectionItem: NSCollectionViewItem {
389 | var icon: NSImage? {
390 | set {
391 | newValue?.isTemplate = false
392 | imageView?.image = newValue ?? NSImage(named: "tmp-logo")
393 | }
394 | get {
395 | return imageView?.image
396 | }
397 | }
398 | var name: String? {
399 | set {
400 | textField?.stringValue = newValue ?? ""
401 | }
402 | get {
403 | return textField?.stringValue
404 | }
405 | }
406 |
407 | var location: URL?
408 | }
409 |
410 | class DetailCollectionItem: BaseDetailCollectionItem {
411 | enum ItemType {
412 | case app
413 | case assets
414 | case group
415 | }
416 | var directoryWatcher: DirectoryWatcher?
417 | @IBOutlet private weak var sizeTextField: NSTextField!
418 |
419 | var type: ItemType = .app {
420 | didSet {
421 | if type == .app {
422 | imageView?.layer?.cornerRadius = 21.0
423 | } else {
424 | icon = NSImage(named: "finder")
425 | imageView?.layer?.cornerRadius = 0
426 | }
427 | }
428 | }
429 |
430 | override var location: URL? {
431 | didSet {
432 | directoryWatcher?.invalidate()
433 | guard let url = location?.appendingPathComponent("Documents", isDirectory: true) else {
434 | return
435 | }
436 | directoryWatcher = DirectoryWatcher.watchFolder(path: url.path, didChange: { [weak self] in
437 | log.verbose("\(url) did change")
438 | self?.updateSizeText()
439 | })
440 | updateSizeText()
441 | }
442 | }
443 |
444 | override func awakeFromNib() {
445 | super.awakeFromNib()
446 | imageView?.wantsLayer = true
447 | imageView?.layer?.cornerRadius = 21.0
448 | imageView?.layer?.masksToBounds = true
449 | }
450 |
451 | override func prepareForReuse() {
452 | super.prepareForReuse()
453 | location = nil
454 | directoryWatcher = nil
455 | icon = nil
456 | name = nil
457 | }
458 |
459 | private func updateSizeText() {
460 | guard let url = location,
461 | let numerator = FileManager.default.enumerator(at: url, includingPropertiesForKeys: [.fileSizeKey], options: [], errorHandler: nil) else {
462 | sizeTextField.isHidden = true
463 | return
464 | }
465 | var total: Int64 = 0
466 | for object in numerator {
467 | var fileSizeResource: AnyObject?
468 | guard let fileURL = object as? NSURL else {
469 | continue
470 | }
471 | try? fileURL.getResourceValue(&fileSizeResource, forKey: .fileSizeKey)
472 | guard let fileSize = fileSizeResource as? NSNumber else {
473 | continue
474 | }
475 | total += fileSize.int64Value
476 | }
477 | sizeTextField.isHidden = false
478 | sizeTextField.stringValue = ByteCountFormatter.string(fromByteCount: total, countStyle: .file)
479 | }
480 | }
481 |
482 | class InfoSectionHeaderView: NSView {
483 | static let defaultHeight: CGFloat = 40.0
484 | private var textField: NSTextField = NSTextField()
485 | var title: String? {
486 | set {
487 | textField.stringValue = newValue ?? ""
488 | }
489 | get {
490 | return textField.stringValue
491 | }
492 | }
493 |
494 | override init(frame frameRect: NSRect) {
495 | super.init(frame: frameRect)
496 |
497 | textField.isBezeled = false
498 | textField.isEditable = false
499 | textField.isSelectable = false
500 | textField.font = NSFont.boldSystemFont(ofSize: 13.0)
501 | addSubview(textField)
502 |
503 | textField.snp.makeConstraints { (make) in
504 | make.left.equalToSuperview().offset(16.0)
505 | make.centerY.right.equalToSuperview()
506 | }
507 | }
508 |
509 | required init?(coder decoder: NSCoder) {
510 | fatalError("init(coder:) has not been implemented")
511 | }
512 |
513 | override func draw(_ dirtyRect: NSRect) {
514 | super.draw(dirtyRect)
515 | NSColor.placeholderTextColor.setFill()
516 | NSRect(x: 16.0, y: 8.0, width: bounds.width - 16.0, height: 0.5).fill()
517 | }
518 | }
519 |
--------------------------------------------------------------------------------
/TySimulator/Model/AppGroupModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppGroupModel.swift
3 | // TySimulator
4 | //
5 | // Created by ty0x2333 on 2016/11/13.
6 | // Copyright © 2016年 ty0x2333. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class AppGroupModel: NSObject {
12 |
13 | var bundleIdentifier: String = ""
14 | var location: URL?
15 | }
16 |
--------------------------------------------------------------------------------
/TySimulator/Model/ApplicationModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ApplicationModel.swift
3 | // TySimulator
4 | //
5 | // Created by ty0x2333 on 2016/11/13.
6 | // Copyright © 2016年 ty0x2333. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class ApplicationBundle {
12 | let bundleID: String
13 | let appName: String
14 | let appIcon: NSImage?
15 | init(bundleID: String, appName: String, appIcon: NSImage?) {
16 | self.bundleID = bundleID
17 | self.appName = appName
18 | self.appIcon = appIcon
19 | }
20 | }
21 |
22 | class ApplicationModel {
23 | let bundle: ApplicationBundle
24 | let uuid: String
25 | let deviceUDID: String
26 | let dataPath: URL
27 |
28 | init?(deviceUDID: String, uuid: String, bundle: ApplicationBundle, dataPath: URL) {
29 | self.deviceUDID = deviceUDID
30 | self.uuid = uuid
31 | self.bundle = bundle
32 | self.dataPath = dataPath
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/TySimulator/Model/CommandModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CommandModel.swift
3 | // TySimulator
4 | //
5 | // Created by ty0x2333 on 2016/11/18.
6 | // Copyright © 2016年 ty0x2333. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import MASShortcut
11 |
12 | class CommandModel: NSObject {
13 | private(set) var id: String
14 | @objc var name: String = ""
15 | @objc var script: String = ""
16 | @objc var key: MASShortcut?
17 |
18 | convenience override init() {
19 | let timeStamp = NSDate().timeIntervalSince1970
20 | self.init(id: String(timeStamp).md5())
21 | }
22 |
23 | @objc init(id: String) {
24 | self.id = id
25 | super.init()
26 | }
27 |
28 | override var description: String {
29 | return "<\(name): \(String(describing: key))>"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/TySimulator/Model/DeviceModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DeviceModel.swift
3 | // TySimulator
4 | //
5 | // Created by ty0x2333 on 2016/11/13.
6 | // Copyright © 2016年 ty0x2333. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | enum OS: String {
12 | case tvOS
13 | case iOS
14 | case watchOS
15 | case unknown
16 |
17 | var order: Int {
18 | switch self {
19 | case .iOS:
20 | return 0
21 | case .tvOS:
22 | return 1
23 | case .watchOS:
24 | return 2
25 | default:
26 | return 3
27 | }
28 | }
29 | }
30 |
31 | class DeviceModel {
32 |
33 | let name: String
34 | let udid: String
35 | let osInfo: String
36 |
37 | let os: OS
38 | let isOpen: Bool
39 | let isAvailable: Bool
40 | let version: String
41 | let hasContent: Bool
42 |
43 | let applications: [ApplicationModel]
44 | let medias: [MediaModel]
45 | let appGroups: [AppGroupModel]
46 | let location: URL
47 |
48 | var displayName: String {
49 | return "\(name) (\(osInfo))"
50 | }
51 |
52 | init(osInfo: String, json: [String: Any]) {
53 | name = (json["name"] as? String) ?? ""
54 | udid = (json["udid"] as? String) ?? ""
55 | if let isAvailable = (json["availability"] as? String)?.contains("(available)") {
56 | self.isAvailable = isAvailable
57 | } else {
58 | self.isAvailable = (json["isAvailable"] as? Bool) ?? false
59 | }
60 | isOpen = (json["state"] as? String)?.contains("Booted") ?? false
61 | self.osInfo = osInfo
62 | if osInfo.components(separatedBy: "-").count >= 3 {
63 | os = OS(rawValue: osInfo.components(separatedBy: "-").first ?? "") ?? .unknown
64 | var osInfoArr = osInfo.components(separatedBy: "-")
65 | osInfoArr.remove(at: 0)
66 | version = osInfoArr.joined(separator: ".")
67 | } else {
68 | os = OS(rawValue: osInfo.components(separatedBy: " ").first ?? "") ?? .unknown
69 | version = osInfo.components(separatedBy: " ").last ?? ""
70 | }
71 | location = Simulator.devicesDirectory.appendingPathComponent("\(udid)")
72 | applications = Simulator.applications(deviceUDID: udid)
73 | appGroups = Simulator.appGroups(deviceUDID: udid)
74 | medias = Simulator.medias(path: location)
75 | hasContent = !applications.isEmpty
76 | }
77 |
78 | func application(bundleIdentifier: String) -> ApplicationModel? {
79 | return applications.first(where: { $0.bundle.bundleID == bundleIdentifier })
80 | }
81 | }
82 |
83 | extension DeviceModel: Equatable {
84 | public static func == (lhs: DeviceModel, rhs: DeviceModel) -> Bool {
85 | return lhs.udid == rhs.udid
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/TySimulator/Model/MediaModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MediaModel.swift
3 | // TySimulator
4 | //
5 | // Created by ty0x2333 on 2016/11/13.
6 | // Copyright © 2016年 ty0x2333. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class MediaModel {
12 |
13 | let name: String
14 | let location: URL
15 |
16 | init(name: String, location: URL) {
17 | self.name = name
18 | self.location = location
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/TySimulator/Preferences/CommandViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CommandViewController.swift
3 | // TySimulator
4 | //
5 | // Created by ty0x2333 on 2016/11/21.
6 | // Copyright © 2016年 ty0x2333. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import MASShortcut
11 | import ACEViewSwift
12 |
13 | class CommandViewController: NSViewController, ACEViewDelegate {
14 | @objc var command: CommandModel!
15 | @IBOutlet weak var nameTextField: NSTextField!
16 | @IBOutlet weak var shortcutView: MASShortcutView!
17 | @IBOutlet weak var aceView: ACEView!
18 | var save: ((CommandModel) -> Void)?
19 |
20 | init(_ command: CommandModel) {
21 | super.init(nibName: nil, bundle: nil)
22 | self.command = command
23 | }
24 | override init(nibName nibNameOrNil: NSNib.Name?, bundle nibBundleOrNil: Bundle?) {
25 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
26 | command = CommandModel()
27 | }
28 |
29 | required init?(coder: NSCoder) {
30 | super.init(coder: coder)
31 | }
32 |
33 | override func viewDidLoad() {
34 | super.viewDidLoad()
35 | title = "Command Editor"
36 | nameTextField.bind(NSBindingName(rawValue: "value"), to: command, withKeyPath: #keyPath(CommandModel.name), options: [NSBindingOption.continuouslyUpdatesValue: true])
37 | shortcutView.bind(NSBindingName(rawValue: "shortcutValue"), to: command, withKeyPath: #keyPath(CommandModel.key), options: [NSBindingOption.continuouslyUpdatesValue: true])
38 |
39 | aceView.onReady = { [unowned self] in
40 | self.aceView.bind(NSBindingName(rawValue: "string"), to: self.command, withKeyPath: #keyPath(CommandModel.script), options: [NSBindingOption.continuouslyUpdatesValue: true])
41 | self.aceView.delegate = self
42 | if self.command.script.isEmpty {
43 | let (deviceIdentifier, applicationIdentifier) = self.placeholderDevice()
44 | self.aceView.string = "open " + Script.transformedValue(deviceIdentifier: deviceIdentifier, applicationIdentifier: applicationIdentifier)
45 | } else {
46 | self.aceView.string = self.command.script
47 | }
48 | self.aceView.mode = .sh
49 | self.aceView.theme = .xcode
50 | self.aceView.keyboardHandler = .ace
51 | self.aceView.showPrintMargin = false
52 | self.aceView.showInvisibles = true
53 | self.aceView.basicAutoCompletion = true
54 | self.aceView.liveAutocompletion = true
55 | self.aceView.snippets = true
56 | self.aceView.emmet = true
57 | }
58 | }
59 |
60 | func placeholderDevice() -> (String, String) {
61 | var deviceIdentifier = "booted"
62 |
63 | // try booted device
64 | if let device = Simulator.shared.bootedDevices.first {
65 | if device.applications.count > 0 {
66 | return (deviceIdentifier, (device.applications.first?.bundle.bundleID)!)
67 | }
68 | }
69 |
70 | // try other device
71 | for device in Simulator.shared.devices {
72 | deviceIdentifier = device.udid
73 | if device.applications.count > 0 {
74 | return (deviceIdentifier, (device.applications.first?.bundle.bundleID)!)
75 | }
76 | }
77 | return (deviceIdentifier, "your_app_bundle_identifier")
78 | }
79 |
80 | @IBAction func onSaveButtonClick(_ sender: NSButton) {
81 | // TODO: log
82 | save?(command)
83 | dismiss(self)
84 | }
85 |
86 | @IBAction func onCancelButtonClick(_ sender: NSButton) {
87 | // TODO: log
88 | dismiss(self)
89 | }
90 |
91 | // MARK: ACEViewDelegate
92 | func textDidChange(_ notification: Notification) {
93 | command.script = aceView.string
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/TySimulator/Preferences/CommandViewController.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 |
70 |
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 |
--------------------------------------------------------------------------------
/TySimulator/Preferences/GeneralPreferencesViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GeneralPreferencesViewController.swift
3 | // TySimulator
4 | //
5 | // Created by ty0x2333 on 2016/11/17.
6 | // Copyright © 2016年 ty0x2333. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import MASPreferences
11 |
12 | class GeneralPreferencesViewController: NSViewController, MASPreferencesViewController {
13 |
14 | @IBOutlet weak var checkForUpdatesButton: NSButton!
15 | @IBOutlet weak var feedbackButton: NSButton!
16 | @IBOutlet weak var applicationBox: NSBox!
17 | @IBOutlet weak var menuBox: NSBox!
18 | @IBOutlet weak var isOnlyHasContentDevices: NSButton!
19 | @IBOutlet weak var isOnlyAvailableDevices: NSButton!
20 | @IBOutlet weak var isLaunchAtStartup: NSButton!
21 | @IBOutlet weak var isAutomaticallyChecksForUpdates: NSButton!
22 |
23 | override func awakeFromNib() {
24 | super.awakeFromNib()
25 | let preference = Preference.shared
26 |
27 | checkForUpdatesButton.title = NSLocalizedString("preference.general.check.update", comment: "preference")
28 | feedbackButton.title = NSLocalizedString("preference.general.feedback", comment: "preference")
29 |
30 | applicationBox.title = NSLocalizedString("preference.general.application", comment: "preference")
31 | menuBox.title = NSLocalizedString("preference.general.menu", comment: "preference")
32 |
33 | isLaunchAtStartup.title = NSLocalizedString("preference.general.launch.startup", comment: "preference")
34 | isLaunchAtStartup.state = NSApplication.isLaunchAtStartup ? .on : .off
35 |
36 | isOnlyAvailableDevices.title = NSLocalizedString("preference.general.only.available.device", comment: "preference")
37 | isOnlyAvailableDevices.state = preference.onlyAvailableDevices ? .on : .off
38 |
39 | isOnlyHasContentDevices.title = NSLocalizedString("preference.general.only.has.application", comment: "preference")
40 | isOnlyHasContentDevices.state = preference.onlyHasContentDevices ? .on : .off
41 |
42 | isAutomaticallyChecksForUpdates.title = NSLocalizedString("preference.general.auto.check.update", comment: "preference")
43 | isAutomaticallyChecksForUpdates.state = DM_SUUpdater.shared().automaticallyChecksForUpdates ? .on : .off
44 | }
45 | @IBAction func onLaunchAtStartupButtonClick(_ sender: NSButton) {
46 | NSApplication.isLaunchAtStartup = sender.state == .on
47 | }
48 |
49 | @IBAction func onOnlyAvailableDevicesButtonClick(_ sender: NSButton) {
50 | Preference.shared.onlyAvailableDevices = sender.state == .on
51 | }
52 |
53 | @IBAction func onOnlyHasContentDevicesButtonClick(_ sender: NSButton) {
54 | Preference.shared.onlyHasContentDevices = sender.state == .on
55 | }
56 |
57 | @IBAction func onAutomaticallyChecksForUpdatesButtonClick(_ sender: NSButton) {
58 | DM_SUUpdater.shared().automaticallyChecksForUpdates = sender.state == .on
59 | }
60 |
61 | @IBAction func onCheckForUpdatesButtonClick(_ sender: NSButton) {
62 | NSApp.checkForUpdates()
63 | }
64 |
65 | @IBAction func onFeedbackButtonClick(_ sender: NSButton) {
66 | NSApp.showFeedbackWindow()
67 | }
68 |
69 | // MARK: MASPreferencesViewController
70 | var viewIdentifier: String {
71 | return "GeneralPreferences"
72 | }
73 |
74 | var toolbarItemImage: NSImage? = NSImage(named: NSImage.preferencesGeneralName)
75 |
76 | var toolbarItemLabel: String? = NSLocalizedString("preference.general", comment: "preference")
77 | }
78 |
--------------------------------------------------------------------------------
/TySimulator/Preferences/GeneralPreferencesViewController.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 |
43 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
83 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
113 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
--------------------------------------------------------------------------------
/TySimulator/Preferences/KeyBindingsPreferencesViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // KeyBindingsPreferencesViewController.swift
3 | // TySimulator
4 | //
5 | // Created by ty0x2333 on 16/11/17.
6 | // Copyright © 2016年 ty0x2333. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import MASPreferences
11 | import MASShortcut
12 |
13 | class KeyBindingsPreferencesViewController: NSViewController, MASPreferencesViewController, NSTableViewDelegate, NSTableViewDataSource, NSTextFieldDelegate {
14 |
15 | @IBOutlet weak var tableView: NSTableView!
16 |
17 | var commandController: NSArrayController = NSArrayController()
18 |
19 | override func awakeFromNib() {
20 | super.awakeFromNib()
21 | tableView.doubleAction = #selector(onTableViewDoubleClick(_:))
22 | }
23 |
24 | // MARK: Actions
25 | @objc func onTableViewDoubleClick(_ sender: NSTableView) {
26 | log.verbose("click row: \(sender.clickedRow)")
27 | let command = Preference.shared.commands[sender.clickedRow]
28 | let commandViewController = CommandViewController(command)
29 | let oldKey = command.key
30 | commandViewController.save = { [weak self] (command) in
31 | log.verbose("save command: \(command)")
32 | MASShortcutMonitor.shared().unregisterShortcut(oldKey)
33 | Preference.shared.setCommand(id: command.id, command: command)
34 | MASShortcutMonitor.shared().register(command: command)
35 | self?.tableView.reloadData()
36 | }
37 | presentAsSheet(commandViewController)
38 | }
39 |
40 | @IBAction func onAddCommandButtonClick(_ sender: NSButton) {
41 | let commandViewController = CommandViewController()
42 | commandViewController.save = { [weak self] (command) in
43 | log.verbose("save command: \(command)")
44 | MASShortcutMonitor.shared().register(command: command)
45 | Preference.shared.append(command)
46 | self?.tableView.reloadData()
47 | }
48 | presentAsSheet(commandViewController)
49 | }
50 |
51 | @IBAction func onRemoveButtonClick(_ sender: NSButton) {
52 | if tableView.numberOfSelectedRows < 1 {
53 | log.warning("no row selected")
54 | return
55 | }
56 | let preference = Preference.shared
57 | let command = preference.commands[tableView.selectedRow]
58 | MASShortcutMonitor.shared().unregister(command: command)
59 | Preference.shared.remove(at: tableView.selectedRow)
60 | tableView.reloadData()
61 | }
62 |
63 | // MARK: NSTableViewDataSource
64 | func numberOfRows(in tableView: NSTableView) -> Int {
65 | return Preference.shared.commands.count
66 | }
67 |
68 | func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
69 | let command = Preference.shared.commands[row]
70 |
71 | if tableColumn == tableView.tableColumns[0] {
72 | if let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "NameTableCellViewIdentifier"), owner: nil) as? NSTableCellView {
73 | cell.textField?.stringValue = command.name
74 | return cell
75 | }
76 | } else if tableColumn == tableView.tableColumns[1] {
77 | if let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "ShortcutTableCellViewIdentifier"), owner: nil) as? ShortcutTableCellView {
78 | cell.shortcutView?.shortcutValue = command.key
79 | cell.shortcutView?.shortcutValueChange = {(sender: MASShortcutView?) in
80 | MASShortcutMonitor.shared().unregister(command: command)
81 | command.key = sender?.shortcutValue
82 | Preference.shared.setCommand(id: command.id, command: command)
83 | MASShortcutMonitor.shared().register(command: command)
84 | log.info("row: \(row), shortcut changed: \(String(describing: sender?.shortcutValue))")
85 | }
86 | return cell
87 | }
88 | }
89 |
90 | return nil
91 | }
92 |
93 | // MARK: MASPreferencesViewController
94 | var viewIdentifier: String {
95 | return "KeyBindingsPreferences"
96 | }
97 |
98 | var toolbarItemImage: NSImage? = NSImage(named: NSImage.advancedName)
99 |
100 | var toolbarItemLabel: String? = NSLocalizedString("preference.key.binding", comment: "preference")
101 | }
102 |
--------------------------------------------------------------------------------
/TySimulator/Preferences/KeyBindingsPreferencesViewController.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 |
132 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
--------------------------------------------------------------------------------
/TySimulator/Preferences/Preference.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Preference.swift
3 | // TySimulator
4 | //
5 | // Created by ty0x2333 on 2016/11/17.
6 | // Copyright © 2016年 ty0x2333. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import SwiftyUserDefaults
11 | import MASPreferences
12 | import MASShortcut
13 |
14 | public typealias CommandRaw = [String: Any]
15 |
16 | extension Array: DefaultsSerializable where Element == [CommandRaw] {
17 | public static var _defaults: DefaultsKeyedArchiverBridge {
18 | return DefaultsKeyedArchiverBridge()
19 | }
20 | public static var _defaultsArray: DefaultsKeyedArchiverBridge<[Element]> {
21 | fatalError("Multidimensional arrays are not supported yet")
22 | }
23 | }
24 |
25 | extension DefaultsKeys {
26 | static let preferences = DefaultsKey<[String: Any]?>("com.tianyiyan.preferences")
27 | static let onlyAvailableDevices = DefaultsKey("onlyAvailableDevices")
28 | static let onlyHasContentDevices = DefaultsKey("onlyHasContentDevices", defaultValue: false)
29 | static let commands = DefaultsKey<[CommandRaw]>("commands", defaultValue: [])
30 | }
31 |
32 | class Preference: NSObject {
33 | static let sharedWindowController: MASPreferencesWindowController = preferencesWindowController()
34 | static let shared: Preference = Preference()
35 | private(set) var commands: [CommandModel] = []
36 | @objc dynamic var onlyAvailableDevices: Bool {
37 | didSet {
38 | Defaults[.onlyAvailableDevices] = onlyAvailableDevices
39 | }
40 | }
41 | @objc dynamic var onlyHasContentDevices: Bool {
42 | didSet {
43 | Defaults[.onlyHasContentDevices] = onlyHasContentDevices
44 | }
45 | }
46 |
47 | override init() {
48 |
49 | // init commands
50 | commands = []
51 | let transformer = CommandTransformer()
52 | for data in Defaults[.commands] {
53 | if let command = transformer.transformedValue(data) as? CommandModel {
54 | MASShortcutMonitor.shared().register(command: command)
55 | commands.append(command)
56 | }
57 | }
58 |
59 | // init switchs
60 | onlyAvailableDevices = Defaults[.onlyAvailableDevices] ?? true
61 | onlyHasContentDevices = Defaults[.onlyHasContentDevices]
62 |
63 | super.init()
64 | }
65 |
66 | func append(_ command: CommandModel) {
67 | append(commands: [command])
68 | }
69 |
70 | func append(commands: [CommandModel]) {
71 | self.commands.append(contentsOf: commands)
72 | synchronize()
73 | }
74 |
75 | func remove(at index: Int) {
76 | commands.remove(at: index)
77 | synchronize()
78 | }
79 |
80 | func setCommand(id: String, command: CommandModel) {
81 | if let idx = commands.index(where: { $0.id == id }) {
82 | commands[idx] = command
83 | synchronize()
84 | }
85 | }
86 |
87 | func synchronize() {
88 | let transformer = CommandTransformer()
89 | Defaults[.commands] = commands.map { transformer.reverseTransformedValue($0) as! [String: Any] }
90 | UserDefaults.standard.synchronize()
91 | }
92 |
93 | private class func preferencesWindowController() -> MASPreferencesWindowController {
94 | let generalViewController = GeneralPreferencesViewController()
95 | let keyBindingViewController = KeyBindingsPreferencesViewController()
96 | let preferencesWindow = MASPreferencesWindowController(viewControllers: [generalViewController, keyBindingViewController], title: NSLocalizedString("preference.title", comment: "preference"))
97 | preferencesWindow.window?.level = .floating
98 | return preferencesWindow
99 | }
100 |
101 | }
102 |
--------------------------------------------------------------------------------
/TySimulator/Preferences/ShortcutTableCellView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ShortcutTableCellView.swift
3 | // TySimulator
4 | //
5 | // Created by ty0x2333 on 2016/11/20.
6 | // Copyright © 2016年 ty0x2333. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import MASShortcut
11 |
12 | class ShortcutTableCellView: NSTableCellView {
13 | @IBOutlet open var shortcutView: MASShortcutView?
14 | }
15 |
--------------------------------------------------------------------------------
/TySimulator/Resource/dsa_pub.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN PUBLIC KEY-----
2 | MIIBtjCCASsGByqGSM44BAEwggEeAoGBAJ4rd/Egp/agelXDdTxRcw8LFOdmW4r7
3 | nzufoqw/be/dUFNG3GOzQtrztk/cYHf4W0xG0mRM2Zc/B4guQzU08PI2P122T7X0
4 | DZm6s7u/oJvBqfoTXpcrRryJESqb40nxA8+/MQ4wJZojUhKUps4DpK43GR3iRUY4
5 | iPKFljXhYLnHAhUAtgdgMSUP1tm20F6uiRyDL822jycCgYAJcXKUCshHulPk1iKL
6 | fa/BeDZK5NHhB0VCqmC8JfASOFHhtP/Ax95vzi4vKZIErch1wKk8fNMnMNxvl1Nk
7 | yD6ilT74ST2YARofJ3ouSF8aH1+Wezqc9G+j9s1bm9O5wkna1BIZDuwbnC4m4EvB
8 | rn16rrS4M5BLWtzparSwppZSzwOBhAACgYBDwG+LgJqATzu2J9BvoMEttyWK5H1j
9 | nXhUtO2eTvl3p8ZsGJ0fGu5jAe5in4eJLI/7/92t7kPEA7QunIEsUtyWX80QBUzO
10 | K2iSv5YoXrM72KRaWFpNomumCGvEfVH7Jd08h94zAYQN3JUeGaI4fKpOLs8wQ3Sf
11 | vyTjNkTb4xeDHg==
12 | -----END PUBLIC KEY-----
13 |
--------------------------------------------------------------------------------
/TySimulator/Resource/tmp-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/TySimulator/Resource/tmp-logo.png
--------------------------------------------------------------------------------
/TySimulator/Script.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Script.swift
3 | // TySimulator
4 | //
5 | // Created by ty0x2333 on 16/12/29.
6 | // Copyright © 2016年 ty0x2333. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | public class Script {
13 | public class func transformedScript(_ script: String) -> String {
14 | var result = script
15 | var res: [NSTextCheckingResult] = []
16 | do {
17 | let regex = try NSRegularExpression(pattern: "\\$\\{\\{[\\s\\S]*?\\}\\}", options: NSRegularExpression.Options.caseInsensitive)
18 | res = regex.matches(in: script, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSRange(location: 0, length: script.count))
19 | } catch {
20 | log.error(error)
21 | }
22 | guard res.count > 0 else {
23 | return script
24 | }
25 | for checkingRes in res {
26 | var commandValue = (script as NSString).substring(with: checkingRes.range)
27 | commandValue = (commandValue as NSString).substring(with: NSRange(location: 2, length: commandValue.count - 3)
28 | )
29 | let command = JSON(parseJSON: commandValue)
30 | let deviceId = command["device"].stringValue
31 |
32 | guard !deviceId.isEmpty else {
33 | log.warning("device id is empty")
34 | continue
35 | }
36 |
37 | guard let device = (deviceId != "booted") ? Simulator.shared.device(udid: deviceId) : Simulator.shared.bootedDevices.first else {
38 | log.warning("no device: \(deviceId)")
39 | continue
40 | }
41 |
42 | if let bundleIdentifier = command["application"].string,
43 | let application = device.application(bundleIdentifier: bundleIdentifier),
44 | let location = application.dataPath.removeTrailingSlash?.absoluteString {
45 |
46 | result = (result as NSString).replacingCharacters(in: checkingRes.range, with: location)
47 | }
48 | }
49 | return result
50 | }
51 |
52 | public class func transformedValue(deviceIdentifier: String, applicationIdentifier: String) -> String {
53 | return "${{\"device\": \"\(deviceIdentifier)\", \"application\": \"\(applicationIdentifier)\"}}"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/TySimulator/Simulator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Simulator.swift
3 | // TySimulator
4 | //
5 | // Created by ty0x2333 on 2018/10/8.
6 | // Copyright © 2018 ty0x2333. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Notification.Name {
12 | public struct Device {
13 | public static let DidChange = Notification.Name(rawValue: "com.tianyiyan.notification.device.didChange")
14 | public static let BootedDidChange = Notification.Name(rawValue: "com.tianyiyan.notification.device.booted.didChange")
15 | }
16 | }
17 |
18 | class Simulator {
19 | static let shared = Simulator()
20 | private(set) var devices: [DeviceModel] = []
21 | private(set) var bootedDevices: [DeviceModel] = []
22 | var deviceContentToken: NSKeyValueObservation?
23 | var deviceAvailableToken: NSKeyValueObservation?
24 |
25 | init() {
26 | let changeHandler: (Preference, NSKeyValueObservedChange) -> Void = { [weak self] _, _ in
27 | DispatchQueue.global().async {
28 | self?.updateDeivces()
29 | }
30 | }
31 | deviceContentToken = Preference.shared.observe(\.onlyHasContentDevices, options: [.new], changeHandler: changeHandler)
32 | deviceAvailableToken = Preference.shared.observe(\.onlyAvailableDevices, options: [.new], changeHandler: changeHandler)
33 | }
34 |
35 | deinit {
36 | deviceContentToken?.invalidate()
37 | deviceAvailableToken?.invalidate()
38 | }
39 |
40 | func updateDeivces() {
41 | let allDevices = Simulator.listDevices()
42 | let preference = Preference.shared
43 | var filter: [DeviceFilter] = []
44 | if preference.onlyAvailableDevices {
45 | filter.append(.onlyAvailableDevices)
46 | }
47 | if preference.onlyHasContentDevices {
48 | filter.append(.onlyHasContentDevices)
49 | }
50 | devices = Simulator.listSortedDevices(filter: filter, descending: false)
51 |
52 | let bootedDevices = allDevices.filter {
53 | $0.hasContent && $0.isAvailable && $0.os != .unknown && $0.isOpen
54 | }.sorted {
55 | $0.displayName.compare($1.displayName) == .orderedAscending
56 | }
57 |
58 | DispatchQueue.main.async {
59 | NotificationCenter.default.post(name: Notification.Name.Device.DidChange, object: nil)
60 | }
61 |
62 | let areInIncreasingOrder: ((DeviceModel, DeviceModel) -> Bool) = { (lhs, rhs) -> Bool in
63 | return lhs.udid > rhs.udid
64 | }
65 |
66 | let hasChanged = bootedDevices.sorted(by: areInIncreasingOrder) != self.bootedDevices.sorted(by: areInIncreasingOrder)
67 | self.bootedDevices = bootedDevices
68 | if hasChanged {
69 | DispatchQueue.main.async {
70 | NotificationCenter.default.post(name: Notification.Name.Device.BootedDidChange, object: nil)
71 | }
72 | }
73 | }
74 |
75 | static var devicesDirectory: URL {
76 | let path = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true).first ?? ""
77 | return URL(fileURLWithPath: path).appendingPathComponent("Developer/CoreSimulator/Devices")
78 | }
79 |
80 | func device(udid: String) -> DeviceModel? {
81 | return devices.first(where: { $0.udid == udid })
82 | }
83 | }
84 |
85 | extension Simulator {
86 | struct DeviceFilter: OptionSet {
87 | let rawValue: Int
88 |
89 | static let onlyAvailableDevices = DeviceFilter(rawValue: 1 << 1)
90 | static let onlyHasContentDevices = DeviceFilter(rawValue: 1 << 2)
91 | }
92 |
93 | // MARK: Device
94 |
95 | class func listDevices(filter: [DeviceFilter] = []) -> [DeviceModel] {
96 | let data = Process.outputData(launchPath: "/usr/bin/xcrun", arguments: ["simctl", "list", "-j", "devices"])
97 |
98 | let deviceObjs = try? JSONSerialization.jsonObject(with: data, options: [.allowFragments])
99 |
100 | var devices: [DeviceModel] = []
101 |
102 | guard let dic = deviceObjs as? [String: [String: Any]], let deviceDic = dic["devices"] else {
103 | return []
104 | }
105 | deviceDic.forEach { (key, value) in
106 | let osInfo = key.replacingOccurrences(of: "com.apple.CoreSimulator.SimRuntime.", with: "")
107 | if let arrayValue = value as? [[String: Any]] {
108 | let models = arrayValue.map { DeviceModel(osInfo: osInfo, json: $0) }
109 | devices.append(contentsOf: models)
110 | }
111 | }
112 |
113 | devices = devices.filter {
114 | var result = $0.os != .unknown
115 | if filter.contains(.onlyAvailableDevices) {
116 | result = result && $0.isAvailable
117 | }
118 | if filter.contains(.onlyHasContentDevices) {
119 | result = result && $0.hasContent
120 | }
121 | return result
122 | }
123 |
124 | return devices
125 | }
126 |
127 | class func listSortedDevices(filter: [DeviceFilter] = [], descending: Bool) -> [DeviceModel] {
128 | return listDevices(filter: filter).sorted {
129 | $0.osInfo.compare($1.osInfo) == (descending ? .orderedDescending : .orderedAscending)
130 | }
131 | }
132 |
133 | class func devicePath(udid: String) -> URL {
134 | return Simulator.devicesDirectory.appendingPathComponent("\(udid)")
135 | }
136 |
137 | // MARK: Media
138 |
139 | class func medias(path: URL) -> [MediaModel] {
140 | let directory = path.appendingPathComponent("/data/Media/DCIM")
141 |
142 | return FileManager.directories(directory).map {
143 | let media = MediaModel(name: $0, location: directory.appendingPathComponent($0))
144 | return media
145 | }
146 | }
147 |
148 | // MARK: Application
149 |
150 | class func applicationsDataPath(deviceUDID: String) -> URL {
151 | let devicePath = Simulator.devicePath(udid: deviceUDID)
152 | return devicePath.appendingPathComponent("data/Containers/Data/Application")
153 | }
154 |
155 | class func applicationsBundlePath(deviceUDID: String) -> URL {
156 | let devicePath = Simulator.devicesDirectory.appendingPathComponent("\(deviceUDID)")
157 | return devicePath.appendingPathComponent("data/Containers/Bundle/Application")
158 | }
159 |
160 | class func applications(deviceUDID: String) -> [ApplicationModel] {
161 | let bundlesDirectory = Simulator.applicationsBundlePath(deviceUDID: deviceUDID)
162 | let applicationDirectories = FileManager.directories(bundlesDirectory)
163 | var result: [ApplicationModel] = []
164 | for uuid in applicationDirectories {
165 | if let bundle = applicationBundle(deviceUDID: deviceUDID, applicationUUID: uuid),
166 | let dataPath = findApplicationDataPath(deviceUDID: deviceUDID, bundleID: bundle.bundleID),
167 | let application = ApplicationModel(deviceUDID: deviceUDID, uuid: uuid, bundle: bundle, dataPath: dataPath) {
168 | result.append(application)
169 | }
170 | }
171 | return result
172 | }
173 |
174 | class func applicationBundle(deviceUDID: String, applicationUUID: String) -> ApplicationBundle? {
175 | let bundlesDirectory = Simulator.applicationsBundlePath(deviceUDID: deviceUDID)
176 | let bundlePath = bundlesDirectory.appendingPathComponent(applicationUUID)
177 |
178 | guard let appIAPDirectory = FileManager.directories(bundlePath).first,
179 | let json = NSDictionary(contentsOf: bundlePath.appendingPathComponent("\(appIAPDirectory)/Info.plist")),
180 | let bundleID = json["CFBundleIdentifier"] as? String else {
181 | return nil
182 | }
183 |
184 | let name = (json["CFBundleName"] as? String) ?? "Unknow"
185 | let icon: NSImage?
186 | if let bundleIcons = json["CFBundleIcons"] as? [String: Any],
187 | let primaryIcon = bundleIcons["CFBundlePrimaryIcon"] as? [String: Any],
188 | let iconFiles = primaryIcon["CFBundleIconFiles"] as? [String],
189 | let iconFile = iconFiles.last {
190 | icon = NSImage(contentsOf: bundlePath.appendingPathComponent("\(appIAPDirectory)/\(iconFile)@3x.png"))
191 | } else {
192 | icon = nil
193 | }
194 |
195 | return ApplicationBundle(bundleID: bundleID, appName: name, appIcon: icon)
196 | }
197 |
198 | class func findApplicationDataPath(deviceUDID: String, bundleID: String) -> URL? {
199 | let directory = Simulator.applicationsDataPath(deviceUDID: deviceUDID)
200 |
201 | let plist = ".com.apple.mobile_container_manager.metadata.plist"
202 | for udid in FileManager.directories(directory) {
203 | let dataPath = directory.appendingPathComponent(udid)
204 | let plistPath = dataPath.appendingPathComponent(plist)
205 | guard let json = NSDictionary(contentsOf: plistPath),
206 | let metaDataIdentifier = json["MCMMetadataIdentifier"] as? String,
207 | metaDataIdentifier == bundleID else {
208 | continue
209 | }
210 |
211 | return dataPath
212 | }
213 | return nil
214 | }
215 |
216 | // MARK: AppGroup
217 |
218 | class func appGroups(deviceUDID: String) -> [AppGroupModel] {
219 | let devicePath = Simulator.devicesDirectory.appendingPathComponent("\(deviceUDID)")
220 | let directory = devicePath.appendingPathComponent("/data/Containers/Shared/AppGroup")
221 | return FileManager.directories(directory).map {
222 | let appGroup = AppGroupModel()
223 | appGroup.location = directory.appendingPathComponent($0)
224 |
225 | let plistPath = appGroup.location!.appendingPathComponent("/.com.apple.mobile_container_manager.metadata.plist")
226 | let json = NSDictionary(contentsOf: plistPath)
227 |
228 | appGroup.bundleIdentifier = json?["MCMMetadataIdentifier"] as? String ?? ""
229 |
230 | return appGroup
231 | }.filter {
232 | return !$0.bundleIdentifier.contains("com.apple")
233 | }
234 | }
235 | }
236 |
--------------------------------------------------------------------------------
/TySimulator/TySimulator-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | //
2 | // TySimulator-Bridging-Header.h
3 | // TySimulator
4 | //
5 | // Created by ty0x2333 on 2016/11/24.
6 | // Copyright © 2016年 ty0x2333. All rights reserved.
7 | //
8 |
9 | #ifndef TySimulator_Bridging_Header_h
10 | #define TySimulator_Bridging_Header_h
11 |
12 | #import // required for MD5
13 | #import
14 |
15 | #endif /* TySimulator_Bridging_Header_h */
16 |
--------------------------------------------------------------------------------
/TySimulator/TySimulator.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.automation.apple-events
6 |
7 | com.apple.security.cs.disable-library-validation
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/TySimulator/Utils/Application.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Application.swift
3 | // TySimulator
4 | //
5 | // Created by ty0x2333 on 2016/12/1.
6 | // Copyright © 2016年 ty0x2333. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension NSApplication {
12 | func showFeedbackWindow() {
13 | DevMateKit.showFeedbackDialog(nil, in: .modalMode)
14 | }
15 |
16 | @objc func showPreferencesWindow() {
17 | (NSApp.delegate as? AppDelegate)?.mainMenuController?.closePopover(sender: nil)
18 | let windowController = Preference.sharedWindowController
19 | windowController.select(at: 0)
20 | windowController.showWindow(nil)
21 | NSApp.activate(ignoringOtherApps: true)
22 | }
23 |
24 | @objc func showAboutWindow() {
25 | (NSApp.delegate as? AppDelegate)?.mainMenuController?.closePopover(sender: nil)
26 | NSApp.orderFrontStandardAboutPanel(nil)
27 | NSApp.activate(ignoringOtherApps: true)
28 | }
29 |
30 | func checkForUpdates() {
31 | DM_SUUpdater.shared().checkForUpdates(NSApp)
32 | }
33 |
34 | @discardableResult
35 | public class func toggleDockIcon(showIcon state: Bool) -> Bool {
36 | return NSApp.setActivationPolicy(state ? .regular : .accessory)
37 | }
38 |
39 | }
40 |
41 | // MARK: Launch
42 | extension NSApplication {
43 |
44 | class var isLaunchAtStartup: Bool {
45 | set {
46 | if newValue == isLaunchAtStartup {
47 | return
48 | }
49 | guard let loginItemsRef = LSSharedFileListCreate( nil, kLSSharedFileListSessionLoginItems.takeRetainedValue(), nil).takeRetainedValue() as LSSharedFileList? else {
50 | return
51 | }
52 | let itemReferences = itemReferencesInLoginItems()
53 | if newValue {
54 | let appUrl = NSURL(fileURLWithPath: Bundle.main.bundlePath)
55 | LSSharedFileListInsertItemURL(loginItemsRef, itemReferences.lastReference, nil, nil, appUrl, nil, nil)
56 | } else if let itemRef = itemReferences.existingReference {
57 | LSSharedFileListItemRemove(loginItemsRef, itemRef)
58 | }
59 | }
60 |
61 | get {
62 | return itemReferencesInLoginItems().existingReference != nil
63 | }
64 | }
65 |
66 | private class func itemReferencesInLoginItems() -> (existingReference: LSSharedFileListItem?, lastReference: LSSharedFileListItem?) {
67 | let appURL: NSURL = NSURL.fileURL(withPath: Bundle.main.bundlePath) as NSURL
68 | let loginItemsRef = LSSharedFileListCreate(nil, kLSSharedFileListSessionLoginItems.takeRetainedValue(), nil).takeRetainedValue()
69 | let loginItems: NSArray = LSSharedFileListCopySnapshot(loginItemsRef, nil).takeRetainedValue() as NSArray
70 |
71 | let lastItemRef: LSSharedFileListItem = loginItems.lastObject as! LSSharedFileListItem
72 |
73 | for ref in loginItems.map({ $0 as! LSSharedFileListItem }) {
74 | if let itemURL = LSSharedFileListItemCopyResolvedURL(ref, 0, nil),
75 | (itemURL.takeRetainedValue() as NSURL).isEqual(appURL) {
76 | return (ref, lastItemRef)
77 | }
78 | }
79 | return (nil, lastItemRef)
80 | }
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/TySimulator/Utils/FileManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FileManager.swift
3 | // TySimulator
4 | //
5 | // Created by ty0x2333 on 2016/11/13.
6 | // Copyright © 2016年 ty0x2333. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension FileManager {
12 |
13 | class func directories(_ path: URL) -> [String] {
14 | let results = try? FileManager.default.contentsOfDirectory(atPath: path.path).filter {
15 | return isDirectory(path.appendingPathComponent("\($0)").path, name: $0)
16 | }
17 |
18 | return results ?? []
19 | }
20 |
21 | class func isDirectory(_ path: String, name: String) -> Bool {
22 | var flag = ObjCBool(false)
23 | FileManager.default.fileExists(atPath: path, isDirectory: &flag)
24 |
25 | return flag.boolValue && !name.hasPrefix(".")
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/TySimulator/Utils/MD5.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MD5.swift
3 | // TySimulator
4 | //
5 | // Created by ty0x2333 on 2016/11/24.
6 | // Copyright © 2016年 ty0x2333. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension String {
12 | func md5() -> String {
13 | let str = cString(using: String.Encoding.utf8)
14 | let strLen = CUnsignedInt(lengthOfBytes(using: String.Encoding.utf8))
15 | let digestLen = Int(CC_MD5_DIGEST_LENGTH)
16 | let result = UnsafeMutablePointer.allocate(capacity: digestLen)
17 |
18 | CC_MD5(str!, strLen, result)
19 |
20 | let hash = NSMutableString()
21 | for idx in 0 ..< digestLen {
22 | hash.appendFormat("%02x", result[idx])
23 | }
24 | // https://stackoverflow.com/a/48570455/5701954
25 | // result.deinitialize()
26 |
27 | return String(format: hash as String)
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/TySimulator/Utils/Process.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Process.swift
3 | // TySimulator
4 | //
5 | // Created by ty0x2333 on 2016/11/13.
6 | // Copyright © 2016年 ty0x2333. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Process {
12 | class func output(launchPath: String, arguments: [String], directoryPath: URL? = nil) -> String {
13 | let data = outputData(launchPath: launchPath, arguments: arguments, directoryPath: directoryPath)
14 | return String(data: data, encoding: .utf8) ?? ""
15 | }
16 |
17 | class func outputData(launchPath: String, arguments: [String], directoryPath: URL? = nil) -> Data {
18 | let output = Pipe()
19 |
20 | let task = Process()
21 | task.launchPath = launchPath
22 | task.arguments = arguments
23 | task.standardOutput = output
24 |
25 | if let path = directoryPath?.removeTrailingSlash?.path {
26 | task.currentDirectoryPath = path
27 | }
28 |
29 | task.launch()
30 |
31 | // For some reason [task waitUntilExit]; does not return sometimes. Therefore this rather hackish solution:
32 | var count = 0
33 | while task.isRunning && count < 10 {
34 | Thread.sleep(forTimeInterval: 0.1)
35 | count += 1
36 | }
37 |
38 | return output.fileHandleForReading.readDataToEndOfFile()
39 | }
40 |
41 | @discardableResult
42 | class func execute(_ script: String) -> String {
43 | let scriptCLI = Script.transformedScript(script)
44 | log.info("run script: \(scriptCLI)")
45 | return output(launchPath: "/bin/sh", arguments: ["-c", scriptCLI])
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/TySimulator/Utils/ShortcutMonitor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ShortcutMonitor.swift
3 | // TySimulator
4 | //
5 | // Created by ty0x2333 on 2016/11/24.
6 | // Copyright © 2016年 ty0x2333. All rights reserved.
7 | //
8 |
9 | import MASShortcut
10 |
11 | extension MASShortcutMonitor {
12 | func register(command: CommandModel) {
13 | guard command.key != nil else {
14 | log.warning("register faild, command shortcut is nil")
15 | return
16 | }
17 | register(command.key, withAction: {
18 | NSSound(named: "Ping")?.play()
19 | log.debug("script: \(command.script)")
20 | Process.execute(command.script)
21 | })
22 | }
23 |
24 | func unregister(command: CommandModel) {
25 | guard command.key != nil else {
26 | log.warning("unregister faild, command shortcut is nil")
27 | return
28 | }
29 | unregisterShortcut(command.key)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/TySimulator/Utils/TextView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TextView.swift
3 | // TySimulator
4 | //
5 | // Created by ty0x2333 on 2016/11/30.
6 | // Copyright © 2016年 ty0x2333. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | private var placeHolderKey = 0
12 | extension NSTextView {
13 | static func awake() {
14 | let swizzlingClosure: () = {
15 | NSTextView.swizzleBecomeFirstResponder
16 | NSTextView.swizzleDraw
17 | }()
18 |
19 | swizzlingClosure
20 | }
21 |
22 | private var placeHolder: NSAttributedString? {
23 | get {
24 | return objc_getAssociatedObject(self, &placeHolderKey) as? NSAttributedString
25 | }
26 |
27 | set {
28 | objc_setAssociatedObject(self, &placeHolderKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_COPY_NONATOMIC)
29 | }
30 | }
31 |
32 | var placeHolderString: String? {
33 | set {
34 | if let string = newValue {
35 | placeHolder = NSAttributedString(string: string, attributes: [NSAttributedString.Key.foregroundColor: NSColor.gray])
36 | needsDisplay = true
37 | }
38 | }
39 |
40 | get {
41 | return placeHolder?.string
42 | }
43 | }
44 |
45 | private static let swizzleBecomeFirstResponder: Void = {
46 | let original = #selector(NSTextView.becomeFirstResponder)
47 | let replacement = #selector(NSTextView.swizzle_becomeFirstResponder)
48 | swizzleFunction(original: original, replacement: replacement)
49 | }()
50 |
51 | private static let swizzleDraw: Void = {
52 | let original = #selector(NSTextView.draw(_:))
53 | let replacement = #selector(NSTextView.swizzle_draw(_:))
54 | swizzleFunction(original: original, replacement: replacement)
55 | }()
56 |
57 | private class func swizzleFunction(original: Selector, replacement: Selector) {
58 | guard let originalMethod: Method = class_getInstanceMethod(NSTextView.self, original),
59 | let replacementMethod: Method = class_getInstanceMethod(NSTextView.self, replacement) else {
60 | return
61 | }
62 |
63 | let originalImplementation: IMP = method_getImplementation(originalMethod)
64 | let originalArgTypes = method_getTypeEncoding(originalMethod)
65 |
66 | let replacementImplementation: IMP = method_getImplementation(replacementMethod)
67 | let replacementArgTypes = method_getTypeEncoding(replacementMethod)
68 |
69 | if class_addMethod(NSTextView.self, original, replacementImplementation, replacementArgTypes) {
70 | class_replaceMethod(NSTextView.self, replacement, originalImplementation, originalArgTypes)
71 | } else {
72 | method_exchangeImplementations(originalMethod, replacementMethod)
73 | }
74 | }
75 |
76 | @objc func swizzle_becomeFirstResponder() -> Bool {
77 | needsDisplay = true
78 | return swizzle_becomeFirstResponder()
79 | }
80 |
81 | @objc func swizzle_draw(_ dirtyRect: NSRect) {
82 | swizzle_draw(dirtyRect)
83 | guard string.isEmpty else {
84 | return
85 | }
86 | if let placeHolder = placeHolder {
87 | placeHolder.draw(in: NSRect(x: 5, y: 0, width: bounds.size.width - 2 * 5, height: bounds.size.height))
88 | }
89 | }
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/TySimulator/Utils/URL.swift:
--------------------------------------------------------------------------------
1 | //
2 | // URL.swift
3 | // TySimulator
4 | //
5 | // Created by ty0x2333 on 2016/11/13.
6 | // Copyright © 2016年 ty0x2333. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension URL {
12 | var removeTrailingSlash: URL? {
13 | guard absoluteString.hasSuffix("/") else {
14 | return self
15 | }
16 | var urlString = absoluteString
17 | urlString.removeLast()
18 | return URL(string: urlString)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/TySimulator/Utils/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // TySimulator
4 | //
5 | // Created by ty0x2333 on 2016/12/3.
6 | // Copyright © 2016年 ty0x2333. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | extension NSViewController {
12 | static func awake() {
13 | let swizzlingClosure: () = {
14 | NSViewController.swizzleViewDidAppear
15 | NSViewController.swizzleViewDidDisappear
16 | }()
17 |
18 | swizzlingClosure
19 | }
20 |
21 | private static let swizzleViewDidAppear: Void = {
22 | let original = #selector(NSViewController.viewDidAppear)
23 | let replacement = #selector(NSViewController.ty_viewDidAppear)
24 | swizzleFunction(original: original, replacement: replacement)
25 | }()
26 |
27 | private static let swizzleViewDidDisappear: Void = {
28 | let original = #selector(NSViewController.viewDidDisappear)
29 | let replacement = #selector(NSViewController.ty_viewDidDisappear)
30 | swizzleFunction(original: original, replacement: replacement)
31 | }()
32 |
33 | private class func swizzleFunction(original: Selector, replacement: Selector) {
34 | guard let originalMethod: Method = class_getInstanceMethod(NSViewController.self, original),
35 | let replacementMethod: Method = class_getInstanceMethod(NSViewController.self, replacement) else {
36 | return
37 | }
38 |
39 | let originalImplementation: IMP = method_getImplementation(originalMethod)
40 | let originalArgTypes = method_getTypeEncoding(originalMethod)
41 |
42 | let replacementImplementation: IMP = method_getImplementation(replacementMethod)
43 | let replacementArgTypes = method_getTypeEncoding(replacementMethod)
44 |
45 | if class_addMethod(NSViewController.self, original, replacementImplementation, replacementArgTypes) {
46 | class_replaceMethod(NSViewController.self, replacement, originalImplementation, originalArgTypes)
47 | } else {
48 | method_exchangeImplementations(originalMethod, replacementMethod)
49 | }
50 | }
51 |
52 | @objc func ty_viewDidAppear() {
53 | ty_viewDidAppear()
54 |
55 | guard className != "NSTouchBarViewController" else {
56 | return
57 | }
58 | if NSApp.windows.contains(where: { $0.isVisible }) {
59 | NSApplication.toggleDockIcon(showIcon: true)
60 | }
61 | }
62 |
63 | @objc func ty_viewDidDisappear() {
64 | ty_viewDidDisappear()
65 |
66 | guard className != "NSTouchBarViewController" else {
67 | return
68 | }
69 | if NSApp.windows.contains(where: { $0.isVisible && String(describing: type(of: $0)) != "NSStatusBarWindow" }) {
70 | return
71 | }
72 | NSApplication.toggleDockIcon(showIcon: false)
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/TySimulator/main.swift:
--------------------------------------------------------------------------------
1 | //
2 | // main.swift
3 | // TySimulator
4 | //
5 | // Created by ty0x2333 on 2016/11/13.
6 | // Copyright © 2016年 ty0x2333. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import SwiftyBeaver
11 |
12 | let log = SwiftyBeaver.self
13 | let console = ConsoleDestination()
14 | log.addDestination(console)
15 |
16 | _ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)
17 |
--------------------------------------------------------------------------------
/TySimulator/zh-Hans.lproj/Localizable.strings:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/TySimulator/zh-Hans.lproj/Localizable.strings
--------------------------------------------------------------------------------
/TySimulator/zh-Hant.lproj/Localizable.strings:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/TySimulator/zh-Hant.lproj/Localizable.strings
--------------------------------------------------------------------------------
/TySimulatorTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/TySimulatorTests/RecentTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RecentTests.swift
3 | // TySimulator
4 | //
5 | // Created by ty0x2333 on 2017/7/1.
6 | // Copyright © 2017年 ty0x2333. All rights reserved.
7 | //
8 |
9 | import Quick
10 | import Nimble
11 | @testable import TySimulator
12 |
13 | class RecentTests: QuickSpec {
14 | override func spec() {
15 | describe("LRU", closure: {
16 | let datas: [Int] = Array(0..<10)
17 | let capacity = 5
18 | context("append datas", {
19 | let lru = LRU(capacity: capacity)
20 | for data in datas {
21 | lru[data] = 0
22 | }
23 | it("count should be equal capacity", closure: {
24 | expect(lru.count).to(equal(capacity))
25 | })
26 |
27 | it("data", closure: {
28 | expect(lru.datas.map{ $0.key }).to(equal(Array(5..<10).reversed()))
29 | })
30 | })
31 |
32 | context("get value", {
33 | let lru = LRU(capacity: capacity)
34 | for data in datas {
35 | lru[data] = data
36 | }
37 | it("value should be equal seted", closure: {
38 | for data in 5..<10 {
39 | expect(lru[data]).to(equal(data))
40 | }
41 | })
42 | })
43 |
44 | context("change value", {
45 | let lru = LRU(capacity: capacity)
46 | lru[10] = 10
47 | lru[10] = 20
48 | it("count", closure: {
49 | expect(lru.count).to(equal(1))
50 | })
51 | it("value should be changed", closure: {
52 | expect(lru[10]).to(equal(20))
53 | })
54 | })
55 |
56 | })
57 |
58 | describe("LRU-K", closure: {
59 | let datas: [Int] = Array(0..<10)
60 | let capacity = 5
61 | context("append datas", {
62 | let lruk = LRUK(capacity: capacity, bufferSize: 10)
63 | for data in datas {
64 | lruk[data] = 0
65 | }
66 | it("should be empty", closure: {
67 | expect(lruk.count).to(equal(0))
68 | })
69 |
70 | it("has cache", closure: {
71 | lruk[0] = 3
72 | expect(lruk.count).to(equal(1))
73 | expect(lruk[0]).to(equal(3))
74 | })
75 | })
76 | })
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/TySimulatorTests/Resource/script-format.txt:
--------------------------------------------------------------------------------
1 | open ${{
2 | "device": "booted",
3 | "application": "com.tianyiyan.note"
4 | }}
--------------------------------------------------------------------------------
/TySimulatorTests/ScriptTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ScriptTests.swift
3 | // TySimulator
4 | //
5 | // Created by ty0x2333 on 2016/11/25.
6 | // Copyright © 2016年 ty0x2333. All rights reserved.
7 | //
8 |
9 | import Quick
10 | import Nimble
11 | @testable import TySimulator
12 |
13 | func content(of filename: String) -> String {
14 | let path = Bundle(for: ScriptTests.self).path(forResource: filename, ofType: "txt")!
15 | return try! String(contentsOfFile: path)
16 | }
17 |
18 | class ScriptTests: QuickSpec {
19 | override func spec() {
20 | describe("parsing script", closure: {
21 |
22 | // context("input valid command", {
23 | // let script = content(of: "script-format")
24 | // it("should be parsed", closure: {
25 | // let transformed = Script.transformedScript(script)
26 | // expect(transformed).toNot(equal(script))
27 | // })
28 | // })
29 |
30 | context("input not valid command", {
31 | afterEach {
32 | print("after not")
33 | }
34 | let command = "${{xxxx}}"
35 | let script = "open \(command)"
36 | it("should be parsed", closure: {
37 | let transformed = Script.transformedScript(script)
38 | expect(transformed).to(equal(script))
39 | })
40 | })
41 | })
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/TySimulatorTests/SimulatorTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SimulatorTests.swift
3 | // TySimulatorTests
4 | //
5 | // Created by ty0x2333 on 2018/10/8.
6 | // Copyright © 2018 ty0x2333. All rights reserved.
7 | //
8 |
9 | import Quick
10 | import Nimble
11 | @testable import TySimulator
12 |
13 | class SimulatorTests: QuickSpec {
14 | override func spec() {
15 | describe("Simulator", closure: {
16 | it("list Devices", closure: {
17 |
18 | })
19 | })
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TySimulatorTests/TestFinder/Empty:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/TySimulatorTests/TestFinder/Empty
--------------------------------------------------------------------------------
/TySimulatorTests/TestFinder/SubFinder/Empty:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/TySimulatorTests/TestFinder/SubFinder/Empty
--------------------------------------------------------------------------------
/TySimulatorTests/TestFinder/SubFinder/SubSubFinder/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/TySimulatorTests/TestFinder/SubFinder/SubSubFinder/.gitkeep
--------------------------------------------------------------------------------
/TySimulatorTests/TestFinder/SubFinder2/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/TySimulatorTests/TestFinder/SubFinder2/.gitkeep
--------------------------------------------------------------------------------
/TySimulatorTests/UtilTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UtilTests.swift
3 | // TySimulatorTests
4 | //
5 | // Created by ty0x2333 on 2018/10/8.
6 | // Copyright © 2018 ty0x2333. All rights reserved.
7 | //
8 |
9 | import Quick
10 | import Nimble
11 | @testable import TySimulator
12 |
13 | class UtilTests: QuickSpec {
14 | override func spec() {
15 | describe("URL", closure: {
16 | context("removeTrailingSlash", {
17 | let result = "https://tyy.sh"
18 | it("removed trailing slash", closure: {
19 | expect(URL(string: "https://tyy.sh/")?.removeTrailingSlash?.absoluteString).to(equal(result))
20 | })
21 |
22 | it("do nothing", closure: {
23 | expect(URL(string: "https://tyy.sh")?.removeTrailingSlash?.absoluteString).to(equal(result))
24 | })
25 | })
26 | })
27 |
28 | describe("String", closure: {
29 | context("md5", {
30 | it("had generated", closure: {
31 | expect("https://tyy.sh".md5()).to(equal("651949dc4ffadf4d933ac565796de4cd"))
32 | })
33 | })
34 | })
35 |
36 | describe("FileManager", closure: {
37 | context("directories", {
38 | let bundle = Bundle(for: UtilTests.self)
39 | it("find directories", closure: {
40 | let path = bundle.path(forResource: "TestFinder", ofType: nil)
41 | let directories = FileManager.directories(URL(string: path!)!)
42 | expect(directories.contains("SubFinder")).to(beTrue())
43 | expect(directories.contains("SubFinder2")).to(beTrue())
44 | expect(directories.count).to(equal(2))
45 | })
46 | })
47 | })
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | codecov:
2 | token: 3f211234-cd1c-409e-8d35-9f9787cf714f
3 |
--------------------------------------------------------------------------------
/resources/DMG Canvas.dmgCanvas/Disk Image:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/resources/DMG Canvas.dmgCanvas/Disk Image
--------------------------------------------------------------------------------
/resources/DMG Canvas.dmgCanvas/QuickLook/Preview.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/resources/DMG Canvas.dmgCanvas/QuickLook/Preview.jpg
--------------------------------------------------------------------------------
/resources/dmg-background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/resources/dmg-background.png
--------------------------------------------------------------------------------
/resources/icon-off.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/resources/icon-off.png
--------------------------------------------------------------------------------
/resources/icon-on.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/resources/icon-on.png
--------------------------------------------------------------------------------
/resources/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/resources/logo.png
--------------------------------------------------------------------------------
/resources/menu-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/resources/menu-icon.png
--------------------------------------------------------------------------------
/resources/tysimulator-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/resources/tysimulator-logo.png
--------------------------------------------------------------------------------
/resources/tysimulator-logo.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ty0x2333/TySimulator/92719fffc648133d4c68627e62ff97af377c59fc/resources/tysimulator-logo.psd
--------------------------------------------------------------------------------
/scripts/ExportOptions.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | destination
6 | export
7 | method
8 | developer-id
9 | provisioningProfiles
10 |
11 | com.tianyiyan.TySimulator
12 | TySimulator Develop ID
13 |
14 | signingCertificate
15 | Developer ID Application
16 | signingStyle
17 | manual
18 | teamID
19 | 9KYD7S7AZY
20 |
21 |
22 |
--------------------------------------------------------------------------------
/scripts/build:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | CUR_DIR=$(cd `dirname $0`; pwd)
4 |
5 | WORKSPACE_PATH=$CUR_DIR/../TySimulator.xcworkspace
6 | BUILD_PATH=$CUR_DIR/../build
7 | ARCHIVE_PATH=$BUILD_PATH/TySimulator.xcarchive
8 | DST_DSYMS_PATH=$BUILD_PATH/dSYMs
9 | XCODE_BUILD="xcodebuild archive ONLY_ACTIVE_ARCH=NO -workspace $WORKSPACE_PATH -scheme TySimulator -archivePath $ARCHIVE_PATH -configuration Release $*"
10 |
11 | if [ ! -d $BUILD_PATH ]; then
12 | mkdir $BUILD_PATH
13 | fi
14 | if [ ! -d $DST_DSYMS_PATH ]; then
15 | mkdir $DST_DSYMS_PATH
16 | fi
17 |
18 | if [ ! $(command -v xcpretty) ]; then
19 | $XCODE_BUILD
20 | else
21 | set -o pipefail && $XCODE_BUILD | xcpretty
22 | fi
23 |
24 | cp -r $BUILD_PATH/TySimulator.xcarchive/Products/Applications/TySimulator.app $BUILD_PATH/TySimulator-unsign.app
25 |
26 | cd $ARCHIVE_PATH/dSYMs
27 | for filename in $(ls $ARCHIVE_PATH/dSYMs); do
28 | zip -r $BUILD_PATH/dSYMs/$filename.zip $filename
29 | done
30 |
--------------------------------------------------------------------------------
/scripts/export_archive:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | CUR_DIR=$(cd `dirname $0`; pwd)
4 | BUILD_PATH=$CUR_DIR/../build
5 | ARCHIVE_PATH=$BUILD_PATH/TySimulator.xcarchive
6 |
7 | cp $CUR_DIR/ExportOptions.plist $BUILD_PATH/ExportOptions.plist
8 |
9 | xcodebuild -exportArchive -archivePath $ARCHIVE_PATH -exportOptionsPlist $BUILD_PATH/ExportOptions.plist -exportPath $BUILD_PATH
10 |
11 | codesign -vvvv $BUILD_PATH/TySimulator.app
12 |
--------------------------------------------------------------------------------
/scripts/generate_app_icon:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | SRC_FILE=$1
3 | DST_PATH=$2
4 |
5 | if [ -z "$DST_PATH" ]; then
6 | DST_PATH=$(dirname $0)
7 | fi
8 |
9 | error() {
10 | local red="\033[1;31m"
11 | local normal="\033[0m"
12 | echo "[${red}ERROR${normal}] $1"
13 | }
14 |
15 | # Check ImageMagick
16 | command -v convert >/dev/null 2>&1 || { error >&2 "The ImageMagick is not installed. Please install it first.see http://www.imagemagick.org/"; exit -1; }
17 |
18 | if [ -z $SRC_FILE ]
19 | then
20 | echo "No argument given"
21 | else
22 | convert "$SRC_FILE" -resize 16x16 "$DST_PATH/Icon-16.png"
23 | convert "$SRC_FILE" -resize 32x32 "$DST_PATH/Icon-16@2x.png"
24 | convert "$SRC_FILE" -resize 32x32 "$DST_PATH/Icon-32.png"
25 | convert "$SRC_FILE" -resize 64x64 "$DST_PATH/Icon-32@2x.png"
26 | convert "$SRC_FILE" -resize 128x128 "$DST_PATH/Icon-128.png"
27 | convert "$SRC_FILE" -resize 256x256 "$DST_PATH/Icon-128@2x.png"
28 | convert "$SRC_FILE" -resize 256x256 "$DST_PATH/Icon-256.png"
29 | convert "$SRC_FILE" -resize 512x512 "$DST_PATH/Icon-256@2x.png"
30 | convert "$SRC_FILE" -resize 512x512 "$DST_PATH/Icon-512.png"
31 | convert "$SRC_FILE" -resize 1024x1024 "$DST_PATH/Icon-512@2x.png"
32 | fi
33 |
--------------------------------------------------------------------------------
/scripts/generate_menu_icon:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | SRC_FILE=$1
3 | DST_PATH=$2
4 |
5 | if [ -z "$DST_PATH" ]; then
6 | DST_PATH=$(dirname $0)
7 | fi
8 |
9 | error() {
10 | local red="\033[1;31m"
11 | local normal="\033[0m"
12 | echo "[${red}ERROR${normal}] $1"
13 | }
14 |
15 | # Check ImageMagick
16 | command -v convert >/dev/null 2>&1 || { error >&2 "The ImageMagick is not installed. Please install it first.see http://www.imagemagick.org/"; exit -1; }
17 |
18 | if [ -z $SRC_FILE ]
19 | then
20 | echo "No argument given"
21 | else
22 | convert "$SRC_FILE" -resize 18 "$DST_PATH/Icon-18.png"
23 | convert "$SRC_FILE" -resize 36 "$DST_PATH/Icon-18@2x.png"
24 | convert "$SRC_FILE" -resize 54 "$DST_PATH/Icon-18@3x.png"
25 | fi
26 |
--------------------------------------------------------------------------------
/scripts/i18n:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | required_ver="1.2.0"
3 |
4 | if [ ! $(command -v tystrings) ]; then
5 | echo "❗️ tystrings command not found. https://github.com/luckytianyiyan/TyStrings"
6 | echo "sudo pip install tystrings"
7 | exit 1
8 | elif [ ! `python -c "import tystrings; from pkg_resources import parse_version; print 1 if parse_version(tystrings.__version__) >= parse_version(\"${required_ver}\") else 0;"` ]
9 | then
10 | echo "❗️ require tystrings version $required_ver"
11 | echo "sudo pip install tystrings --upgrade"
12 | exit 1
13 | fi
14 |
15 | CUR_DIR=$(dirname $0)
16 | PROJ_DIR=$CUR_DIR/../TySimulator
17 | tystrings generate $(find $PROJ_DIR -name \*.m -o -name \*.swift) -o $PROJ_DIR/Base.lproj $PROJ_DIR/zh-Hans.lproj $PROJ_DIR/zh-Hant.lproj
18 | echo "Press any key to continue"
19 | read -n 1 -s -p " "
20 | tystrings translate $PROJ_DIR/zh-Hans.lproj/Localizable.strings $PROJ_DIR/zh-Hant.lproj/Localizable.strings --src-lang zh --dst-lang cht --appid 20160709000024959 --secret ke4UYwwvvgV9iQEIVjrC
21 |
--------------------------------------------------------------------------------
/scripts/pack:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | VERSION=`sed -n '/MARKETING_VERSION/{s/MARKETING_VERSION = //;s/;//;s/^[[:space:]]*//;p;q;}' ./TySimulator.xcodeproj/project.pbxproj`
3 | dmgcanvas resources/DMG\ Canvas.dmgCanvas build/TySimulator\ $VERSION.dmg -volumeName TySimulator\ $VERSION -setTextString textObject3 $VERSION
--------------------------------------------------------------------------------