├── Cartfile
├── Cartfile.resolved
├── script
├── schemes.awk
├── xctool.awk
├── bootstrap
├── LICENSE.md
├── README.md
└── cibuild
├── SimRecorder.xcodeproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
└── project.pbxproj
├── SimRecorder.xcworkspace
└── contents.xcworkspacedata
├── SimRecorder.framework
└── SimRecorder.framework
│ ├── SimRecorder.framework.h
│ └── Info.plist
├── Makefile
├── README.md
├── SimRecorder
├── Components.plist
├── Info.plist
└── main.swift
└── .gitignore
/Cartfile:
--------------------------------------------------------------------------------
1 | github "nomothetis/OptionKit" ~> 1.0.0
2 |
--------------------------------------------------------------------------------
/Cartfile.resolved:
--------------------------------------------------------------------------------
1 | github "nomothetis/OptionKit" "1.0.0"
2 |
--------------------------------------------------------------------------------
/script/schemes.awk:
--------------------------------------------------------------------------------
1 | BEGIN {
2 | FS = "\n";
3 | }
4 |
5 | /Schemes:/ {
6 | while (getline && $0 != "") {
7 | sub(/^ +/, "");
8 | print "'" $0 "'";
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/SimRecorder.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/SimRecorder.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/script/xctool.awk:
--------------------------------------------------------------------------------
1 | # Exit statuses:
2 | #
3 | # 0 - No errors found.
4 | # 1 - Wrong SDK. Retry with SDK `iphonesimulator`.
5 | # 2 - Missing target.
6 |
7 | BEGIN {
8 | status = 0;
9 | }
10 |
11 | {
12 | print;
13 | }
14 |
15 | /Testing with the '(.+)' SDK is not yet supported/ {
16 | status = 1;
17 | }
18 |
19 | /does not contain a target named/ {
20 | status = 2;
21 | }
22 |
23 | END {
24 | exit status;
25 | }
26 |
--------------------------------------------------------------------------------
/SimRecorder.framework/SimRecorder.framework/SimRecorder.framework.h:
--------------------------------------------------------------------------------
1 | //
2 | // SimRecorder.framework.h
3 | // SimRecorder.framework
4 | //
5 | // Created by koki-miki on 11/7/15.
6 | // Copyright © 2015 giginet. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for SimRecorder.framework.
12 | FOUNDATION_EXPORT double SimRecorder_frameworkVersionNumber;
13 |
14 | //! Project version string for SimRecorder.framework.
15 | FOUNDATION_EXPORT const unsigned char SimRecorder_frameworkVersionString[];
16 |
17 | // In this header, you should import all the public headers of your framework using statements like #import
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | SWIFT_FILE?=SimRecorder/main.swift
2 | CARTAGE_PATH?=Carthage/Build/Mac
3 | FRAMEWORK_PATH?=@executable_path/../Frameworks/
4 | EXECUTABLE_PATH?=build/simrec
5 | TARGET_SDK?=macosx
6 | BUILD_TOOL?=xcrun
7 | BUILD_OPTION?=-sdk $(TARGET_SDK) swiftc $(SWIFT_FILE) -F$(CARTAGE_PATH) -Xlinker -rpath -Xlinker "$(FRAMEWORK_PATH)" -o $(EXECUTABLE_PATH)
8 |
9 | bootstrap:
10 | carthage update
11 |
12 | build: bootstrap
13 | mkdir -p build
14 | $(BUILD_TOOL) $(BUILD_OPTION)
15 |
16 | clean:
17 | rm $(EXECUTABLE_PATH)
18 |
19 | prefix_install: build
20 | mkdir -p "$(PREFIX)/Frameworks" "$(PREFIX)/bin"
21 | cp -rf $(CARTAGE_PATH)/OptionKit.framework "$(PREFIX)/Frameworks/"
22 | cp build/simrec "$(PREFIX)/bin/"
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # iOS Sim Recorder
2 |
3 | ## Install
4 |
5 |
6 | ```
7 | $ brew install carthage
8 | $ brew tap giginet/utils
9 | $ brew install simrecorder
10 | ```
11 |
12 | ## Usage
13 |
14 | 1. Launch your iOS Simulator
15 | 2. Exec `simrec`
16 | 3. Stop recording with `Ctrl-C`
17 |
18 | ```
19 | $ simrec
20 | ```
21 |
22 | You'd like to stop recording, you send INTERRUPT signal(`Ctrl + C`), then an animation will be generated.
23 |
24 | ### Command Line Options
25 |
26 | |Option|Description|Default|
27 | |------|-----------|-------|
28 | |-q |Quality of animation(0...1.0) |1.0|
29 | |-f |Frame rate of animation |5|
30 | |-o |Output path|animation.gif|
31 | |-l |loop count of animation|0(Infinity)|
32 |
--------------------------------------------------------------------------------
/SimRecorder/Components.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | BundleIsVersionChecked
7 |
8 | BundleOverwriteAction
9 | upgrade
10 | ChildBundles
11 |
12 |
13 | BundleOverwriteAction
14 |
15 | RootRelativeBundlePath
16 | Library/Frameworks/SimRecorder.framework/Versions/A/Frameworks/OptionKit.framework
17 |
18 |
19 | RootRelativeBundlePath
20 | Library/Frameworks/SimRecorder.framework
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/script/bootstrap:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | export SCRIPT_DIR=$(dirname "$0")
4 |
5 | ##
6 | ## Bootstrap Process
7 | ##
8 |
9 | main ()
10 | {
11 | local submodules=$(git submodule status)
12 | local result=$?
13 |
14 | if [ "$result" -ne "0" ]
15 | then
16 | exit $result
17 | fi
18 |
19 | if [ -n "$submodules" ]
20 | then
21 | echo "*** Updating submodules..."
22 | update_submodules
23 | fi
24 | }
25 |
26 | bootstrap_submodule ()
27 | {
28 | local bootstrap="script/bootstrap"
29 |
30 | if [ -e "$bootstrap" ]
31 | then
32 | echo "*** Bootstrapping $name..."
33 | "$bootstrap" >/dev/null
34 | else
35 | update_submodules
36 | fi
37 | }
38 |
39 | update_submodules ()
40 | {
41 | git submodule sync --quiet && git submodule update --init && git submodule foreach --quiet bootstrap_submodule
42 | }
43 |
44 | export -f bootstrap_submodule
45 | export -f update_submodules
46 |
47 | main
48 |
--------------------------------------------------------------------------------
/SimRecorder.framework/SimRecorder.framework/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 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSHumanReadableCopyright
24 | Copyright © 2015 giginet. All rights reserved.
25 | NSPrincipalClass
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/script/LICENSE.md:
--------------------------------------------------------------------------------
1 | **Copyright (c) 2013 Justin Spahr-Summers**
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7 | the Software, and to permit persons to whom the Software is furnished to do so,
8 | subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19 |
--------------------------------------------------------------------------------
/SimRecorder/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 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1
25 | LSMinimumSystemVersion
26 | $(MACOSX_DEPLOYMENT_TARGET)
27 | NSHumanReadableCopyright
28 | Copyright © 2015 giginet. All rights reserved.
29 | NSMainNibFile
30 | MainMenu
31 | NSPrincipalClass
32 | NSApplication
33 |
34 |
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ### https://raw.github.com/github/gitignore/95bdb4e35df6f7b736f2e95dd53ef11f2a8a439e/Swift.gitignore
2 |
3 | # Xcode
4 | #
5 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
6 |
7 | ## Build generated
8 | build/
9 | DerivedData
10 |
11 | ## Various settings
12 | *.pbxuser
13 | !default.pbxuser
14 | *.mode1v3
15 | !default.mode1v3
16 | *.mode2v3
17 | !default.mode2v3
18 | *.perspectivev3
19 | !default.perspectivev3
20 | xcuserdata
21 |
22 | ## Other
23 | *.xccheckout
24 | *.moved-aside
25 | *.xcuserstate
26 | *.xcscmblueprint
27 |
28 | ## Obj-C/Swift specific
29 | *.hmap
30 | *.ipa
31 | *.xcuserstate
32 | *.xcscmblueprint
33 |
34 | # CocoaPods
35 | #
36 | # We recommend against adding the Pods directory to your .gitignore. However
37 | # you should judge for yourself, the pros and cons are mentioned at:
38 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
39 | #
40 | # Pods/
41 |
42 | # Carthage
43 | #
44 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
45 | Carthage/Checkouts
46 | Carthage/Build
47 |
48 | # fastlane
49 | #
50 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
51 | # screenshots whenever they are needed.
52 |
53 | fastlane/report.xml
54 | fastlane/screenshots
55 |
56 | build/
57 | *.gif
58 |
--------------------------------------------------------------------------------
/script/README.md:
--------------------------------------------------------------------------------
1 | # objc-build-scripts
2 |
3 | This project is a collection of scripts created with two goals:
4 |
5 | 1. To standardize how Objective-C projects are bootstrapped after cloning
6 | 1. To easily build Objective-C projects on continuous integration servers
7 |
8 | ## Scripts
9 |
10 | Right now, there are two important scripts: [`bootstrap`](#bootstrap) and
11 | [`cibuild`](#cibuild). Both are Bash scripts, to maximize compatibility and
12 | eliminate pesky system configuration issues (like setting up a working Ruby
13 | environment).
14 |
15 | The structure of the scripts on disk is meant to follow that of a typical Ruby
16 | project:
17 |
18 | ```
19 | script/
20 | bootstrap
21 | cibuild
22 | ```
23 |
24 | ### bootstrap
25 |
26 | This script is responsible for bootstrapping (initializing) your project after
27 | it's been checked out. Here, you should install or clone any dependencies that
28 | are required for a working build and development environment.
29 |
30 | By default, the script will verify that [xctool][] is installed, then initialize
31 | and update submodules recursively. If any submodules contain `script/bootstrap`,
32 | that will be run as well.
33 |
34 | To check that other tools are installed, you can set the `REQUIRED_TOOLS`
35 | environment variable before running `script/bootstrap`, or edit it within the
36 | script directly. Note that no installation is performed automatically, though
37 | this can always be added within your specific project.
38 |
39 | ### cibuild
40 |
41 | This script is responsible for building the project, as you would want it built
42 | for continuous integration. This is preferable to putting the logic on the CI
43 | server itself, since it ensures that any changes are versioned along with the
44 | source.
45 |
46 | By default, the script will run [`bootstrap`](#bootstrap), look for any Xcode
47 | workspace or project in the working directory, then build all targets/schemes
48 | (as found by `xcodebuild -list`) using [xctool][].
49 |
50 | You can also specify the schemes to build by passing them into the script:
51 |
52 | ```sh
53 | script/cibuild ReactiveCocoa-Mac ReactiveCocoa-iOS
54 | ```
55 |
56 | As with the `bootstrap` script, there are several environment variables that can
57 | be used to customize behavior. They can be set on the command line before
58 | invoking the script, or the defaults changed within the script directly.
59 |
60 | ## Getting Started
61 |
62 | To add the scripts to your project, read the contents of this repository into
63 | a `script` folder:
64 |
65 | ```
66 | $ git remote add objc-build-scripts https://github.com/jspahrsummers/objc-build-scripts.git
67 | $ git fetch objc-build-scripts
68 | $ git read-tree --prefix=script/ -u objc-build-scripts/master
69 | ```
70 |
71 | Then commit the changes, to incorporate the scripts into your own repository's
72 | history. You can also freely tweak the scripts for your specific project's
73 | needs.
74 |
75 | To merge in upstream changes later:
76 |
77 | ```
78 | $ git fetch -p objc-build-scripts
79 | $ git merge --ff --squash -Xsubtree=script objc-build-scripts/master
80 | ```
81 |
82 | [xctool]: https://github.com/facebook/xctool
83 |
--------------------------------------------------------------------------------
/script/cibuild:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | export SCRIPT_DIR=$(dirname "$0")
4 |
5 | ##
6 | ## Configuration Variables
7 | ##
8 |
9 | SCHEMES="$@"
10 |
11 | config ()
12 | {
13 | # The workspace to build.
14 | #
15 | # If not set and no workspace is found, the -workspace flag will not be passed
16 | # to `xctool`.
17 | #
18 | # Only one of `XCWORKSPACE` and `XCODEPROJ` needs to be set. The former will
19 | # take precedence.
20 | : ${XCWORKSPACE=$(find_pattern "*.xcworkspace")}
21 |
22 | # The project to build.
23 | #
24 | # If not set and no project is found, the -project flag will not be passed
25 | # to `xctool`.
26 | #
27 | # Only one of `XCWORKSPACE` and `XCODEPROJ` needs to be set. The former will
28 | # take precedence.
29 | : ${XCODEPROJ=$(find_pattern "*.xcodeproj")}
30 |
31 | # A bootstrap script to run before building.
32 | #
33 | # If this file does not exist, it is not considered an error.
34 | : ${BOOTSTRAP="$SCRIPT_DIR/bootstrap"}
35 |
36 | # Extra options to pass to xctool.
37 | : ${XCTOOL_OPTIONS="RUN_CLANG_STATIC_ANALYZER=NO"}
38 |
39 | # A whitespace-separated list of default schemes to build.
40 | #
41 | # Individual names can be quoted to avoid word splitting.
42 | : ${SCHEMES:=$(xcodebuild -list -project "$XCODEPROJ" 2>/dev/null | awk -f "$SCRIPT_DIR/schemes.awk")}
43 |
44 | # A whitespace-separated list of executables that must be present and locatable.
45 | : ${REQUIRED_TOOLS="xctool"}
46 |
47 | export XCWORKSPACE
48 | export XCODEPROJ
49 | export BOOTSTRAP
50 | export XCTOOL_OPTIONS
51 | export SCHEMES
52 | export REQUIRED_TOOLS
53 | }
54 |
55 | ##
56 | ## Build Process
57 | ##
58 |
59 | main ()
60 | {
61 | config
62 |
63 | if [ -n "$REQUIRED_TOOLS" ]
64 | then
65 | echo "*** Checking dependencies..."
66 | check_deps
67 | fi
68 |
69 | if [ -f "$BOOTSTRAP" ]
70 | then
71 | echo "*** Bootstrapping..."
72 | "$BOOTSTRAP" || exit $?
73 | fi
74 |
75 | echo "*** The following schemes will be built:"
76 | echo "$SCHEMES" | xargs -n 1 echo " "
77 | echo
78 |
79 | echo "$SCHEMES" | xargs -n 1 | (
80 | local status=0
81 |
82 | while read scheme
83 | do
84 | build_scheme "$scheme" || status=1
85 | done
86 |
87 | exit $status
88 | )
89 | }
90 |
91 | check_deps ()
92 | {
93 | for tool in $REQUIRED_TOOLS
94 | do
95 | which -s "$tool"
96 | if [ "$?" -ne "0" ]
97 | then
98 | echo "*** Error: $tool not found. Please install it and cibuild again."
99 | exit 1
100 | fi
101 | done
102 | }
103 |
104 | find_pattern ()
105 | {
106 | ls -d $1 2>/dev/null | head -n 1
107 | }
108 |
109 | run_xctool ()
110 | {
111 | if [ -n "$XCWORKSPACE" ]
112 | then
113 | xctool -workspace "$XCWORKSPACE" $XCTOOL_OPTIONS "$@" 2>&1
114 | elif [ -n "$XCODEPROJ" ]
115 | then
116 | xctool -project "$XCODEPROJ" $XCTOOL_OPTIONS "$@" 2>&1
117 | else
118 | echo "*** No workspace or project file found."
119 | exit 1
120 | fi
121 | }
122 |
123 | parse_build ()
124 | {
125 | awk -f "$SCRIPT_DIR/xctool.awk" 2>&1 >/dev/null
126 | }
127 |
128 | build_scheme ()
129 | {
130 | local scheme=$1
131 |
132 | echo "*** Building and testing $scheme..."
133 | echo
134 |
135 | local sdkflag=
136 | local action=test
137 |
138 | # Determine whether we can run unit tests for this target.
139 | run_xctool -scheme "$scheme" run-tests | parse_build
140 |
141 | local awkstatus=$?
142 |
143 | if [ "$awkstatus" -eq "1" ]
144 | then
145 | # SDK not found, try for iphonesimulator.
146 | sdkflag="-sdk iphonesimulator"
147 |
148 | # Determine whether the unit tests will run with iphonesimulator
149 | run_xctool $sdkflag -scheme "$scheme" run-tests | parse_build
150 |
151 | awkstatus=$?
152 |
153 | if [ "$awkstatus" -ne "0" ]
154 | then
155 | # Unit tests will not run on iphonesimulator.
156 | sdkflag=""
157 | fi
158 | fi
159 |
160 | if [ "$awkstatus" -ne "0" ]
161 | then
162 | # Unit tests aren't supported.
163 | action=build
164 | fi
165 |
166 | run_xctool $sdkflag -scheme "$scheme" $action
167 | }
168 |
169 | export -f build_scheme
170 | export -f run_xctool
171 | export -f parse_build
172 |
173 | main
174 |
--------------------------------------------------------------------------------
/SimRecorder.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 54210D771BEB88C10018FC44 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54210D761BEB88C10018FC44 /* main.swift */; };
11 | /* End PBXBuildFile section */
12 |
13 | /* Begin PBXFileReference section */
14 | 54210D661BEB887C0018FC44 /* SimRecorder.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SimRecorder.app; sourceTree = BUILT_PRODUCTS_DIR; };
15 | 54210D701BEB887C0018FC44 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
16 | 54210D761BEB88C10018FC44 /* main.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; };
17 | /* End PBXFileReference section */
18 |
19 | /* Begin PBXFrameworksBuildPhase section */
20 | 54210D631BEB887C0018FC44 /* Frameworks */ = {
21 | isa = PBXFrameworksBuildPhase;
22 | buildActionMask = 2147483647;
23 | files = (
24 | );
25 | runOnlyForDeploymentPostprocessing = 0;
26 | };
27 | /* End PBXFrameworksBuildPhase section */
28 |
29 | /* Begin PBXGroup section */
30 | 54210D5D1BEB887C0018FC44 = {
31 | isa = PBXGroup;
32 | children = (
33 | 54210D681BEB887C0018FC44 /* SimRecorder */,
34 | 54210D671BEB887C0018FC44 /* Products */,
35 | );
36 | sourceTree = "";
37 | };
38 | 54210D671BEB887C0018FC44 /* Products */ = {
39 | isa = PBXGroup;
40 | children = (
41 | 54210D661BEB887C0018FC44 /* SimRecorder.app */,
42 | );
43 | name = Products;
44 | sourceTree = "";
45 | };
46 | 54210D681BEB887C0018FC44 /* SimRecorder */ = {
47 | isa = PBXGroup;
48 | children = (
49 | 54210D701BEB887C0018FC44 /* Info.plist */,
50 | 54210D761BEB88C10018FC44 /* main.swift */,
51 | );
52 | path = SimRecorder;
53 | sourceTree = "";
54 | };
55 | /* End PBXGroup section */
56 |
57 | /* Begin PBXNativeTarget section */
58 | 54210D651BEB887C0018FC44 /* SimRecorder */ = {
59 | isa = PBXNativeTarget;
60 | buildConfigurationList = 54210D731BEB887C0018FC44 /* Build configuration list for PBXNativeTarget "SimRecorder" */;
61 | buildPhases = (
62 | 54210D621BEB887C0018FC44 /* Sources */,
63 | 54210D631BEB887C0018FC44 /* Frameworks */,
64 | 54210D641BEB887C0018FC44 /* Resources */,
65 | );
66 | buildRules = (
67 | );
68 | dependencies = (
69 | );
70 | name = SimRecorder;
71 | productName = SimRecorder;
72 | productReference = 54210D661BEB887C0018FC44 /* SimRecorder.app */;
73 | productType = "com.apple.product-type.application";
74 | };
75 | /* End PBXNativeTarget section */
76 |
77 | /* Begin PBXProject section */
78 | 54210D5E1BEB887C0018FC44 /* Project object */ = {
79 | isa = PBXProject;
80 | attributes = {
81 | LastSwiftUpdateCheck = 0710;
82 | LastUpgradeCheck = 0710;
83 | ORGANIZATIONNAME = giginet;
84 | TargetAttributes = {
85 | 54210D651BEB887C0018FC44 = {
86 | CreatedOnToolsVersion = 7.1;
87 | };
88 | };
89 | };
90 | buildConfigurationList = 54210D611BEB887C0018FC44 /* Build configuration list for PBXProject "SimRecorder" */;
91 | compatibilityVersion = "Xcode 3.2";
92 | developmentRegion = English;
93 | hasScannedForEncodings = 0;
94 | knownRegions = (
95 | en,
96 | Base,
97 | );
98 | mainGroup = 54210D5D1BEB887C0018FC44;
99 | productRefGroup = 54210D671BEB887C0018FC44 /* Products */;
100 | projectDirPath = "";
101 | projectRoot = "";
102 | targets = (
103 | 54210D651BEB887C0018FC44 /* SimRecorder */,
104 | );
105 | };
106 | /* End PBXProject section */
107 |
108 | /* Begin PBXResourcesBuildPhase section */
109 | 54210D641BEB887C0018FC44 /* Resources */ = {
110 | isa = PBXResourcesBuildPhase;
111 | buildActionMask = 2147483647;
112 | files = (
113 | );
114 | runOnlyForDeploymentPostprocessing = 0;
115 | };
116 | /* End PBXResourcesBuildPhase section */
117 |
118 | /* Begin PBXSourcesBuildPhase section */
119 | 54210D621BEB887C0018FC44 /* Sources */ = {
120 | isa = PBXSourcesBuildPhase;
121 | buildActionMask = 2147483647;
122 | files = (
123 | 54210D771BEB88C10018FC44 /* main.swift in Sources */,
124 | );
125 | runOnlyForDeploymentPostprocessing = 0;
126 | };
127 | /* End PBXSourcesBuildPhase section */
128 |
129 | /* Begin XCBuildConfiguration section */
130 | 54210D711BEB887C0018FC44 /* Debug */ = {
131 | isa = XCBuildConfiguration;
132 | buildSettings = {
133 | ALWAYS_SEARCH_USER_PATHS = NO;
134 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
135 | CLANG_CXX_LIBRARY = "libc++";
136 | CLANG_ENABLE_MODULES = YES;
137 | CLANG_ENABLE_OBJC_ARC = YES;
138 | CLANG_WARN_BOOL_CONVERSION = YES;
139 | CLANG_WARN_CONSTANT_CONVERSION = YES;
140 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
141 | CLANG_WARN_EMPTY_BODY = YES;
142 | CLANG_WARN_ENUM_CONVERSION = YES;
143 | CLANG_WARN_INT_CONVERSION = YES;
144 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
145 | CLANG_WARN_UNREACHABLE_CODE = YES;
146 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
147 | CODE_SIGN_IDENTITY = "-";
148 | COPY_PHASE_STRIP = NO;
149 | DEBUG_INFORMATION_FORMAT = dwarf;
150 | ENABLE_STRICT_OBJC_MSGSEND = YES;
151 | ENABLE_TESTABILITY = YES;
152 | GCC_C_LANGUAGE_STANDARD = gnu99;
153 | GCC_DYNAMIC_NO_PIC = NO;
154 | GCC_NO_COMMON_BLOCKS = YES;
155 | GCC_OPTIMIZATION_LEVEL = 0;
156 | GCC_PREPROCESSOR_DEFINITIONS = (
157 | "DEBUG=1",
158 | "$(inherited)",
159 | );
160 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
161 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
162 | GCC_WARN_UNDECLARED_SELECTOR = YES;
163 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
164 | GCC_WARN_UNUSED_FUNCTION = YES;
165 | GCC_WARN_UNUSED_VARIABLE = YES;
166 | MACOSX_DEPLOYMENT_TARGET = 10.10;
167 | MTL_ENABLE_DEBUG_INFO = YES;
168 | ONLY_ACTIVE_ARCH = YES;
169 | SDKROOT = macosx;
170 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
171 | };
172 | name = Debug;
173 | };
174 | 54210D721BEB887C0018FC44 /* Release */ = {
175 | isa = XCBuildConfiguration;
176 | buildSettings = {
177 | ALWAYS_SEARCH_USER_PATHS = NO;
178 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
179 | CLANG_CXX_LIBRARY = "libc++";
180 | CLANG_ENABLE_MODULES = YES;
181 | CLANG_ENABLE_OBJC_ARC = YES;
182 | CLANG_WARN_BOOL_CONVERSION = YES;
183 | CLANG_WARN_CONSTANT_CONVERSION = YES;
184 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
185 | CLANG_WARN_EMPTY_BODY = YES;
186 | CLANG_WARN_ENUM_CONVERSION = YES;
187 | CLANG_WARN_INT_CONVERSION = YES;
188 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
189 | CLANG_WARN_UNREACHABLE_CODE = YES;
190 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
191 | CODE_SIGN_IDENTITY = "-";
192 | COPY_PHASE_STRIP = NO;
193 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
194 | ENABLE_NS_ASSERTIONS = NO;
195 | ENABLE_STRICT_OBJC_MSGSEND = YES;
196 | GCC_C_LANGUAGE_STANDARD = gnu99;
197 | GCC_NO_COMMON_BLOCKS = YES;
198 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
199 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
200 | GCC_WARN_UNDECLARED_SELECTOR = YES;
201 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
202 | GCC_WARN_UNUSED_FUNCTION = YES;
203 | GCC_WARN_UNUSED_VARIABLE = YES;
204 | MACOSX_DEPLOYMENT_TARGET = 10.10;
205 | MTL_ENABLE_DEBUG_INFO = NO;
206 | SDKROOT = macosx;
207 | };
208 | name = Release;
209 | };
210 | 54210D741BEB887C0018FC44 /* Debug */ = {
211 | isa = XCBuildConfiguration;
212 | buildSettings = {
213 | CLANG_ENABLE_MODULES = YES;
214 | COMBINE_HIDPI_IMAGES = YES;
215 | INFOPLIST_FILE = SimRecorder/Info.plist;
216 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
217 | PRODUCT_BUNDLE_IDENTIFIER = "net.gigi-net.SimRecorder";
218 | PRODUCT_NAME = "$(TARGET_NAME)";
219 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
220 | };
221 | name = Debug;
222 | };
223 | 54210D751BEB887C0018FC44 /* Release */ = {
224 | isa = XCBuildConfiguration;
225 | buildSettings = {
226 | CLANG_ENABLE_MODULES = YES;
227 | COMBINE_HIDPI_IMAGES = YES;
228 | INFOPLIST_FILE = SimRecorder/Info.plist;
229 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
230 | PRODUCT_BUNDLE_IDENTIFIER = "net.gigi-net.SimRecorder";
231 | PRODUCT_NAME = "$(TARGET_NAME)";
232 | };
233 | name = Release;
234 | };
235 | /* End XCBuildConfiguration section */
236 |
237 | /* Begin XCConfigurationList section */
238 | 54210D611BEB887C0018FC44 /* Build configuration list for PBXProject "SimRecorder" */ = {
239 | isa = XCConfigurationList;
240 | buildConfigurations = (
241 | 54210D711BEB887C0018FC44 /* Debug */,
242 | 54210D721BEB887C0018FC44 /* Release */,
243 | );
244 | defaultConfigurationIsVisible = 0;
245 | defaultConfigurationName = Release;
246 | };
247 | 54210D731BEB887C0018FC44 /* Build configuration list for PBXNativeTarget "SimRecorder" */ = {
248 | isa = XCConfigurationList;
249 | buildConfigurations = (
250 | 54210D741BEB887C0018FC44 /* Debug */,
251 | 54210D751BEB887C0018FC44 /* Release */,
252 | );
253 | defaultConfigurationIsVisible = 0;
254 | defaultConfigurationName = Release;
255 | };
256 | /* End XCConfigurationList section */
257 | };
258 | rootObject = 54210D5E1BEB887C0018FC44 /* Project object */;
259 | }
260 |
--------------------------------------------------------------------------------
/SimRecorder/main.swift:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env swift -framework OptionKit -F Carthage/Build/Mac
2 | import Cocoa
3 | import CoreGraphics
4 | import OptionKit
5 |
6 | extension NSImage {
7 | var CGImage: CGImageRef {
8 | get {
9 | let imageData = self.TIFFRepresentation
10 | let source = CGImageSourceCreateWithData(imageData!, nil)
11 | let maskRef = CGImageSourceCreateImageAtIndex(source!, 0, nil)!
12 | return maskRef;
13 | }
14 | }
15 | }
16 |
17 | enum TargetSimulator: String {
18 | case iOS = "ios"
19 | case iOSWatch = "ios-watch"
20 | case Android64ARM = "android64-arm"
21 | case Android64X86 = "android64-x86"
22 | case Android64MIPS = "android64-mips"
23 |
24 | func hasBundleId() -> Bool {
25 | return self == .iOS || self == .iOSWatch
26 | }
27 |
28 | func toFilterValue() -> String {
29 | switch self {
30 | case .iOS: return "com.apple.iphonesimulator"
31 | case .iOSWatch: return "com.apple.watchsimulator"
32 | case .Android64ARM: return "emulator64-arm"
33 | case .Android64X86: return "emulator64-x86"
34 | case .Android64MIPS: return "emulator64-mips"
35 | }
36 | }
37 | }
38 |
39 | class Storage {
40 | private let DefaultTempDirName = "simrec"
41 | private var url : NSURL?
42 |
43 | init() {
44 | self.url = createTemporaryDirectory()
45 | }
46 |
47 | func createTemporaryDirectory() -> NSURL? {
48 | let url : NSURL = NSURL(fileURLWithPath: NSTemporaryDirectory())
49 | let pathURL : NSURL = url.URLByAppendingPathComponent(DefaultTempDirName)
50 | let fileManager = NSFileManager.defaultManager()
51 | do {
52 | try fileManager.createDirectoryAtURL(pathURL, withIntermediateDirectories: true, attributes: nil)
53 | return pathURL
54 | } catch {
55 | return nil
56 | }
57 | }
58 |
59 | func basePath() -> String? {
60 | return self.url?.absoluteString;
61 | }
62 |
63 | func writeToFile(image : CGImageRef, filename : String) -> NSData {
64 | let bitmapRep : NSBitmapImageRep = NSBitmapImageRep(CGImage: image)
65 | let fileURL : NSURL = NSURL(string: filename, relativeToURL: self.url)!
66 | let properties = Dictionary()
67 | let data : NSData = bitmapRep.representationUsingType(NSBitmapImageFileType.NSPNGFileType, properties: properties)!
68 | if !data.writeToFile(fileURL.path!, atomically: false) {
69 | print("write to file failed")
70 | }
71 | return data
72 | }
73 | }
74 |
75 | class Converter {
76 | typealias ConvertFinishedCallback = (data: NSData?, succeed: Bool) -> ()
77 |
78 | func createGIF(with images: [NSImage], quality: Float = 1.0, loopCount: UInt = 0, frameDelay: Double, destinationURL : NSURL, callback : ConvertFinishedCallback?) {
79 | let frameCount = images.count
80 | let animationProperties =
81 | [kCGImagePropertyGIFDictionary as String: [kCGImagePropertyGIFLoopCount as String: loopCount],
82 | kCGImageDestinationLossyCompressionQuality as String: quality]
83 | let frameProperties = [kCGImagePropertyGIFDictionary as String: [kCGImagePropertyGIFDelayTime as String: frameDelay, kCGImagePropertyGIFUnclampedDelayTime as String: frameDelay]]
84 |
85 | let destination = CGImageDestinationCreateWithURL(destinationURL, kUTTypeGIF, frameCount, nil)
86 | if let destination = destination {
87 | CGImageDestinationSetProperties(destination, animationProperties)
88 | for var i = 0; i < frameCount; ++i {
89 | autoreleasepool {
90 | let image = images[i];
91 | CGImageDestinationAddImage(destination, image.CGImage, frameProperties)
92 | }
93 | }
94 | if CGImageDestinationFinalize(destination) {
95 | if let callback = callback {
96 | callback(data: NSData(contentsOfURL: destinationURL), succeed: true)
97 | }
98 | } else {
99 | if let callback = callback {
100 | callback(data: nil, succeed: false)
101 | }
102 | }
103 | }
104 | }
105 | }
106 |
107 | class Recorder {
108 | private var windowID : CGWindowID?
109 | private var frame: UInt = 0
110 | private var timer: NSTimer!
111 | private let storage: Storage = Storage()
112 | private let converter: Converter = Converter()
113 | private var images: [NSImage] = []
114 | var quality: Float = 1.0
115 | var fps: UInt = 5
116 | var outputPath: String = "animation.gif"
117 | var loopCount: UInt = 0
118 | var targetSimulator: TargetSimulator
119 |
120 | convenience init() {
121 | self.init(targetSimulator: .iOS)
122 | }
123 |
124 | init(targetSimulator: TargetSimulator) {
125 | self.targetSimulator = targetSimulator
126 | self.windowID = self.simulatorWindowID()
127 | }
128 |
129 | private func simulatorWindowID() -> CGWindowID? {
130 | var windowIDs : [CGWindowID] = []
131 |
132 | let simulators : [NSRunningApplication]
133 | if targetSimulator.hasBundleId() {
134 | simulators = NSWorkspace.sharedWorkspace().runningApplications.filter({
135 | (app : NSRunningApplication) in
136 | return app.bundleIdentifier == targetSimulator.toFilterValue()
137 | })
138 | } else {
139 | simulators = NSWorkspace.sharedWorkspace().runningApplications.filter({
140 | (app : NSRunningApplication) in
141 | return app.localizedName == targetSimulator.toFilterValue()
142 | })
143 | }
144 |
145 | if (simulators.count > 0) {
146 | let simulator : NSRunningApplication = simulators.first!
147 |
148 | let windowArray : CFArrayRef = CGWindowListCopyWindowInfo(CGWindowListOption.OptionOnScreenOnly, 0)!
149 | let windows : NSArray = windowArray as NSArray
150 | for window in windows {
151 | let dict = window as! Dictionary
152 | let windowIDNumber: NSNumber = dict["kCGWindowNumber"] as! NSNumber
153 | let ownerPID: NSNumber = dict["kCGWindowOwnerPID"] as! NSNumber
154 | if ownerPID.intValue == Int32(simulator.processIdentifier) {
155 | windowIDs.append(CGWindowID(windowIDNumber.intValue))
156 | }
157 | }
158 | }
159 | if windowIDs.count > 0 {
160 | return windowIDs.last;
161 | }
162 | return nil
163 | }
164 |
165 | func secPerFrame() -> Double {
166 | return 1.0 / Double(self.fps)
167 | }
168 |
169 | func outputURL() -> NSURL {
170 | return NSURL(string: self.outputPath)!
171 | }
172 |
173 | func isAttachSimulator() -> Bool {
174 | return self.windowID != nil
175 | }
176 |
177 | @objc private func takeScreenshot() {
178 | let imageRef : CGImageRef = CGWindowListCreateImage(CGRectNull, CGWindowListOption.OptionIncludingWindow, windowID!, CGWindowImageOption.BoundsIgnoreFraming)!
179 | let newRef = removeAlpha(imageRef)
180 | let data : NSData = self.storage.writeToFile(newRef, filename: "\(self.frame).png")
181 | ++self.frame
182 | let image = NSImage(data: data)
183 | if let image = image {
184 | self.images.append(image)
185 | }
186 | }
187 |
188 | private func removeAlpha(imageRef: CGImageRef) -> CGImageRef {
189 | let width = CGImageGetWidth(imageRef)
190 | let height = CGImageGetHeight(imageRef)
191 | let bitmapContext: CGContextRef? = CGBitmapContextCreate(nil,
192 | width,
193 | height,
194 | CGImageGetBitsPerComponent(imageRef),
195 | CGImageGetBytesPerRow(imageRef),
196 | CGImageGetColorSpace(imageRef),
197 | CGImageAlphaInfo.NoneSkipLast.rawValue | CGBitmapInfo.ByteOrder32Little.rawValue)
198 | let rect: CGRect = CGRectMake(0, 0, CGFloat(width), CGFloat(height))
199 | if let bitmapContext = bitmapContext {
200 | CGContextDrawImage(bitmapContext, rect, imageRef);
201 | return CGBitmapContextCreateImage(bitmapContext)!
202 | }
203 | return imageRef
204 | }
205 |
206 | func startCapture() {
207 | self.timer = NSTimer.scheduledTimerWithTimeInterval(self.secPerFrame(), target: self, selector: "takeScreenshot", userInfo: nil, repeats: true)
208 | }
209 |
210 | func endCapture(callback : Converter.ConvertFinishedCallback?) {
211 | self.timer?.invalidate()
212 | let destinationURL : NSURL = NSURL(fileURLWithPath: self.outputPath)
213 | self.converter.createGIF(with: self.images, quality: self.quality, loopCount: self.loopCount, frameDelay: self.secPerFrame(), destinationURL: destinationURL, callback: callback)
214 | }
215 | }
216 |
217 | class Command {
218 | typealias SignalCallback = @convention(c) (Int32) -> Void
219 |
220 | static func execute(arguments : [String]) {
221 | let frameRateOption = Option(trigger: OptionTrigger.Mixed("f", "fps"), numberOfParameters: 1, helpDescription: "Recording frames per second")
222 | let outputPathOption = Option(trigger: OptionTrigger.Mixed("o", "outputPath"), numberOfParameters: 1, helpDescription: "Animation output path")
223 | let qualityOption = Option(trigger: OptionTrigger.Mixed("q", "quality"), numberOfParameters: 1, helpDescription: "Quality of animations 0.0 ~ 1.0")
224 | let loopCountOption = Option(trigger: OptionTrigger.Mixed("l", "loopCount"), numberOfParameters: 1, helpDescription: "Loop count of animations. if you passed 0, it animate eternally")
225 | let targetSimulatorOption = Option(trigger: OptionTrigger.Mixed("t", "targetSimulator"), numberOfParameters: 1, helpDescription: "Target simulator [\(TargetSimulator.iOS.rawValue)|\(TargetSimulator.Android64ARM.rawValue)|\(TargetSimulator.Android64X86.rawValue)|\(TargetSimulator.Android64MIPS.rawValue)]")
226 |
227 | let parser = OptionParser(definitions: [frameRateOption, outputPathOption, qualityOption, loopCountOption, targetSimulatorOption])
228 |
229 | do {
230 | let (options, _) = try parser.parse(arguments)
231 |
232 | let recorder : Recorder
233 |
234 | if let targetSimulator = options[targetSimulatorOption]?.first {
235 | if let targetSimulatorEnum = TargetSimulator.init(rawValue: targetSimulator) {
236 | recorder = Recorder(targetSimulator: targetSimulatorEnum)
237 | } else {
238 | recorder = Recorder()
239 | }
240 | } else {
241 | recorder = Recorder()
242 | }
243 |
244 | guard recorder.isAttachSimulator() else {
245 | print("iOS simulator seems not to launch")
246 | exit(EXIT_FAILURE)
247 | }
248 |
249 | if let frameRate: UInt = options[frameRateOption]?.flatMap({ UInt($0) }).first {
250 | recorder.fps = frameRate
251 | }
252 |
253 | if let outputPath = options[outputPathOption]?.first {
254 | recorder.outputPath = outputPath
255 | }
256 |
257 | if let quality: Float = options[qualityOption]?.flatMap({ Float($0) }).first {
258 | recorder.quality = quality
259 | }
260 |
261 | if let loopCount: UInt = options[loopCountOption]?.flatMap({ UInt($0) }).first {
262 | recorder.loopCount = loopCount
263 | }
264 |
265 | let callback : @convention(block) (Int32) -> Void = { (Int32) -> Void in
266 | recorder.endCapture({ (data : NSData?, succeed : Bool) in
267 | if succeed {
268 | print("Gif animation generated")
269 | exit(EXIT_SUCCESS)
270 | } else {
271 | print("Gif animation generation is failed")
272 | exit(EXIT_FAILURE)
273 | }
274 | })
275 | }
276 |
277 | // Convert Objective-C block to C function pointer
278 | let imp = imp_implementationWithBlock(unsafeBitCast(callback, AnyObject.self))
279 | signal(SIGINT, unsafeBitCast(imp, SignalCallback.self))
280 | recorder.startCapture()
281 | autoreleasepool {
282 | NSRunLoop.currentRunLoop().run()
283 | }
284 | } catch {
285 | }
286 | }
287 | }
288 |
289 | let actualArguments = Array(Process.arguments[1..