Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15fe5a06c2 | ||
|
|
8d4c7bd705 | ||
|
|
b0d85abfa6 | ||
|
|
4295017324 | ||
|
|
d80d70215a | ||
|
|
a46f905aa6 | ||
|
|
453c50d505 | ||
|
|
61c7d9f2b3 | ||
|
|
34707e7743 | ||
|
|
9a9b9651a1 | ||
|
|
d4576a8739 | ||
|
|
87824c2650 | ||
|
|
283e735f90 | ||
|
|
202f968b98 | ||
|
|
62df1cd5ba | ||
|
|
f24c28c848 | ||
|
|
8fb465d3ca | ||
|
|
29dff2320b | ||
|
|
a40b30016b | ||
|
|
409b262a4f | ||
|
|
73197e4378 | ||
|
|
f51dbe4295 | ||
|
|
597aaa1bab | ||
|
|
b9656da8a5 | ||
|
|
422db1898c | ||
|
|
56691791b8 | ||
|
|
de2a5d3cd7 | ||
|
|
49fc10191c | ||
|
|
bf8aaa8198 | ||
|
|
9a4cdc9c94 | ||
|
|
f6153fe818 | ||
|
|
f2fa9e18ea | ||
|
|
aa013be6e6 | ||
|
|
7e21b131ac | ||
|
|
97681f3a0d | ||
|
|
e476cc407c | ||
|
|
f2e03bbf1f | ||
|
|
a6cef32c9a | ||
|
|
6d94897c21 | ||
|
|
a065c78b5f | ||
|
|
83479719a4 | ||
|
|
fd4ede58ca | ||
|
|
7ed5a9831f | ||
|
|
42b7fd35ce | ||
|
|
42c97eafaa | ||
|
|
5b82be0546 | ||
|
|
1529058f52 | ||
|
|
3dcca3a922 | ||
|
|
9b8f230c46 | ||
|
|
7699d9e574 | ||
|
|
641e1c5066 | ||
|
|
617ef7c6a6 | ||
|
|
c2620b8321 | ||
|
|
9e91d136bf | ||
|
|
9960d2a2ff | ||
|
|
311e77e01a | ||
|
|
c08df921da | ||
|
|
ba4e55aac4 | ||
|
|
af16f03703 | ||
|
|
8189d54703 | ||
|
|
ac8514e3cb | ||
|
|
8f4b8da3ce | ||
|
|
8337b3e708 | ||
|
|
cb527baead | ||
|
|
ed10612e2c | ||
|
|
7d3eaeef83 | ||
|
|
dd5ecb37c4 | ||
|
|
636143ee97 | ||
|
|
28ff29bb96 | ||
|
|
a508c6d855 | ||
|
|
dbd6eabd24 | ||
|
|
ad0a5935f7 | ||
|
|
dcd2dc97a6 | ||
|
|
f92544c7d2 | ||
|
|
797fa0f492 | ||
|
|
ff52adf4f8 | ||
|
|
8bb49ada11 | ||
|
|
3f709b616b | ||
|
|
83694f8141 | ||
|
|
a4352aae4d | ||
|
|
2ac19336d6 | ||
|
|
63b09c5b27 | ||
|
|
b2c2f0837a | ||
|
|
335b5aef1f | ||
|
|
918e1248f2 | ||
|
|
442a791e0c | ||
|
|
573b93329d | ||
|
|
40359c5e32 | ||
|
|
3b01b50503 | ||
|
|
76b55cc230 | ||
|
|
e136cd96c1 | ||
|
|
5028eaed59 | ||
|
|
d18c713684 | ||
|
|
08e169eb23 | ||
|
|
611358e35c | ||
|
|
63275130bb | ||
|
|
7c9313e04d | ||
|
|
d466a0af07 | ||
|
|
c8adc184c9 | ||
|
|
d40a29df71 | ||
|
|
85820bf812 | ||
|
|
5d4d10ac8a | ||
|
|
eca018ad15 | ||
|
|
90e582a263 | ||
|
|
3717228e28 | ||
|
|
8c0997ddcd | ||
|
|
d722c9e18d | ||
|
|
94c9433b99 | ||
|
|
befef62b2f | ||
|
|
dc00c1e3e7 | ||
|
|
3555d60a9b | ||
|
|
bd8b9f8bfb | ||
|
|
72dcd76e37 | ||
|
|
fbae7f88b5 | ||
|
|
791a5b1ea1 | ||
|
|
8c5a82573b | ||
|
|
cc56a36bbd | ||
|
|
336dbeed98 | ||
|
|
f8613cdbfd | ||
|
|
a5961dd9b5 | ||
|
|
17a5866fdd | ||
|
|
5e77380636 | ||
|
|
845af246b5 | ||
|
|
87cac4e562 | ||
|
|
70eb4fd69d | ||
|
|
c5120bec37 | ||
|
|
24fc9ac0a9 | ||
|
|
2c0624f1e7 | ||
|
|
8b227f28f7 | ||
|
|
23e9c3f236 | ||
|
|
eefde316de | ||
|
|
a3cfd787de | ||
|
|
410d0d367b | ||
|
|
39b04d46a4 | ||
|
|
0e8647ad07 | ||
|
|
73091e7e8c | ||
|
|
16a2ab3941 | ||
|
|
39a2e8be78 | ||
|
|
8e6dadfa79 | ||
|
|
c2134585df | ||
|
|
dedcbd9d3e | ||
|
|
bd379677a2 | ||
|
|
466ebe2840 | ||
|
|
736887b945 | ||
|
|
993bfca0ae | ||
|
|
5ad87f7400 | ||
|
|
e862dbd6b6 | ||
|
|
befde5eb28 | ||
|
|
7fd3b8df1c | ||
|
|
cee501d9b4 | ||
|
|
a103c0fd45 | ||
|
|
bf71ae003f | ||
|
|
5b102ce3d3 | ||
|
|
c2012fa195 | ||
|
|
003fc8c1f8 | ||
|
|
9123033bb3 | ||
|
|
253144188c | ||
|
|
4ccb9bebbf | ||
|
|
49f647748c | ||
|
|
e2e8f0121d | ||
|
|
b5ea4c3e50 | ||
|
|
90d47004e7 | ||
|
|
450960eaaf | ||
|
|
44550baaa9 | ||
|
|
293dc26b2e | ||
|
|
109fa6f463 | ||
|
|
44f64e1a35 | ||
|
|
2e479837ed | ||
|
|
1b681a8730 | ||
|
|
f988ce0278 | ||
|
|
1ef5c87afc | ||
|
|
f5b99b6b9c | ||
|
|
485dd4b001 | ||
|
|
92e2c0d7cc | ||
|
|
48ffca57ec | ||
|
|
e83a996cd5 | ||
|
|
57191e3b9a | ||
|
|
eadcf3b3eb | ||
|
|
71e0646659 | ||
|
|
f2edee1669 | ||
|
|
11948bf618 | ||
|
|
4a9294a4b7 | ||
|
|
a9b8f753a2 | ||
|
|
05761c787c | ||
|
|
9a7b3b1325 | ||
|
|
b04a4e72ff | ||
|
|
b0b9a614f3 | ||
|
|
76ab786e94 | ||
|
|
8afcdc92d4 | ||
|
|
907a2cd59b | ||
|
|
a85b931d87 | ||
|
|
62f1b3e595 | ||
|
|
b9381ad027 | ||
|
|
98d01f7775 | ||
|
|
de39c87185 | ||
|
|
10a00b0d28 | ||
|
|
4d8788add3 | ||
|
|
9c019be322 | ||
|
|
80254513c4 | ||
|
|
5a13e44fdb | ||
|
|
ec0a462092 | ||
|
|
25c7b18147 | ||
|
|
fb238e0d1d | ||
|
|
bfc0b89926 | ||
|
|
2f0e219726 | ||
|
|
2305f33572 | ||
|
|
d17f2f9dee | ||
|
|
a28049a58c | ||
|
|
2d2f71dcab | ||
|
|
59506a2364 | ||
|
|
2cec53ab6b | ||
|
|
42ef67d939 | ||
|
|
30ec68cd4e | ||
|
|
941c414930 | ||
|
|
b918005555 | ||
|
|
c74fa4d5b0 | ||
|
|
1a71e040c0 | ||
|
|
236c9d5c6e | ||
|
|
862b8632e3 | ||
|
|
cdb5f51ab8 | ||
|
|
314f32388a | ||
|
|
39b0b429d6 | ||
|
|
23a12ed101 | ||
|
|
653a789110 | ||
|
|
04b2fccaef | ||
|
|
ec0124e255 | ||
|
|
132a1fc410 | ||
|
|
63297c8483 | ||
|
|
5fd2c29a73 | ||
|
|
e6e7ec9202 | ||
|
|
fcc770d820 | ||
|
|
4243bcd5d1 | ||
|
|
67b08cd8b9 | ||
|
|
e697938afc | ||
|
|
275f6df8c1 | ||
|
|
aea2067e1c | ||
|
|
b5241a0c30 | ||
|
|
6cac2348b4 | ||
|
|
0293df7748 | ||
|
|
391872ddb8 | ||
|
|
4bafefbe16 | ||
|
|
812f3f3717 | ||
|
|
e498736af6 | ||
|
|
1842a11f56 | ||
|
|
85f81ca8c1 | ||
|
|
b792629943 | ||
|
|
7df76fcd93 | ||
|
|
8b60e79406 | ||
|
|
fcb6c7386c | ||
|
|
2729fad47c | ||
|
|
41eb933e11 | ||
|
|
f759f719e6 | ||
|
|
74283a6763 | ||
|
|
0a19c5950a | ||
|
|
e8832ed746 | ||
|
|
6cc165b589 | ||
|
|
1c86c4485d | ||
|
|
127e5ed54a | ||
|
|
3fee5d0a62 | ||
|
|
c913b3f04f | ||
|
|
dfd30c0624 | ||
|
|
5117a6ede5 | ||
|
|
fb9c071243 | ||
|
|
d0a8f0e0f1 | ||
|
|
6bb6c40bec | ||
|
|
af3bc82d0f | ||
|
|
b9d5d94d70 | ||
|
|
43f4804194 | ||
|
|
be26ce9a8f | ||
|
|
ca562e2251 | ||
|
|
d7ee0916e9 | ||
|
|
3bc9e050f1 | ||
|
|
4b9bd8398b | ||
|
|
20a61dd9f4 | ||
|
|
dc4ecbf262 | ||
|
|
68f55235ec | ||
|
|
5434b653c7 | ||
|
|
25d7af9453 | ||
|
|
5584083809 | ||
|
|
9f9aec70b0 | ||
|
|
a8d1770871 | ||
|
|
d0d60b8ffc | ||
|
|
86f7112789 | ||
|
|
da1115e529 | ||
|
|
593da7ef3e | ||
|
|
de5377a95d | ||
|
|
7d7500b187 | ||
|
|
45dbd46da1 | ||
|
|
89d8bc6009 | ||
|
|
b99988836a | ||
|
|
3e94613eb3 | ||
|
|
832b9189f8 | ||
|
|
3464539bd3 | ||
|
|
815592117d | ||
|
|
b51341ef54 | ||
|
|
9ffbb5f155 | ||
|
|
9716867ade | ||
|
|
7c141c1fd2 | ||
|
|
42c4e7ca58 | ||
|
|
6a6ea4a79a | ||
|
|
5d0ca8451d | ||
|
|
4641f68eb8 | ||
|
|
dd33f57b9a | ||
|
|
f6b225ca9c | ||
|
|
4ac3f11ff8 | ||
|
|
62aaf27293 | ||
|
|
eac2d985e2 | ||
|
|
61ea7f3f1c | ||
|
|
40f7faa1b1 | ||
|
|
9e18b8021b | ||
|
|
ce95f2c153 | ||
|
|
fd50b71b16 | ||
|
|
39ab472cf6 | ||
|
|
48ba7eb2dc | ||
|
|
42edaa1bad | ||
|
|
955fb59156 | ||
|
|
2fbc67ef00 | ||
|
|
23564a3028 | ||
|
|
9a07d5a33a | ||
|
|
84c037f235 | ||
|
|
c13df82f5b | ||
|
|
b15d61a5bf | ||
|
|
5aff719984 | ||
|
|
b0c2978405 | ||
|
|
91dcb505de | ||
|
|
d1f35d938b | ||
|
|
359b822951 | ||
|
|
39580e4352 | ||
|
|
7b3d099449 | ||
|
|
b17c45e9ca | ||
|
|
4c7a2cecdd | ||
|
|
175074ece8 | ||
|
|
8a5ec513bf | ||
|
|
7cfb7f73a2 | ||
|
|
c608685737 | ||
|
|
3beca2fc72 | ||
|
|
c59aafb4bb | ||
|
|
256e40acdc | ||
|
|
39b1b65e16 | ||
|
|
1783abec13 | ||
|
|
983c65d399 | ||
|
|
4caa9672c9 | ||
|
|
15be6a3f75 | ||
|
|
d38eb0a3ba | ||
|
|
e8ea942ad2 | ||
|
|
cb68b10475 | ||
|
|
11c854ce44 | ||
|
|
9636f4742b | ||
|
|
5867317526 | ||
|
|
8a8f7b6903 | ||
|
|
61b6e25a91 | ||
|
|
50cff820de | ||
|
|
4e586a28c8 | ||
|
|
0702b4ed57 | ||
|
|
4a2132a682 | ||
|
|
ba81d1d6a9 | ||
|
|
402ce4b3ec | ||
|
|
fc713453ad | ||
|
|
b222ccffbb | ||
|
|
56ce7329d2 | ||
|
|
99ab6db557 | ||
|
|
d7adf96f3a | ||
|
|
0ae58c61a9 | ||
|
|
2ec7d1d2f1 | ||
|
|
ad1b9059ee | ||
|
|
4439c7cce6 | ||
|
|
b8a4093c75 | ||
|
|
ec3748b26e | ||
|
|
1c1f22e047 | ||
|
|
9aa9b0fc5a | ||
|
|
f8e2ca8500 | ||
|
|
3076e29335 | ||
|
|
fe616d86c5 | ||
|
|
bfa4856bbe | ||
|
|
5ea92a7b48 | ||
|
|
4ba71db7b2 | ||
|
|
c1e6aeb43c | ||
|
|
e3ffb66ef0 | ||
|
|
5daa82b0ba | ||
|
|
a8bcc6afba | ||
|
|
e6aa16ad01 | ||
|
|
ad0705d126 | ||
|
|
2f31f93bf8 | ||
|
|
ea3be613c5 | ||
|
|
b495246f34 | ||
|
|
814470f4bf | ||
|
|
82293a08b6 | ||
|
|
20d66336a7 | ||
|
|
c8441ec9fa | ||
|
|
f72135c227 | ||
|
|
100e6e4ce8 | ||
|
|
ec8be20d62 |
No files matched your search
@@ -0,0 +1,8 @@
|
|||||||
|
# ProBot No Response (https://probot.github.io/apps/no-response/)
|
||||||
|
|
||||||
|
daysUntilClose: 7
|
||||||
|
responseRequiredLabel: 'need more info'
|
||||||
|
closeComment: >
|
||||||
|
This issue has been automatically closed because there was no response to a
|
||||||
|
request for more information from the issue opener. Please leave a comment or
|
||||||
|
open a new issue if you have additional information related to this issue.
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# ProBot Stale (https://probot.github.io/apps/stale/)
|
||||||
|
|
||||||
|
daysUntilStale: 90
|
||||||
|
daysUntilClose: 7
|
||||||
|
exemptLabels:
|
||||||
|
- accepted
|
||||||
|
- bug
|
||||||
|
- enhancement
|
||||||
|
- meta
|
||||||
|
staleLabel: stale
|
||||||
|
markComment: >
|
||||||
|
This issue has been automatically marked as stale because it has not had
|
||||||
|
recent activity. It will be closed if no further activity occurs.
|
||||||
|
closeComment: false
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
**/xcuserdata/
|
**/xcuserdata/
|
||||||
**/Moonlight.xcscmblueprint
|
**/Moonlight.xcscmblueprint
|
||||||
Build
|
Build
|
||||||
|
DerivedData
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
[submodule "limelight-common-c"]
|
[submodule "moonlight-common/moonlight-common-c"]
|
||||||
path = limelight-common-c
|
path = moonlight-common/moonlight-common-c
|
||||||
url = git@github.com:limelight-stream/limelight-common-c.git
|
url = https://github.com/moonlight-stream/moonlight-common-c.git
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
language: objective-c
|
||||||
|
|
||||||
|
git:
|
||||||
|
depth: 1
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: osx
|
||||||
|
osx_image: xcode11
|
||||||
|
env:
|
||||||
|
- SCHEME="Moonlight"
|
||||||
|
- TARGET="platform=iOS Simulator,OS=12.1,name=iPhone SE"
|
||||||
|
- os: osx
|
||||||
|
osx_image: xcode11
|
||||||
|
env:
|
||||||
|
- SCHEME="Moonlight"
|
||||||
|
- TARGET="platform=iOS Simulator,OS=12.1,name=iPad Pro (9.7-inch)"
|
||||||
|
- os: osx
|
||||||
|
osx_image: xcode11
|
||||||
|
env:
|
||||||
|
- SCHEME="Moonlight TV"
|
||||||
|
- TARGET="platform=tvOS Simulator,OS=12.1,name=Apple TV 4K"
|
||||||
|
|
||||||
|
script:
|
||||||
|
- set -o pipefail && xcodebuild -project Moonlight.xcodeproj -scheme "$SCHEME" -destination "$TARGET" build | xcpretty
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="8191" systemVersion="15A279b" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14868" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES">
|
||||||
|
<device id="retina6_1" orientation="landscape" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="iOS"/>
|
<deployment identifier="iOS"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="8154"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14824"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<objects>
|
<objects>
|
||||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||||
<navigationController id="vV9-32-ww6">
|
<navigationController id="vV9-32-ww6">
|
||||||
<simulatedOrientationMetrics key="simulatedOrientationMetrics" orientation="landscapeRight"/>
|
|
||||||
<navigationBar key="navigationBar" contentMode="scaleToFill" id="y2P-aE-Haa">
|
<navigationBar key="navigationBar" contentMode="scaleToFill" id="y2P-aE-Haa">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
|
<rect key="frame" x="0.0" y="0.0" width="896" height="44"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<animations/>
|
<color key="barTintColor" red="0.66666668653488159" green="0.66666668653488159" blue="0.66666668653488159" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<color key="barTintColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
|
||||||
</navigationBar>
|
</navigationBar>
|
||||||
<viewControllers>
|
<viewControllers>
|
||||||
<viewController id="l6V-ue-5Om">
|
<viewController id="l6V-ue-5Om">
|
||||||
@@ -22,30 +22,14 @@
|
|||||||
<viewControllerLayoutGuide type="bottom" id="gBD-cy-cth"/>
|
<viewControllerLayoutGuide type="bottom" id="gBD-cy-cth"/>
|
||||||
</layoutGuides>
|
</layoutGuides>
|
||||||
<view key="view" contentMode="scaleToFill" id="6lF-fc-kpK">
|
<view key="view" contentMode="scaleToFill" id="6lF-fc-kpK">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="896" height="414"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<animations/>
|
<color key="backgroundColor" red="0.3333333432674408" green="0.3333333432674408" blue="0.3333333432674408" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<color key="backgroundColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
|
|
||||||
</view>
|
</view>
|
||||||
<navigationItem key="navigationItem" id="rW0-BH-s7l">
|
<navigationItem key="navigationItem" id="rW0-BH-s7l"/>
|
||||||
<barButtonItem key="leftBarButtonItem" enabled="NO" id="d9R-Zo-Uml">
|
|
||||||
<button key="customView" opaque="NO" contentMode="center" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="9JC-B5-BAE">
|
|
||||||
<rect key="frame" x="-23" y="-15" width="46" height="30"/>
|
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
|
||||||
<animations/>
|
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
|
||||||
<state key="normal" title="Moonlight" image="Logo">
|
|
||||||
<color key="titleColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
|
||||||
<color key="titleShadowColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
|
||||||
</state>
|
|
||||||
<variation key="widthClass=regular" preservesSuperviewLayoutMargins="NO"/>
|
|
||||||
</button>
|
|
||||||
</barButtonItem>
|
|
||||||
</navigationItem>
|
|
||||||
</viewController>
|
</viewController>
|
||||||
</viewControllers>
|
</viewControllers>
|
||||||
|
<point key="canvasLocation" x="34" y="149"/>
|
||||||
</navigationController>
|
</navigationController>
|
||||||
</objects>
|
</objects>
|
||||||
<resources>
|
|
||||||
<image name="Logo" width="32" height="32"/>
|
|
||||||
</resources>
|
|
||||||
</document>
|
</document>
|
||||||
@@ -6,11 +6,19 @@
|
|||||||
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
|
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#if TARGET_OS_IPHONE
|
||||||
#import <UIKit/UIKit.h>
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
@interface AppDelegate : UIResponder <UIApplicationDelegate>
|
@interface AppDelegate : UIResponder <UIApplicationDelegate>
|
||||||
|
|
||||||
@property (strong, nonatomic) UIWindow *window;
|
@property (strong, nonatomic) UIWindow *window;
|
||||||
|
#else
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
@interface AppDelegate : NSObject <NSApplicationDelegate>
|
||||||
|
|
||||||
|
@property (readonly, strong) NSPersistentContainer *persistentContainer;
|
||||||
|
@property (strong, nonatomic) NSWindow *window;
|
||||||
|
#endif
|
||||||
|
|
||||||
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
|
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
|
||||||
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
|
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
|
||||||
|
|||||||
@@ -16,35 +16,17 @@
|
|||||||
|
|
||||||
static NSOperationQueue* mainQueue;
|
static NSOperationQueue* mainQueue;
|
||||||
|
|
||||||
|
#if TARGET_OS_TV
|
||||||
|
static NSString* DB_NAME = @"Moonlight_tvOS.bin";
|
||||||
|
#elif TARGET_OS_IPHONE
|
||||||
|
static NSString* DB_NAME = @"Limelight_iOS.sqlite";
|
||||||
|
#else
|
||||||
|
static NSString* DB_NAME = @"moonlight_mac.sqlite";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if TARGET_OS_IPHONE
|
||||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
||||||
{
|
{
|
||||||
[[UILabel appearance] setFont:[UIFont fontWithName:@"Roboto-Regular" size:[UIFont systemFontSize]]];
|
|
||||||
[[UIButton appearance].titleLabel setFont:[UIFont fontWithName:@"Roboto-Regular" size:[UIFont systemFontSize]]];
|
|
||||||
|
|
||||||
|
|
||||||
// Generate selected segment background image
|
|
||||||
CGSize borderImageSize = CGSizeMake(1.f, 100.f);
|
|
||||||
|
|
||||||
UIGraphicsBeginImageContext(borderImageSize);
|
|
||||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
|
||||||
|
|
||||||
CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor);
|
|
||||||
CGContextFillRect(context, CGRectMake(0.f, borderImageSize.height * 0.8, borderImageSize.width, borderImageSize.height));
|
|
||||||
|
|
||||||
UIImage *selectedSegmentBG = UIGraphicsGetImageFromCurrentImageContext();
|
|
||||||
UIGraphicsEndImageContext();
|
|
||||||
|
|
||||||
// Clear default border and background color
|
|
||||||
[[UISegmentedControl appearance] setBackgroundImage:[[UIImage alloc] init] forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
|
|
||||||
[[UISegmentedControl appearance] setDividerImage:[[UIImage alloc] init] forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
|
|
||||||
|
|
||||||
// Set selected segment background image
|
|
||||||
[[UISegmentedControl appearance] setBackgroundImage:[selectedSegmentBG imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] forState:UIControlStateSelected barMetrics:UIBarMetricsDefault];
|
|
||||||
|
|
||||||
// Change font on UISegmentedControl
|
|
||||||
[[UISegmentedControl appearance] setTitleTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
|
||||||
[UIColor whiteColor], NSForegroundColorAttributeName,
|
|
||||||
[UIFont fontWithName:@"Roboto-Regular" size:[UIFont systemFontSize]], NSFontAttributeName, nil] forState:UIControlStateNormal];
|
|
||||||
|
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
@@ -76,15 +58,36 @@ static NSOperationQueue* mainQueue;
|
|||||||
// Saves changes in the application's managed object context before the application terminates.
|
// Saves changes in the application's managed object context before the application terminates.
|
||||||
[self saveContext];
|
[self saveContext];
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
||||||
|
// Insert code here to initialize your application
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)applicationWillTerminate:(NSNotification *)aNotification {
|
||||||
|
// Insert code here to tear down your application
|
||||||
|
[self saveContext];
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
- (void)saveContext
|
- (void)saveContext
|
||||||
{
|
{
|
||||||
NSError *error = nil;
|
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
|
||||||
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
|
|
||||||
if (managedObjectContext != nil) {
|
if (managedObjectContext != nil) {
|
||||||
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
|
[managedObjectContext performBlock:^{
|
||||||
Log(LOG_E, @"Critical database error: %@, %@", error, [error userInfo]);
|
if (![managedObjectContext hasChanges]) {
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
NSError *error = nil;
|
||||||
|
if (![managedObjectContext save:&error]) {
|
||||||
|
Log(LOG_E, @"Critical database error: %@, %@", error, [error userInfo]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if TARGET_OS_TV
|
||||||
|
NSData* dbData = [NSData dataWithContentsOfURL:[[[[NSFileManager defaultManager] URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask] lastObject] URLByAppendingPathComponent:DB_NAME]];
|
||||||
|
[[NSUserDefaults standardUserDefaults] setObject:dbData forKey:DB_NAME];
|
||||||
|
#endif
|
||||||
|
}];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,19 +128,30 @@ static NSOperationQueue* mainQueue;
|
|||||||
return _persistentStoreCoordinator;
|
return _persistentStoreCoordinator;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSURL *storeURL = [self getStoreURL];
|
|
||||||
|
|
||||||
NSError *error = nil;
|
NSError *error = nil;
|
||||||
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
|
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
|
||||||
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
|
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
|
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
|
||||||
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
|
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
|
||||||
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
|
NSString* storeType;
|
||||||
|
|
||||||
|
#if TARGET_OS_TV
|
||||||
|
// Use a binary store for tvOS since we will need exclusive access to the file
|
||||||
|
// to serialize into NSUserDefaults.
|
||||||
|
storeType = NSBinaryStoreType;
|
||||||
|
#else
|
||||||
|
storeType = NSSQLiteStoreType;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// We must ensure the persistent store is ready to opened
|
||||||
|
[self preparePersistentStore];
|
||||||
|
|
||||||
|
if (![_persistentStoreCoordinator addPersistentStoreWithType:storeType configuration:nil URL:[self getStoreURL] options:options error:&error]) {
|
||||||
// Log the error
|
// Log the error
|
||||||
Log(LOG_E, @"Critical database error: %@, %@", error, [error userInfo]);
|
Log(LOG_E, @"Critical database error: %@, %@", error, [error userInfo]);
|
||||||
|
|
||||||
// Drop the database
|
// Drop the database
|
||||||
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil];
|
[self dropDatabase];
|
||||||
|
|
||||||
// Try again
|
// Try again
|
||||||
return [self persistentStoreCoordinator];
|
return [self persistentStoreCoordinator];
|
||||||
@@ -154,8 +168,50 @@ static NSOperationQueue* mainQueue;
|
|||||||
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
|
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void) dropDatabase
|
||||||
|
{
|
||||||
|
// Delete the file on disk
|
||||||
|
[[NSFileManager defaultManager] removeItemAtURL:[self getStoreURL] error:nil];
|
||||||
|
|
||||||
|
#if TARGET_OS_TV
|
||||||
|
// Also delete the copy in the NSUserDefaults on tvOS
|
||||||
|
[[NSUserDefaults standardUserDefaults] removeObjectForKey:DB_NAME];
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) preparePersistentStore
|
||||||
|
{
|
||||||
|
#if TARGET_OS_TV
|
||||||
|
// On tvOS, we may need to inflate the DB from NSUserDefaults
|
||||||
|
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
|
||||||
|
NSString *cacheDirectory = [paths objectAtIndex:0];
|
||||||
|
NSString *dbPath = [cacheDirectory stringByAppendingPathComponent:DB_NAME];
|
||||||
|
|
||||||
|
// Always prefer the on disk version
|
||||||
|
if (![[NSFileManager defaultManager] fileExistsAtPath:dbPath]) {
|
||||||
|
// If that is unavailable, inflate it from NSUserDefaults
|
||||||
|
NSData* data = [[NSUserDefaults standardUserDefaults] dataForKey:DB_NAME];
|
||||||
|
if (data != nil) {
|
||||||
|
Log(LOG_I, @"Inflating database from NSUserDefaults");
|
||||||
|
[data writeToFile:dbPath atomically:YES];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Log(LOG_I, @"No database on disk or in NSUserDefaults");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Log(LOG_I, @"Using cached database");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
- (NSURL*) getStoreURL {
|
- (NSURL*) getStoreURL {
|
||||||
return [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"Limelight_iOS.sqlite"];
|
#if TARGET_OS_TV
|
||||||
|
// We use the cache folder to store our database on tvOS
|
||||||
|
return [[[[NSFileManager defaultManager] URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask] lastObject] URLByAppendingPathComponent:DB_NAME];
|
||||||
|
#else
|
||||||
|
return [[self applicationDocumentsDirectory] URLByAppendingPathComponent:DB_NAME];
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -6,19 +6,19 @@
|
|||||||
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
|
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
|
|
||||||
@interface CryptoManager : NSObject
|
@interface CryptoManager : NSObject
|
||||||
|
|
||||||
+ (void) generateKeyPairUsingSSl;
|
+ (void) generateKeyPairUsingSSL;
|
||||||
+ (NSData*) readCertFromFile;
|
+ (NSData*) readCertFromFile;
|
||||||
+ (NSData*) readKeyFromFile;
|
+ (NSData*) readKeyFromFile;
|
||||||
+ (NSData*) readP12FromFile;
|
+ (NSData*) readP12FromFile;
|
||||||
+ (NSData*) getSignatureFromCert:(NSData*)cert;
|
+ (NSData*) getSignatureFromCert:(NSData*)cert;
|
||||||
+ (NSData*) nullTerminateString:(NSData*)data;
|
+ (NSData*) pemToDer:(NSData*)pemCertBytes;
|
||||||
|
|
||||||
- (NSData*) createAESKeyFromSalt:(NSData*)saltedPIN;
|
- (NSData*) createAESKeyFromSaltSHA1:(NSData*)saltedPIN;
|
||||||
|
- (NSData*) createAESKeyFromSaltSHA256:(NSData*)saltedPIN;
|
||||||
- (NSData*) SHA1HashData:(NSData*)data;
|
- (NSData*) SHA1HashData:(NSData*)data;
|
||||||
|
- (NSData*) SHA256HashData:(NSData*)data;
|
||||||
- (NSData*) aesEncrypt:(NSData*)data withKey:(NSData*)key;
|
- (NSData*) aesEncrypt:(NSData*)data withKey:(NSData*)key;
|
||||||
- (NSData*) aesDecrypt:(NSData*)data withKey:(NSData*)key;
|
- (NSData*) aesDecrypt:(NSData*)data withKey:(NSData*)key;
|
||||||
- (bool) verifySignature:(NSData *)data withSignature:(NSData*)signature andCert:(NSData*)cert;
|
- (bool) verifySignature:(NSData *)data withSignature:(NSData*)signature andCert:(NSData*)cert;
|
||||||
|
|||||||
@@ -16,19 +16,31 @@
|
|||||||
#include <openssl/evp.h>
|
#include <openssl/evp.h>
|
||||||
|
|
||||||
@implementation CryptoManager
|
@implementation CryptoManager
|
||||||
static const int SHA1_DIGEST_LENGTH = 20;
|
static const int SHA1_HASH_LENGTH = 20;
|
||||||
|
static const int SHA256_HASH_LENGTH = 32;
|
||||||
static NSData* key = nil;
|
static NSData* key = nil;
|
||||||
static NSData* cert = nil;
|
static NSData* cert = nil;
|
||||||
static NSData* p12 = nil;
|
static NSData* p12 = nil;
|
||||||
|
|
||||||
- (NSData*) createAESKeyFromSalt:(NSData*)saltedPIN {
|
- (NSData*) createAESKeyFromSaltSHA1:(NSData*)saltedPIN {
|
||||||
return [[self SHA1HashData:saltedPIN] subdataWithRange:NSMakeRange(0, 16)];
|
return [[self SHA1HashData:saltedPIN] subdataWithRange:NSMakeRange(0, 16)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (NSData*) createAESKeyFromSaltSHA256:(NSData*)saltedPIN {
|
||||||
|
return [[self SHA256HashData:saltedPIN] subdataWithRange:NSMakeRange(0, 16)];
|
||||||
|
}
|
||||||
|
|
||||||
- (NSData*) SHA1HashData:(NSData*)data {
|
- (NSData*) SHA1HashData:(NSData*)data {
|
||||||
unsigned char sha1[SHA1_DIGEST_LENGTH];
|
unsigned char sha1[SHA1_HASH_LENGTH];
|
||||||
SHA1([data bytes], [data length], sha1);
|
SHA1([data bytes], [data length], sha1);
|
||||||
NSData* bytes = [NSData dataWithBytes:sha1 length:20];
|
NSData* bytes = [NSData dataWithBytes:sha1 length:sizeof(sha1)];
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSData*) SHA256HashData:(NSData*)data {
|
||||||
|
unsigned char sha256[SHA256_HASH_LENGTH];
|
||||||
|
SHA256([data bytes], [data length], sha256);
|
||||||
|
NSData* bytes = [NSData dataWithBytes:sha256 length:sizeof(sha256)];
|
||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,17 +87,28 @@ static NSData* p12 = nil;
|
|||||||
return (((int)[data length] + 15) / 16) * 16;
|
return (((int)[data length] + 15) / 16) * 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (NSData*) nullTerminateString:(NSData*)data {
|
+ (NSData*) pemToDer:(NSData*)pemCertBytes {
|
||||||
NSMutableData* mutData = [NSMutableData dataWithData:data];
|
X509* x509;
|
||||||
[mutData appendBytes:"" length:1];
|
|
||||||
return mutData;
|
BIO* bio = BIO_new_mem_buf([pemCertBytes bytes], (int)[pemCertBytes length]);
|
||||||
|
x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
|
||||||
|
BIO_free(bio);
|
||||||
|
|
||||||
|
bio = BIO_new(BIO_s_mem());
|
||||||
|
i2d_X509_bio(bio, x509);
|
||||||
|
|
||||||
|
BUF_MEM* mem;
|
||||||
|
BIO_get_mem_ptr(bio, &mem);
|
||||||
|
|
||||||
|
NSData* ret = [[NSData alloc] initWithBytes:mem->data length:mem->length];
|
||||||
|
BIO_free(bio);
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (bool) verifySignature:(NSData *)data withSignature:(NSData*)signature andCert:(NSData*)cert {
|
- (bool) verifySignature:(NSData *)data withSignature:(NSData*)signature andCert:(NSData*)cert {
|
||||||
const char* buffer = [[CryptoManager nullTerminateString:cert] bytes];
|
|
||||||
X509* x509;
|
X509* x509;
|
||||||
BIO* bio = BIO_new(BIO_s_mem());
|
BIO* bio = BIO_new_mem_buf([cert bytes], (int)[cert length]);
|
||||||
BIO_puts(bio, buffer);
|
|
||||||
x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
|
x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
|
||||||
|
|
||||||
BIO_free(bio);
|
BIO_free(bio);
|
||||||
@@ -110,9 +133,7 @@ static NSData* p12 = nil;
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (NSData *)signData:(NSData *)data withKey:(NSData *)key {
|
- (NSData *)signData:(NSData *)data withKey:(NSData *)key {
|
||||||
const char* buffer = [[CryptoManager nullTerminateString:key] bytes];
|
BIO* bio = BIO_new_mem_buf([key bytes], (int)[key length]);
|
||||||
BIO* bio = BIO_new(BIO_s_mem());
|
|
||||||
BIO_puts(bio, buffer);
|
|
||||||
|
|
||||||
EVP_PKEY* pkey;
|
EVP_PKEY* pkey;
|
||||||
pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
|
pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
|
||||||
@@ -147,57 +168,60 @@ static NSData* p12 = nil;
|
|||||||
return signedData;
|
return signedData;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: these three methods are almost identical, fix the copy-pasta
|
+ (NSData*) readCryptoObject:(NSString*)item {
|
||||||
|
#if TARGET_OS_TV
|
||||||
|
return [[NSUserDefaults standardUserDefaults] dataForKey:item];
|
||||||
|
#else
|
||||||
|
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
|
||||||
|
NSString *documentsDirectory = [paths objectAtIndex:0];
|
||||||
|
NSString *file = [documentsDirectory stringByAppendingPathComponent:item];
|
||||||
|
return [NSData dataWithContentsOfFile:file];
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (void) writeCryptoObject:(NSString*)item data:(NSData*)data {
|
||||||
|
#if TARGET_OS_TV
|
||||||
|
[[NSUserDefaults standardUserDefaults] setObject:data forKey:item];
|
||||||
|
#else
|
||||||
|
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
|
||||||
|
NSString *documentsDirectory = [paths objectAtIndex:0];
|
||||||
|
NSString *file = [documentsDirectory stringByAppendingPathComponent:item];
|
||||||
|
[data writeToFile:file atomically:NO];
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
+ (NSData*) readCertFromFile {
|
+ (NSData*) readCertFromFile {
|
||||||
if (cert == nil) {
|
if (cert == nil) {
|
||||||
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
|
cert = [CryptoManager readCryptoObject:@"client.crt"];
|
||||||
NSString *documentsDirectory = [paths objectAtIndex:0];
|
|
||||||
NSString *certFile = [documentsDirectory stringByAppendingPathComponent:@"client.crt"];
|
|
||||||
cert = [NSData dataWithContentsOfFile:certFile];
|
|
||||||
}
|
}
|
||||||
return cert;
|
return cert;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (NSData*) readP12FromFile {
|
+ (NSData*) readP12FromFile {
|
||||||
if (p12 == nil) {
|
if (p12 == nil) {
|
||||||
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
|
p12 = [CryptoManager readCryptoObject:@"client.p12"];
|
||||||
NSString *documentsDirectory = [paths objectAtIndex:0];
|
|
||||||
NSString *p12File = [documentsDirectory stringByAppendingPathComponent:@"client.p12"];
|
|
||||||
p12 = [NSData dataWithContentsOfFile:p12File];
|
|
||||||
}
|
}
|
||||||
return p12;
|
return p12;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (NSData*) readKeyFromFile {
|
+ (NSData*) readKeyFromFile {
|
||||||
if (key == nil) {
|
if (key == nil) {
|
||||||
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
|
key = [CryptoManager readCryptoObject:@"client.key"];
|
||||||
NSString *documentsDirectory = [paths objectAtIndex:0];
|
|
||||||
NSString *keyFile = [documentsDirectory stringByAppendingPathComponent:@"client.key"];
|
|
||||||
key = [NSData dataWithContentsOfFile:keyFile];
|
|
||||||
}
|
}
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (bool) keyPairExists {
|
+ (bool) keyPairExists {
|
||||||
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
|
bool keyFileExists = [CryptoManager readCryptoObject:@"client.key"] != nil;
|
||||||
NSString *documentsDirectory = [paths objectAtIndex:0];
|
bool p12FileExists = [CryptoManager readCryptoObject:@"client.p12"] != nil;
|
||||||
NSString *keyFile = [documentsDirectory stringByAppendingPathComponent:@"client.key"];
|
bool certFileExists = [CryptoManager readCryptoObject:@"client.crt"] != nil;
|
||||||
NSString *p12File = [documentsDirectory stringByAppendingPathComponent:@"client.p12"];
|
|
||||||
NSString *certFile = [documentsDirectory stringByAppendingPathComponent:@"client.crt"];
|
|
||||||
|
|
||||||
bool keyFileExists = [[NSFileManager defaultManager] fileExistsAtPath:keyFile];
|
|
||||||
bool p12FileExists = [[NSFileManager defaultManager] fileExistsAtPath:p12File];
|
|
||||||
bool certFileExists = [[NSFileManager defaultManager] fileExistsAtPath:certFile];
|
|
||||||
|
|
||||||
return keyFileExists && p12FileExists && certFileExists;
|
return keyFileExists && p12FileExists && certFileExists;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (NSData *)getSignatureFromCert:(NSData *)cert {
|
+ (NSData *)getSignatureFromCert:(NSData *)cert {
|
||||||
const char* buffer = [[CryptoManager nullTerminateString:cert] bytes];
|
BIO* bio = BIO_new_mem_buf([cert bytes], (int)[cert length]);
|
||||||
X509* x509;
|
X509* x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
|
||||||
BIO* bio = BIO_new(BIO_s_mem());
|
|
||||||
BIO_puts(bio, buffer);
|
|
||||||
x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
|
|
||||||
|
|
||||||
if (!x509) {
|
if (!x509) {
|
||||||
Log(LOG_E, @"Unable to parse certificate in memory!");
|
Log(LOG_E, @"Unable to parse certificate in memory!");
|
||||||
@@ -206,23 +230,59 @@ static NSData* p12 = nil;
|
|||||||
return [NSData dataWithBytes:x509->signature->data length:x509->signature->length];
|
return [NSData dataWithBytes:x509->signature->data length:x509->signature->length];
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (void) generateKeyPairUsingSSl {
|
+ (NSData*)getKeyFromCertKeyPair:(CertKeyPair*)certKeyPair {
|
||||||
|
BIO* bio = BIO_new(BIO_s_mem());
|
||||||
|
|
||||||
|
PEM_write_bio_PrivateKey(bio, certKeyPair->pkey, NULL, NULL, 0, NULL, NULL);
|
||||||
|
|
||||||
|
BUF_MEM* mem;
|
||||||
|
BIO_get_mem_ptr(bio, &mem);
|
||||||
|
NSData* data = [NSData dataWithBytes:mem->data length:mem->length];
|
||||||
|
BIO_free(bio);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (NSData*)getP12FromCertKeyPair:(CertKeyPair*)certKeyPair {
|
||||||
|
BIO* bio = BIO_new(BIO_s_mem());
|
||||||
|
|
||||||
|
i2d_PKCS12_bio(bio, certKeyPair->p12);
|
||||||
|
|
||||||
|
BUF_MEM* mem;
|
||||||
|
BIO_get_mem_ptr(bio, &mem);
|
||||||
|
NSData* data = [NSData dataWithBytes:mem->data length:mem->length];
|
||||||
|
BIO_free(bio);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (NSData*)getCertFromCertKeyPair:(CertKeyPair*)certKeyPair {
|
||||||
|
BIO* bio = BIO_new(BIO_s_mem());
|
||||||
|
|
||||||
|
PEM_write_bio_X509(bio, certKeyPair->x509);
|
||||||
|
|
||||||
|
BUF_MEM* mem;
|
||||||
|
BIO_get_mem_ptr(bio, &mem);
|
||||||
|
NSData* data = [NSData dataWithBytes:mem->data length:mem->length];
|
||||||
|
BIO_free(bio);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (void) generateKeyPairUsingSSL {
|
||||||
static dispatch_once_t pred;
|
static dispatch_once_t pred;
|
||||||
dispatch_once(&pred, ^{
|
dispatch_once(&pred, ^{
|
||||||
if (![CryptoManager keyPairExists]) {
|
if (![CryptoManager keyPairExists]) {
|
||||||
|
|
||||||
Log(LOG_I, @"Generating Certificate... ");
|
Log(LOG_I, @"Generating Certificate... ");
|
||||||
CertKeyPair certKeyPair = generateCertKeyPair();
|
CertKeyPair certKeyPair = generateCertKeyPair();
|
||||||
|
|
||||||
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
|
NSData* certData = [CryptoManager getCertFromCertKeyPair:&certKeyPair];
|
||||||
NSString* documentsDirectory = [paths objectAtIndex:0];
|
NSData* p12Data = [CryptoManager getP12FromCertKeyPair:&certKeyPair];
|
||||||
NSString* certFile = [documentsDirectory stringByAppendingPathComponent:@"client.crt"];
|
NSData* keyData = [CryptoManager getKeyFromCertKeyPair:&certKeyPair];
|
||||||
NSString* keyPairFile = [documentsDirectory stringByAppendingPathComponent:@"client.key"];
|
|
||||||
NSString* p12File = [documentsDirectory stringByAppendingPathComponent:@"client.p12"];
|
|
||||||
|
|
||||||
//Log(LOG_D, @"Writing cert and key to: \n%@\n%@", certFile, keyPairFile);
|
|
||||||
saveCertKeyPair([certFile UTF8String], [p12File UTF8String], [keyPairFile UTF8String], certKeyPair);
|
|
||||||
freeCertKeyPair(certKeyPair);
|
freeCertKeyPair(certKeyPair);
|
||||||
|
|
||||||
|
[CryptoManager writeCryptoObject:@"client.crt" data:certData];
|
||||||
|
[CryptoManager writeCryptoObject:@"client.p12" data:p12Data];
|
||||||
|
[CryptoManager writeCryptoObject:@"client.key" data:keyData];
|
||||||
|
|
||||||
Log(LOG_I, @"Certificate created");
|
Log(LOG_I, @"Certificate created");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,8 +6,6 @@
|
|||||||
// Copyright © 2015 Moonlight Stream. All rights reserved.
|
// Copyright © 2015 Moonlight Stream. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
|
|
||||||
@interface IdManager : NSObject
|
@interface IdManager : NSObject
|
||||||
|
|
||||||
+ (NSString*) getUniqueId;
|
+ (NSString*) getUniqueId;
|
||||||
|
|||||||
@@ -13,15 +13,14 @@
|
|||||||
|
|
||||||
+ (NSString*) getUniqueId {
|
+ (NSString*) getUniqueId {
|
||||||
DataManager* dataMan = [[DataManager alloc] init];
|
DataManager* dataMan = [[DataManager alloc] init];
|
||||||
Settings* prefs = [dataMan retrieveSettings];
|
|
||||||
|
|
||||||
NSString* uniqueId = prefs.uniqueId;
|
NSString* uniqueId = [dataMan getUniqueId];
|
||||||
if (uniqueId == nil) {
|
if (uniqueId == nil) {
|
||||||
uniqueId = [IdManager generateUniqueId];
|
uniqueId = [IdManager generateUniqueId];
|
||||||
prefs.uniqueId = uniqueId;
|
[dataMan updateUniqueId:uniqueId];
|
||||||
[dataMan saveData];
|
|
||||||
Log(LOG_I, @"No UUID found. Generated new UUID: %@", uniqueId);
|
Log(LOG_I, @"No UUID found. Generated new UUID: %@", uniqueId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return uniqueId;
|
return uniqueId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ static const int NUM_YEARS = 10;
|
|||||||
int mkcert(X509 **x509p, EVP_PKEY **pkeyp, int bits, int serial, int years);
|
int mkcert(X509 **x509p, EVP_PKEY **pkeyp, int bits, int serial, int years);
|
||||||
int add_ext(X509 *cert, int nid, char *value);
|
int add_ext(X509 *cert, int nid, char *value);
|
||||||
|
|
||||||
struct CertKeyPair generateCertKeyPair() {
|
struct CertKeyPair generateCertKeyPair(void) {
|
||||||
BIO *bio_err;
|
BIO *bio_err;
|
||||||
X509 *x509 = NULL;
|
X509 *x509 = NULL;
|
||||||
EVP_PKEY *pkey = NULL;
|
EVP_PKEY *pkey = NULL;
|
||||||
@@ -62,21 +62,6 @@ void freeCertKeyPair(struct CertKeyPair certKeyPair) {
|
|||||||
PKCS12_free(certKeyPair.p12);
|
PKCS12_free(certKeyPair.p12);
|
||||||
}
|
}
|
||||||
|
|
||||||
void saveCertKeyPair(const char* certFile, const char* p12File, const char* keyPairFile, CertKeyPair certKeyPair) {
|
|
||||||
FILE* certFilePtr = fopen(certFile, "w");
|
|
||||||
FILE* keyPairFilePtr = fopen(keyPairFile, "w");
|
|
||||||
FILE* p12FilePtr = fopen(p12File, "wb");
|
|
||||||
|
|
||||||
//TODO: error check
|
|
||||||
PEM_write_PrivateKey(keyPairFilePtr, certKeyPair.pkey, NULL, NULL, 0, NULL, NULL);
|
|
||||||
PEM_write_X509(certFilePtr, certKeyPair.x509);
|
|
||||||
i2d_PKCS12_fp(p12FilePtr, certKeyPair.p12);
|
|
||||||
|
|
||||||
fclose(p12FilePtr);
|
|
||||||
fclose(certFilePtr);
|
|
||||||
fclose(keyPairFilePtr);
|
|
||||||
}
|
|
||||||
|
|
||||||
int mkcert(X509 **x509p, EVP_PKEY **pkeyp, int bits, int serial, int years) {
|
int mkcert(X509 **x509p, EVP_PKEY **pkeyp, int bits, int serial, int years) {
|
||||||
X509 *x;
|
X509 *x;
|
||||||
EVP_PKEY *pk;
|
EVP_PKEY *pk;
|
||||||
@@ -122,13 +107,7 @@ int mkcert(X509 **x509p, EVP_PKEY **pkeyp, int bits, int serial, int years) {
|
|||||||
*/
|
*/
|
||||||
X509_set_issuer_name(x, name);
|
X509_set_issuer_name(x, name);
|
||||||
|
|
||||||
/* Add various extensions: standard extensions */
|
if (!X509_sign(x, pk, EVP_sha256())) {
|
||||||
add_ext(x, NID_basic_constraints, "critical,CA:TRUE");
|
|
||||||
add_ext(x, NID_key_usage, "critical,keyCertSign,cRLSign");
|
|
||||||
|
|
||||||
add_ext(x, NID_subject_key_identifier, "hash");
|
|
||||||
|
|
||||||
if (!X509_sign(x, pk, EVP_sha1())) {
|
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,29 +118,3 @@ int mkcert(X509 **x509p, EVP_PKEY **pkeyp, int bits, int serial, int years) {
|
|||||||
err:
|
err:
|
||||||
return(0);
|
return(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Add extension using V3 code: we can set the config file as NULL
|
|
||||||
* because we wont reference any other sections.
|
|
||||||
*/
|
|
||||||
|
|
||||||
int add_ext(X509 *cert, int nid, char *value)
|
|
||||||
{
|
|
||||||
X509_EXTENSION *ex;
|
|
||||||
X509V3_CTX ctx;
|
|
||||||
/* This sets the 'context' of the extensions. */
|
|
||||||
/* No configuration database */
|
|
||||||
X509V3_set_ctx_nodb(&ctx);
|
|
||||||
/* Issuer and subject certs: both the target since it is self signed,
|
|
||||||
* no request and no CRL
|
|
||||||
*/
|
|
||||||
X509V3_set_ctx(&ctx, cert, cert, NULL, NULL, 0);
|
|
||||||
ex = X509V3_EXT_conf_nid(NULL, &ctx, nid, value);
|
|
||||||
if (!ex) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
X509_add_ext(cert, ex, -1);
|
|
||||||
X509_EXTENSION_free(ex);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -18,8 +18,7 @@ typedef struct CertKeyPair {
|
|||||||
PKCS12 *p12;
|
PKCS12 *p12;
|
||||||
} CertKeyPair;
|
} CertKeyPair;
|
||||||
|
|
||||||
struct CertKeyPair generateCertKeyPair();
|
struct CertKeyPair generateCertKeyPair(void);
|
||||||
void freeCertKeyPair(CertKeyPair);
|
void freeCertKeyPair(CertKeyPair);
|
||||||
void saveCertKeyPair(const char* certFile, const char* p12File, const char* keyPairFile, CertKeyPair certKeyPair);
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
//
|
|
||||||
// App+CoreDataProperties.h
|
|
||||||
// Moonlight
|
|
||||||
//
|
|
||||||
// Created by Diego Waxemberg on 7/10/15.
|
|
||||||
// Copyright © 2015 Limelight Stream. All rights reserved.
|
|
||||||
//
|
|
||||||
// Delete this file and regenerate it using "Create NSManagedObject Subclass…"
|
|
||||||
// to keep your implementation up to date with your model.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "App.h"
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
@interface App (CoreDataProperties)
|
|
||||||
|
|
||||||
@property (nullable, nonatomic, retain) NSString *id;
|
|
||||||
@property (nullable, nonatomic, retain) NSData *image;
|
|
||||||
@property (nullable, nonatomic, retain) NSString *name;
|
|
||||||
@property (nullable, nonatomic, retain) Host *host;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
//
|
|
||||||
// App.h
|
|
||||||
// Moonlight
|
|
||||||
//
|
|
||||||
// Created by Diego Waxemberg on 7/10/15.
|
|
||||||
// Copyright © 2015 Limelight Stream. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
#import <CoreData/CoreData.h>
|
|
||||||
|
|
||||||
@class Host;
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
@interface App : NSManagedObject
|
|
||||||
|
|
||||||
@property BOOL isRunning;
|
|
||||||
|
|
||||||
- (NSComparisonResult)compareName:(App *)other;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
||||||
|
|
||||||
#import "App+CoreDataProperties.h"
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
//
|
|
||||||
// App.m
|
|
||||||
// Moonlight
|
|
||||||
//
|
|
||||||
// Created by Diego Waxemberg on 7/10/15.
|
|
||||||
// Copyright © 2015 Limelight Stream. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "App.h"
|
|
||||||
#import "Host.h"
|
|
||||||
|
|
||||||
@implementation App
|
|
||||||
|
|
||||||
@synthesize isRunning;
|
|
||||||
|
|
||||||
- (NSComparisonResult)compareName:(App *)other {
|
|
||||||
return [self.name caseInsensitiveCompare:other.name];
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
@@ -6,24 +6,34 @@
|
|||||||
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
|
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
#import "Settings.h"
|
|
||||||
#import "AppDelegate.h"
|
#import "AppDelegate.h"
|
||||||
#import "Host.h"
|
#import "TemporaryHost.h"
|
||||||
#import "App.h"
|
|
||||||
#import "TemporaryApp.h"
|
#import "TemporaryApp.h"
|
||||||
|
#import "TemporarySettings.h"
|
||||||
|
|
||||||
@interface DataManager : NSObject
|
@interface DataManager : NSObject
|
||||||
|
|
||||||
@property (strong, nonatomic) AppDelegate* appDelegate;
|
- (void) saveSettingsWithBitrate:(NSInteger)bitrate
|
||||||
|
framerate:(NSInteger)framerate
|
||||||
|
height:(NSInteger)height
|
||||||
|
width:(NSInteger)width
|
||||||
|
onscreenControls:(NSInteger)onscreenControls
|
||||||
|
remote:(BOOL)streamingRemotely
|
||||||
|
optimizeGames:(BOOL)optimizeGames
|
||||||
|
multiController:(BOOL)multiController
|
||||||
|
audioOnPC:(BOOL)audioOnPC
|
||||||
|
useHevc:(BOOL)useHevc
|
||||||
|
enableHdr:(BOOL)enableHdr;
|
||||||
|
|
||||||
- (void) saveSettingsWithBitrate:(NSInteger)bitrate framerate:(NSInteger)framerate height:(NSInteger)height width:(NSInteger)width onscreenControls:(NSInteger)onscreenControls;
|
- (NSArray*) getHosts;
|
||||||
- (Settings*) retrieveSettings;
|
- (void) updateHost:(TemporaryHost*)host;
|
||||||
- (NSArray*) retrieveHosts;
|
- (void) updateAppsForExistingHost:(TemporaryHost *)host;
|
||||||
- (void) saveData;
|
- (void) removeHost:(TemporaryHost*)host;
|
||||||
- (Host*) createHost;
|
- (void) removeApp:(TemporaryApp*)app;
|
||||||
- (void) removeHost:(Host*)host;
|
|
||||||
- (App*) addAppFromTemporaryApp:(TemporaryApp*)tempApp;
|
- (TemporarySettings*) getSettings;
|
||||||
- (void) removeAppFromHost:(App*)app;
|
|
||||||
|
- (void) updateUniqueId:(NSString*)uniqueId;
|
||||||
|
- (NSString*) getUniqueId;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -8,45 +8,142 @@
|
|||||||
|
|
||||||
#import "DataManager.h"
|
#import "DataManager.h"
|
||||||
#import "TemporaryApp.h"
|
#import "TemporaryApp.h"
|
||||||
|
#import "TemporarySettings.h"
|
||||||
|
|
||||||
@implementation DataManager
|
@implementation DataManager {
|
||||||
|
NSManagedObjectContext *_managedObjectContext;
|
||||||
+ (NSObject *)databaseLock {
|
AppDelegate *_appDelegate;
|
||||||
static NSObject *lock = nil;
|
|
||||||
if (lock == nil) {
|
|
||||||
lock = [[NSObject alloc] init];
|
|
||||||
}
|
|
||||||
return lock;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (id) init {
|
- (id) init {
|
||||||
self = [super init];
|
self = [super init];
|
||||||
self.appDelegate = [[UIApplication sharedApplication] delegate];
|
|
||||||
|
// HACK: Avoid calling [UIApplication delegate] off the UI thread to keep
|
||||||
|
// Main Thread Checker happy.
|
||||||
|
if ([NSThread isMainThread]) {
|
||||||
|
_appDelegate = (AppDelegate *)[[OSApplication sharedApplication] delegate];
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||||
|
self->_appDelegate = (AppDelegate *)[[OSApplication sharedApplication] delegate];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
|
||||||
|
[_managedObjectContext setParentContext:[_appDelegate managedObjectContext]];
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void) saveSettingsWithBitrate:(NSInteger)bitrate framerate:(NSInteger)framerate height:(NSInteger)height width:(NSInteger)width onscreenControls:(NSInteger)onscreenControls {
|
- (void) updateUniqueId:(NSString*)uniqueId {
|
||||||
Settings* settingsToSave = [self retrieveSettings];
|
[_managedObjectContext performBlockAndWait:^{
|
||||||
settingsToSave.framerate = [NSNumber numberWithInteger:framerate];
|
[self retrieveSettings].uniqueId = uniqueId;
|
||||||
// Bitrate is persisted in kbps
|
[self saveData];
|
||||||
settingsToSave.bitrate = [NSNumber numberWithInteger:bitrate];
|
}];
|
||||||
settingsToSave.height = [NSNumber numberWithInteger:height];
|
}
|
||||||
settingsToSave.width = [NSNumber numberWithInteger:width];
|
|
||||||
settingsToSave.onscreenControls = [NSNumber numberWithInteger:onscreenControls];
|
|
||||||
|
|
||||||
NSError* error;
|
- (NSString*) getUniqueId {
|
||||||
if (![[self.appDelegate managedObjectContext] save:&error]) {
|
__block NSString *uid;
|
||||||
Log(LOG_E, @"Unable to save settings to database: %@", error);
|
|
||||||
}
|
[_managedObjectContext performBlockAndWait:^{
|
||||||
[self.appDelegate saveContext];
|
uid = [self retrieveSettings].uniqueId;
|
||||||
|
}];
|
||||||
|
|
||||||
|
return uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) saveSettingsWithBitrate:(NSInteger)bitrate
|
||||||
|
framerate:(NSInteger)framerate
|
||||||
|
height:(NSInteger)height
|
||||||
|
width:(NSInteger)width
|
||||||
|
onscreenControls:(NSInteger)onscreenControls
|
||||||
|
remote:(BOOL)streamingRemotely
|
||||||
|
optimizeGames:(BOOL)optimizeGames
|
||||||
|
multiController:(BOOL)multiController
|
||||||
|
audioOnPC:(BOOL)audioOnPC
|
||||||
|
useHevc:(BOOL)useHevc
|
||||||
|
enableHdr:(BOOL)enableHdr {
|
||||||
|
|
||||||
|
[_managedObjectContext performBlockAndWait:^{
|
||||||
|
Settings* settingsToSave = [self retrieveSettings];
|
||||||
|
settingsToSave.framerate = [NSNumber numberWithInteger:framerate];
|
||||||
|
settingsToSave.bitrate = [NSNumber numberWithInteger:bitrate];
|
||||||
|
settingsToSave.height = [NSNumber numberWithInteger:height];
|
||||||
|
settingsToSave.width = [NSNumber numberWithInteger:width];
|
||||||
|
settingsToSave.onscreenControls = [NSNumber numberWithInteger:onscreenControls];
|
||||||
|
settingsToSave.streamingRemotely = streamingRemotely;
|
||||||
|
settingsToSave.optimizeGames = optimizeGames;
|
||||||
|
settingsToSave.multiController = multiController;
|
||||||
|
settingsToSave.playAudioOnPC = audioOnPC;
|
||||||
|
settingsToSave.useHevc = useHevc;
|
||||||
|
settingsToSave.enableHdr = enableHdr;
|
||||||
|
|
||||||
|
[self saveData];
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) updateHost:(TemporaryHost *)host {
|
||||||
|
[_managedObjectContext performBlockAndWait:^{
|
||||||
|
// Add a new persistent managed object if one doesn't exist
|
||||||
|
Host* parent = [self getHostForTemporaryHost:host withHostRecords:[self fetchRecords:@"Host"]];
|
||||||
|
if (parent == nil) {
|
||||||
|
NSEntityDescription* entity = [NSEntityDescription entityForName:@"Host" inManagedObjectContext:self->_managedObjectContext];
|
||||||
|
parent = [[Host alloc] initWithEntity:entity insertIntoManagedObjectContext:self->_managedObjectContext];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push changes from the temp host to the persistent one
|
||||||
|
[host propagateChangesToParent:parent];
|
||||||
|
|
||||||
|
[self saveData];
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) updateAppsForExistingHost:(TemporaryHost *)host {
|
||||||
|
[_managedObjectContext performBlockAndWait:^{
|
||||||
|
Host* parent = [self getHostForTemporaryHost:host withHostRecords:[self fetchRecords:@"Host"]];
|
||||||
|
if (parent == nil) {
|
||||||
|
// The host must exist to be updated
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSMutableSet *applist = [[NSMutableSet alloc] init];
|
||||||
|
NSArray *appRecords = [self fetchRecords:@"App"];
|
||||||
|
for (TemporaryApp* app in host.appList) {
|
||||||
|
// Add a new persistent managed object if one doesn't exist
|
||||||
|
App* parentApp = [self getAppForTemporaryApp:app withAppRecords:appRecords];
|
||||||
|
if (parentApp == nil) {
|
||||||
|
NSEntityDescription* entity = [NSEntityDescription entityForName:@"App" inManagedObjectContext:self->_managedObjectContext];
|
||||||
|
parentApp = [[App alloc] initWithEntity:entity insertIntoManagedObjectContext:self->_managedObjectContext];
|
||||||
|
}
|
||||||
|
|
||||||
|
[app propagateChangesToParent:parentApp withHost:parent];
|
||||||
|
|
||||||
|
[applist addObject:parentApp];
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.appList = applist;
|
||||||
|
|
||||||
|
[self saveData];
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (TemporarySettings*) getSettings {
|
||||||
|
__block TemporarySettings *tempSettings;
|
||||||
|
|
||||||
|
[_managedObjectContext performBlockAndWait:^{
|
||||||
|
tempSettings = [[TemporarySettings alloc] initFromSettings:[self retrieveSettings]];
|
||||||
|
}];
|
||||||
|
|
||||||
|
return tempSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (Settings*) retrieveSettings {
|
- (Settings*) retrieveSettings {
|
||||||
NSArray* fetchedRecords = [self fetchRecords:@"Settings"];
|
NSArray* fetchedRecords = [self fetchRecords:@"Settings"];
|
||||||
if (fetchedRecords.count == 0) {
|
if (fetchedRecords.count == 0) {
|
||||||
// create a new settings object with the default values
|
// create a new settings object with the default values
|
||||||
NSEntityDescription* entity = [NSEntityDescription entityForName:@"Settings" inManagedObjectContext:[self.appDelegate managedObjectContext]];
|
NSEntityDescription* entity = [NSEntityDescription entityForName:@"Settings" inManagedObjectContext:_managedObjectContext];
|
||||||
Settings* settings = [[Settings alloc] initWithEntity:entity insertIntoManagedObjectContext:[self.appDelegate managedObjectContext]];
|
Settings* settings = [[Settings alloc] initWithEntity:entity insertIntoManagedObjectContext:_managedObjectContext];
|
||||||
|
|
||||||
return settings;
|
return settings;
|
||||||
} else {
|
} else {
|
||||||
@@ -55,74 +152,82 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (Host*) createHost {
|
- (void) removeApp:(TemporaryApp*)app {
|
||||||
NSEntityDescription* entity = [NSEntityDescription entityForName:@"Host" inManagedObjectContext:[self.appDelegate managedObjectContext]];
|
[_managedObjectContext performBlockAndWait:^{
|
||||||
return [[Host alloc] initWithEntity:entity insertIntoManagedObjectContext:[self.appDelegate managedObjectContext]];
|
App* managedApp = [self getAppForTemporaryApp:app withAppRecords:[self fetchRecords:@"App"]];
|
||||||
|
if (managedApp != nil) {
|
||||||
|
[self->_managedObjectContext deleteObject:managedApp];
|
||||||
|
[self saveData];
|
||||||
|
}
|
||||||
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void) removeHost:(Host*)host {
|
- (void) removeHost:(TemporaryHost*)host {
|
||||||
[[self.appDelegate managedObjectContext] deleteObject:host];
|
[_managedObjectContext performBlockAndWait:^{
|
||||||
[self saveData];
|
Host* managedHost = [self getHostForTemporaryHost:host withHostRecords:[self fetchRecords:@"Host"]];
|
||||||
|
if (managedHost != nil) {
|
||||||
|
[self->_managedObjectContext deleteObject:managedHost];
|
||||||
|
[self saveData];
|
||||||
|
}
|
||||||
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void) saveData {
|
- (void) saveData {
|
||||||
@synchronized([DataManager databaseLock]) {
|
NSError* error;
|
||||||
NSError* error;
|
if ([_managedObjectContext hasChanges] && ![_managedObjectContext save:&error]) {
|
||||||
if (![[self.appDelegate managedObjectContext] save:&error]) {
|
Log(LOG_E, @"Unable to save hosts to database: %@", error);
|
||||||
Log(LOG_E, @"Unable to save hosts to database: %@", error);
|
}
|
||||||
|
|
||||||
|
[_appDelegate saveContext];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSArray*) getHosts {
|
||||||
|
__block NSMutableArray *tempHosts = [[NSMutableArray alloc] init];
|
||||||
|
|
||||||
|
[_managedObjectContext performBlockAndWait:^{
|
||||||
|
NSArray *hosts = [self fetchRecords:@"Host"];
|
||||||
|
|
||||||
|
for (Host* host in hosts) {
|
||||||
|
[tempHosts addObject:[[TemporaryHost alloc] initFromHost:host]];
|
||||||
}
|
}
|
||||||
[self.appDelegate saveContext];
|
}];
|
||||||
}
|
|
||||||
|
return tempHosts;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSArray*) retrieveHosts {
|
// Only call from within performBlockAndWait!!!
|
||||||
return [self fetchRecords:@"Host"];
|
- (Host*) getHostForTemporaryHost:(TemporaryHost*)tempHost withHostRecords:(NSArray*)hosts {
|
||||||
}
|
for (Host* host in hosts) {
|
||||||
|
if ([tempHost.uuid isEqualToString:host.uuid]) {
|
||||||
- (App*) addAppFromTemporaryApp:(TemporaryApp*)tempApp {
|
return host;
|
||||||
|
}
|
||||||
App* managedApp;
|
|
||||||
|
|
||||||
@synchronized([DataManager databaseLock]) {
|
|
||||||
NSEntityDescription* entity = [NSEntityDescription entityForName:@"App" inManagedObjectContext:[self.appDelegate managedObjectContext]];
|
|
||||||
managedApp = [[App alloc] initWithEntity:entity insertIntoManagedObjectContext:[self.appDelegate managedObjectContext]];
|
|
||||||
|
|
||||||
assert(tempApp.host != nil);
|
|
||||||
|
|
||||||
managedApp.id = tempApp.id;
|
|
||||||
managedApp.image = tempApp.image;
|
|
||||||
managedApp.name = tempApp.name;
|
|
||||||
managedApp.isRunning = tempApp.isRunning;
|
|
||||||
managedApp.host = tempApp.host;
|
|
||||||
|
|
||||||
[managedApp.host addAppListObject:managedApp];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return managedApp;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void) removeAppFromHost:(App*)app {
|
// Only call from within performBlockAndWait!!!
|
||||||
@synchronized([DataManager databaseLock]) {
|
- (App*) getAppForTemporaryApp:(TemporaryApp*)tempApp withAppRecords:(NSArray*)apps {
|
||||||
assert(app.host != nil);
|
for (App* app in apps) {
|
||||||
|
if ([app.id isEqualToString:tempApp.id] &&
|
||||||
[app.host removeAppListObject:app];
|
[app.host.uuid isEqualToString:tempApp.host.uuid]) {
|
||||||
[[self.appDelegate managedObjectContext] deleteObject:app];
|
return app;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSArray*) fetchRecords:(NSString*)entityName {
|
- (NSArray*) fetchRecords:(NSString*)entityName {
|
||||||
NSArray* fetchedRecords;
|
NSArray* fetchedRecords;
|
||||||
|
|
||||||
@synchronized([DataManager databaseLock]) {
|
NSFetchRequest* fetchRequest = [[NSFetchRequest alloc] init];
|
||||||
NSFetchRequest* fetchRequest = [[NSFetchRequest alloc] init];
|
NSEntityDescription* entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:_managedObjectContext];
|
||||||
NSEntityDescription* entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:[self.appDelegate managedObjectContext]];
|
[fetchRequest setEntity:entity];
|
||||||
[fetchRequest setEntity:entity];
|
|
||||||
[fetchRequest setAffectedStores:[NSArray arrayWithObjects:[[self.appDelegate persistentStoreCoordinator] persistentStoreForURL:[self.appDelegate getStoreURL]], nil]];
|
|
||||||
|
|
||||||
NSError* error;
|
NSError* error;
|
||||||
fetchedRecords = [[self.appDelegate managedObjectContext] executeFetchRequest:fetchRequest error:&error];
|
fetchedRecords = [_managedObjectContext executeFetchRequest:fetchRequest error:&error];
|
||||||
//TODO: handle errors
|
//TODO: handle errors
|
||||||
}
|
|
||||||
|
|
||||||
return fetchedRecords;
|
return fetchedRecords;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
//
|
|
||||||
// Host+CoreDataProperties.h
|
|
||||||
// Moonlight
|
|
||||||
//
|
|
||||||
// Created by Diego Waxemberg on 7/10/15.
|
|
||||||
// Copyright © 2015 Limelight Stream. All rights reserved.
|
|
||||||
//
|
|
||||||
// Delete this file and regenerate it using "Create NSManagedObject Subclass…"
|
|
||||||
// to keep your implementation up to date with your model.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "Host.h"
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
@interface Host (CoreDataProperties)
|
|
||||||
|
|
||||||
@property (nullable, nonatomic, retain) NSString *address;
|
|
||||||
@property (nullable, nonatomic, retain) NSString *externalAddress;
|
|
||||||
@property (nullable, nonatomic, retain) NSString *localAddress;
|
|
||||||
@property (nullable, nonatomic, retain) NSString *mac;
|
|
||||||
@property (nullable, nonatomic, retain) NSString *name;
|
|
||||||
@property (nullable, nonatomic, retain) NSNumber *pairState;
|
|
||||||
@property (nullable, nonatomic, retain) NSString *uuid;
|
|
||||||
@property (nullable, nonatomic, retain) NSSet *appList;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@interface Host (CoreDataGeneratedAccessors)
|
|
||||||
|
|
||||||
- (void)addAppListObject:(NSManagedObject *)value;
|
|
||||||
- (void)removeAppListObject:(NSManagedObject *)value;
|
|
||||||
- (void)addAppList:(NSSet *)values;
|
|
||||||
- (void)removeAppList:(NSSet *)values;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
//
|
|
||||||
// Host.h
|
|
||||||
// Moonlight
|
|
||||||
//
|
|
||||||
// Created by Diego Waxemberg on 7/10/15.
|
|
||||||
// Copyright © 2015 Limelight Stream. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
#import <CoreData/CoreData.h>
|
|
||||||
#import "Utils.h"
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
@interface Host : NSManagedObject
|
|
||||||
|
|
||||||
@property (nonatomic) BOOL online;
|
|
||||||
@property (nonatomic) PairState pairState;
|
|
||||||
@property (nonatomic) NSString * activeAddress;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
||||||
|
|
||||||
#import "Host+CoreDataProperties.h"
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
//
|
|
||||||
// Host.m
|
|
||||||
// Moonlight
|
|
||||||
//
|
|
||||||
// Created by Diego Waxemberg on 7/10/15.
|
|
||||||
// Copyright © 2015 Limelight Stream. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "Host.h"
|
|
||||||
|
|
||||||
@implementation Host
|
|
||||||
|
|
||||||
@dynamic pairState;
|
|
||||||
@synthesize online;
|
|
||||||
@synthesize activeAddress;
|
|
||||||
|
|
||||||
@end
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
//
|
|
||||||
// Settings.h
|
|
||||||
// Moonlight
|
|
||||||
//
|
|
||||||
// Created by Diego Waxemberg on 10/28/14.
|
|
||||||
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
#import <CoreData/CoreData.h>
|
|
||||||
|
|
||||||
|
|
||||||
@interface Settings : NSManagedObject
|
|
||||||
|
|
||||||
@property (nonatomic, retain) NSNumber * bitrate;
|
|
||||||
@property (nonatomic, retain) NSNumber * framerate;
|
|
||||||
@property (nonatomic, retain) NSNumber * height;
|
|
||||||
@property (nonatomic, retain) NSNumber * width;
|
|
||||||
@property (nonatomic, retain) NSNumber * onscreenControls;
|
|
||||||
@property (nonatomic, retain) NSString * uniqueId;
|
|
||||||
|
|
||||||
@end
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
//
|
|
||||||
// Settings.m
|
|
||||||
// Moonlight
|
|
||||||
//
|
|
||||||
// Created by Diego Waxemberg on 10/28/14.
|
|
||||||
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "Settings.h"
|
|
||||||
|
|
||||||
|
|
||||||
@implementation Settings
|
|
||||||
|
|
||||||
@dynamic bitrate;
|
|
||||||
@dynamic framerate;
|
|
||||||
@dynamic height;
|
|
||||||
@dynamic width;
|
|
||||||
@dynamic onscreenControls;
|
|
||||||
@dynamic uniqueId;
|
|
||||||
|
|
||||||
@end
|
|
||||||
@@ -6,15 +6,25 @@
|
|||||||
// Copyright © 2015 Moonlight Stream. All rights reserved.
|
// Copyright © 2015 Moonlight Stream. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import "TemporaryHost.h"
|
||||||
#import "Host.h"
|
#import "App+CoreDataClass.h"
|
||||||
|
|
||||||
@interface TemporaryApp : NSObject
|
@interface TemporaryApp : NSObject
|
||||||
|
|
||||||
@property (nullable, nonatomic, retain) NSString *id;
|
@property (nullable, nonatomic, retain) NSString *id;
|
||||||
@property (nullable, nonatomic, retain) NSData *image;
|
|
||||||
@property (nullable, nonatomic, retain) NSString *name;
|
@property (nullable, nonatomic, retain) NSString *name;
|
||||||
@property (nonatomic) BOOL isRunning;
|
@property (nullable, nonatomic, retain) NSString *installPath;
|
||||||
@property (nullable, nonatomic, retain) Host *host;
|
@property (nonatomic) BOOL hdrSupported;
|
||||||
|
@property (nullable, nonatomic, retain) TemporaryHost *host;
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
- (id) initFromApp:(App*)app withTempHost:(TemporaryHost*)tempHost;
|
||||||
|
|
||||||
|
- (NSComparisonResult)compareName:(TemporaryApp *)other;
|
||||||
|
|
||||||
|
- (void) propagateChangesToParent:(App*)parent withHost:(Host*)host;
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -10,4 +10,43 @@
|
|||||||
|
|
||||||
@implementation TemporaryApp
|
@implementation TemporaryApp
|
||||||
|
|
||||||
|
- (id) initFromApp:(App*)app withTempHost:(TemporaryHost*)tempHost {
|
||||||
|
self = [self init];
|
||||||
|
|
||||||
|
self.id = app.id;
|
||||||
|
self.name = app.name;
|
||||||
|
self.hdrSupported = app.hdrSupported;
|
||||||
|
self.host = tempHost;
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) propagateChangesToParent:(App*)parent withHost:(Host*)host {
|
||||||
|
parent.id = self.id;
|
||||||
|
parent.name = self.name;
|
||||||
|
parent.hdrSupported = self.hdrSupported;
|
||||||
|
parent.host = host;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSComparisonResult)compareName:(TemporaryApp *)other {
|
||||||
|
return [self.name caseInsensitiveCompare:other.name];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSUInteger)hash {
|
||||||
|
return [self.host.uuid hash] * 31 + [self.id intValue];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)isEqual:(id)object {
|
||||||
|
if (self == object) {
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (![object isKindOfClass:[App class]]) {
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [self.host.uuid isEqualToString:((App*)object).host.uuid] &&
|
||||||
|
[self.id isEqualToString:((App*)object).id];
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
//
|
||||||
|
// TemporaryHost.h
|
||||||
|
// Moonlight
|
||||||
|
//
|
||||||
|
// Created by Cameron Gutman on 12/1/15.
|
||||||
|
// Copyright © 2015 Moonlight Stream. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "Utils.h"
|
||||||
|
#import "Host+CoreDataClass.h"
|
||||||
|
|
||||||
|
@interface TemporaryHost : NSObject
|
||||||
|
|
||||||
|
@property (nonatomic) State state;
|
||||||
|
@property (nonatomic) PairState pairState;
|
||||||
|
@property (nonatomic, nullable) NSString * activeAddress;
|
||||||
|
@property (nonatomic, nullable) NSString * currentGame;
|
||||||
|
|
||||||
|
@property (nonatomic, nullable, retain) NSData *serverCert;
|
||||||
|
@property (nonatomic, nullable, retain) NSString *address;
|
||||||
|
@property (nonatomic, nullable, retain) NSString *externalAddress;
|
||||||
|
@property (nonatomic, nullable, retain) NSString *localAddress;
|
||||||
|
@property (nonatomic, nullable, retain) NSString *ipv6Address;
|
||||||
|
@property (nonatomic, nullable, retain) NSString *mac;
|
||||||
|
@property (nonatomic) int serverCodecModeSupport;
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@property (nonatomic, retain) NSString *name;
|
||||||
|
@property (nonatomic, retain) NSString *uuid;
|
||||||
|
@property (nonatomic, retain) NSMutableSet *appList;
|
||||||
|
|
||||||
|
- (id) initFromHost:(Host*)host;
|
||||||
|
|
||||||
|
- (NSComparisonResult)compareName:(TemporaryHost *)other;
|
||||||
|
|
||||||
|
- (void) propagateChangesToParent:(Host*)host;
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
//
|
||||||
|
// TemporaryHost.m
|
||||||
|
// Moonlight
|
||||||
|
//
|
||||||
|
// Created by Cameron Gutman on 12/1/15.
|
||||||
|
// Copyright © 2015 Moonlight Stream. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "DataManager.h"
|
||||||
|
#import "TemporaryHost.h"
|
||||||
|
#import "TemporaryApp.h"
|
||||||
|
|
||||||
|
@implementation TemporaryHost
|
||||||
|
|
||||||
|
- (id) init {
|
||||||
|
self = [super init];
|
||||||
|
self.appList = [[NSMutableSet alloc] init];
|
||||||
|
self.currentGame = @"0";
|
||||||
|
self.state = StateUnknown;
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id) initFromHost:(Host*)host {
|
||||||
|
self = [self init];
|
||||||
|
|
||||||
|
self.address = host.address;
|
||||||
|
self.externalAddress = host.externalAddress;
|
||||||
|
self.localAddress = host.localAddress;
|
||||||
|
self.ipv6Address = host.ipv6Address;
|
||||||
|
self.mac = host.mac;
|
||||||
|
self.name = host.name;
|
||||||
|
self.uuid = host.uuid;
|
||||||
|
self.serverCodecModeSupport = host.serverCodecModeSupport;
|
||||||
|
self.serverCert = host.serverCert;
|
||||||
|
|
||||||
|
// Ensure we don't use a stale cached pair state if we haven't pinned the cert yet
|
||||||
|
self.pairState = host.serverCert ? [host.pairState intValue] : PairStateUnpaired;
|
||||||
|
|
||||||
|
NSMutableSet *appList = [[NSMutableSet alloc] init];
|
||||||
|
|
||||||
|
for (App* app in host.appList) {
|
||||||
|
TemporaryApp *tempApp = [[TemporaryApp alloc] initFromApp:app withTempHost:self];
|
||||||
|
[appList addObject:tempApp];
|
||||||
|
}
|
||||||
|
|
||||||
|
self.appList = appList;
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) propagateChangesToParent:(Host*)parentHost {
|
||||||
|
// Avoid overwriting existing data with nil if
|
||||||
|
// we don't have everything populated in the temporary
|
||||||
|
// host.
|
||||||
|
if (self.address != nil) {
|
||||||
|
parentHost.address = self.address;
|
||||||
|
}
|
||||||
|
if (self.externalAddress != nil) {
|
||||||
|
parentHost.externalAddress = self.externalAddress;
|
||||||
|
}
|
||||||
|
if (self.localAddress != nil) {
|
||||||
|
parentHost.localAddress = self.localAddress;
|
||||||
|
}
|
||||||
|
if (self.ipv6Address != nil) {
|
||||||
|
parentHost.ipv6Address = self.ipv6Address;
|
||||||
|
}
|
||||||
|
if (self.mac != nil) {
|
||||||
|
parentHost.mac = self.mac;
|
||||||
|
}
|
||||||
|
if (self.serverCert != nil) {
|
||||||
|
parentHost.serverCert = self.serverCert;
|
||||||
|
}
|
||||||
|
parentHost.name = self.name;
|
||||||
|
parentHost.uuid = self.uuid;
|
||||||
|
parentHost.serverCodecModeSupport = self.serverCodecModeSupport;
|
||||||
|
parentHost.pairState = [NSNumber numberWithInt:self.pairState];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSComparisonResult)compareName:(TemporaryHost *)other {
|
||||||
|
return [self.name caseInsensitiveCompare:other.name];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSUInteger)hash {
|
||||||
|
return [self.uuid hash];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)isEqual:(id)object {
|
||||||
|
if (self == object) {
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (![object isKindOfClass:[Host class]]) {
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [self.uuid isEqualToString:((Host*)object).uuid];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
//
|
||||||
|
// TemporarySettings.h
|
||||||
|
// Moonlight
|
||||||
|
//
|
||||||
|
// Created by Cameron Gutman on 12/1/15.
|
||||||
|
// Copyright © 2015 Moonlight Stream. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "Settings+CoreDataClass.h"
|
||||||
|
|
||||||
|
@interface TemporarySettings : NSObject
|
||||||
|
|
||||||
|
@property (nonatomic, retain) Settings * parent;
|
||||||
|
|
||||||
|
@property (nonatomic, retain) NSNumber * bitrate;
|
||||||
|
@property (nonatomic, retain) NSNumber * framerate;
|
||||||
|
@property (nonatomic, retain) NSNumber * height;
|
||||||
|
@property (nonatomic, retain) NSNumber * width;
|
||||||
|
@property (nonatomic, retain) NSNumber * onscreenControls;
|
||||||
|
@property (nonatomic, retain) NSString * uniqueId;
|
||||||
|
@property (nonatomic) BOOL streamingRemotely;
|
||||||
|
@property (nonatomic) BOOL useHevc;
|
||||||
|
@property (nonatomic) BOOL multiController;
|
||||||
|
@property (nonatomic) BOOL playAudioOnPC;
|
||||||
|
@property (nonatomic) BOOL optimizeGames;
|
||||||
|
@property (nonatomic) BOOL enableHdr;
|
||||||
|
|
||||||
|
- (id) initFromSettings:(Settings*)settings;
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
//
|
||||||
|
// TemporarySettings.m
|
||||||
|
// Moonlight
|
||||||
|
//
|
||||||
|
// Created by Cameron Gutman on 12/1/15.
|
||||||
|
// Copyright © 2015 Moonlight Stream. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "TemporarySettings.h"
|
||||||
|
#import "OnScreenControls.h"
|
||||||
|
|
||||||
|
@implementation TemporarySettings
|
||||||
|
|
||||||
|
- (id) initFromSettings:(Settings*)settings {
|
||||||
|
self = [self init];
|
||||||
|
|
||||||
|
self.parent = settings;
|
||||||
|
|
||||||
|
#if TARGET_OS_TV
|
||||||
|
NSInteger _bitrate = [[NSUserDefaults standardUserDefaults] integerForKey:@"bitrate"];
|
||||||
|
NSInteger _framerate = [[NSUserDefaults standardUserDefaults] integerForKey:@"framerate"];
|
||||||
|
|
||||||
|
if (_bitrate) {
|
||||||
|
self.bitrate = [NSNumber numberWithInteger:_bitrate];
|
||||||
|
} else {
|
||||||
|
self.bitrate = [NSNumber numberWithInteger:20000];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_framerate) {
|
||||||
|
self.framerate = [NSNumber numberWithInteger:_framerate];
|
||||||
|
} else {
|
||||||
|
self.framerate = [NSNumber numberWithInteger:60];
|
||||||
|
}
|
||||||
|
|
||||||
|
self.useHevc = [[NSUserDefaults standardUserDefaults] boolForKey:@"useHevc"] || NO;
|
||||||
|
self.playAudioOnPC = [[NSUserDefaults standardUserDefaults] boolForKey:@"audioOnPC"] || NO;
|
||||||
|
self.enableHdr = [[NSUserDefaults standardUserDefaults] boolForKey:@"enableHdr"] || NO;
|
||||||
|
self.optimizeGames = [[NSUserDefaults standardUserDefaults] boolForKey:@"optimizeGames"] || YES;
|
||||||
|
self.multiController = [[NSUserDefaults standardUserDefaults] boolForKey:@"multipleControllers"] || YES;
|
||||||
|
|
||||||
|
NSInteger _screenSize = [[NSUserDefaults standardUserDefaults] integerForKey:@"streamResolution"];
|
||||||
|
switch (_screenSize) {
|
||||||
|
case 0:
|
||||||
|
self.height = [NSNumber numberWithInteger:720];
|
||||||
|
self.width = [NSNumber numberWithInteger:1280];
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
self.height = [NSNumber numberWithInteger:2160];
|
||||||
|
self.width = [NSNumber numberWithInteger:3840];
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
default:
|
||||||
|
self.height = [NSNumber numberWithInteger:1080];
|
||||||
|
self.width = [NSNumber numberWithInteger:1920];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
self.onscreenControls = [NSNumber numberWithInteger:OnScreenControlsLevelOff];
|
||||||
|
#else
|
||||||
|
self.bitrate = settings.bitrate;
|
||||||
|
self.framerate = settings.framerate;
|
||||||
|
self.height = settings.height;
|
||||||
|
self.width = settings.width;
|
||||||
|
self.useHevc = settings.useHevc;
|
||||||
|
self.playAudioOnPC = settings.playAudioOnPC;
|
||||||
|
self.enableHdr = settings.enableHdr;
|
||||||
|
self.optimizeGames = settings.optimizeGames;
|
||||||
|
self.multiController = settings.multiController;
|
||||||
|
self.onscreenControls = settings.onscreenControls;
|
||||||
|
#endif
|
||||||
|
self.uniqueId = settings.uniqueId;
|
||||||
|
self.streamingRemotely = settings.streamingRemotely;
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -1,203 +0,0 @@
|
|||||||
Font data copyright Google 2012
|
|
||||||
|
|
||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "1x",
|
|
||||||
"filename" : "limelight_computer_add_icon_1x.png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "2x",
|
|
||||||
"filename" : "limelight_computer_add_icon_2x.png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "3x",
|
|
||||||
"filename" : "limelight_computer_add_icon_3x.png"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
@@ -2,12 +2,12 @@
|
|||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
|
"filename" : "add.pdf",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "2x",
|
"scale" : "2x"
|
||||||
"filename" : "logo_universal.png"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
|
After Width: | Height: | Size: 32 KiB |
@@ -1,76 +1,108 @@
|
|||||||
{
|
{
|
||||||
"images" : [
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"size" : "20x20",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"size" : "20x20",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"size" : "29x29",
|
"size" : "29x29",
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"filename" : "app_icon_settings_58x58_@2x-1.png",
|
"filename" : "Icon-App-29x29@2x.png",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "29x29",
|
"size" : "29x29",
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"filename" : "app_icon_settings_87x87_@3x.png",
|
"filename" : "Icon-App-29x29@3x.png",
|
||||||
"scale" : "3x"
|
"scale" : "3x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "40x40",
|
"size" : "40x40",
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"filename" : "app_icon_spotlight_80x80_@2x-1.png",
|
"filename" : "Icon-App-40x40@2x.png",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "40x40",
|
"size" : "40x40",
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"filename" : "app_icon_spotlight_120x120_@3x.png",
|
"filename" : "Icon-App-40x40@3x.png",
|
||||||
"scale" : "3x"
|
"scale" : "3x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "60x60",
|
"size" : "60x60",
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"filename" : "app_icon_120x120_@2x.png",
|
"filename" : "Icon-App-60x60@2x.png",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "60x60",
|
"size" : "60x60",
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"filename" : "app_icon_180x180_@3x.png",
|
"filename" : "Icon-App-60x60@3x.png",
|
||||||
"scale" : "3x"
|
"scale" : "3x"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"size" : "20x20",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"size" : "20x20",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"size" : "29x29",
|
"size" : "29x29",
|
||||||
"idiom" : "ipad",
|
"idiom" : "ipad",
|
||||||
"filename" : "app_icon_settings_29x29_@1x.png",
|
"filename" : "Icon-App-29x29@1x.png",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "29x29",
|
"size" : "29x29",
|
||||||
"idiom" : "ipad",
|
"idiom" : "ipad",
|
||||||
"filename" : "app_icon_settings_58x58_@2x.png",
|
"filename" : "Icon-App-29x29@2x-1.png",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "40x40",
|
"size" : "40x40",
|
||||||
"idiom" : "ipad",
|
"idiom" : "ipad",
|
||||||
"filename" : "app_icon_spotlight_40x40_@1x.png",
|
"filename" : "Icon-App-40x40@1x.png",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "40x40",
|
"size" : "40x40",
|
||||||
"idiom" : "ipad",
|
"idiom" : "ipad",
|
||||||
"filename" : "app_icon_spotlight_80x80_@2x.png",
|
"filename" : "Icon-App-40x40@2x-1.png",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "76x76",
|
"size" : "76x76",
|
||||||
"idiom" : "ipad",
|
"idiom" : "ipad",
|
||||||
"filename" : "app_icon_76x76_@1x.png",
|
"filename" : "Icon-App-76x76@1x.png",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "76x76",
|
"size" : "76x76",
|
||||||
"idiom" : "ipad",
|
"idiom" : "ipad",
|
||||||
"filename" : "app_icon_152x152_@2x.png",
|
"filename" : "Icon-App-76x76@2x.png",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "83.5x83.5",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-83.5x83.5@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "1024x1024",
|
||||||
|
"idiom" : "ios-marketing",
|
||||||
|
"filename" : "1024x icon.png",
|
||||||
|
"scale" : "1x"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"info" : {
|
"info" : {
|
||||||
|
|||||||
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 41 KiB |
@@ -0,0 +1,69 @@
|
|||||||
|
%PDF-1.5
|
||||||
|
%µí®û
|
||||||
|
4 0 obj
|
||||||
|
<< /Length 5 0 R
|
||||||
|
/Filter /FlateDecode
|
||||||
|
>>
|
||||||
|
stream
|
||||||
|
xœ]‘]!„ß{Š^ÀÊ”Àâ1<‚Ùøó êý…E␍ »ÛN‡Yp°µƒmª‰×=)´³×•÷§À×7iØ·¸Hbä"‹= ¯3_\W«FÆ!K©%ðÁ½ÛñÎX¤@;$Ú®‹ ”mÊê@Œ¬Ç¿ ‰µµ+¸“!ïd�±ùË}œ#¡TÙ;7_Û«†,ÚLÙ˜EˆMHS~»ì8Ðà—î�$ëô¾±ÍX™F—ØÙ´<òŸ\W¾Í¤MlæÜûþeÓeÇÿÎé¦Lg7
|
||||||
|
endstream
|
||||||
|
endobj
|
||||||
|
5 0 obj
|
||||||
|
212
|
||||||
|
endobj
|
||||||
|
3 0 obj
|
||||||
|
<<
|
||||||
|
/ExtGState <<
|
||||||
|
/a0 << /CA 0.3 /ca 0.3 >>
|
||||||
|
/a1 << /CA 1 /ca 1 >>
|
||||||
|
>>
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
2 0 obj
|
||||||
|
<< /Type /Page % 1
|
||||||
|
/Parent 1 0 R
|
||||||
|
/MediaBox [ 0 0 225 225 ]
|
||||||
|
/Contents 4 0 R
|
||||||
|
/Group <<
|
||||||
|
/Type /Group
|
||||||
|
/S /Transparency
|
||||||
|
/I true
|
||||||
|
/CS /DeviceRGB
|
||||||
|
>>
|
||||||
|
/Resources 3 0 R
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
1 0 obj
|
||||||
|
<< /Type /Pages
|
||||||
|
/Kids [ 2 0 R ]
|
||||||
|
/Count 1
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
6 0 obj
|
||||||
|
<< /Producer (cairo 1.16.0 (https://cairographics.org))
|
||||||
|
/CreationDate (D:20190830180928-07'00)
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
7 0 obj
|
||||||
|
<< /Type /Catalog
|
||||||
|
/Pages 1 0 R
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
xref
|
||||||
|
0 8
|
||||||
|
0000000000 65535 f
|
||||||
|
0000000648 00000 n
|
||||||
|
0000000430 00000 n
|
||||||
|
0000000326 00000 n
|
||||||
|
0000000015 00000 n
|
||||||
|
0000000304 00000 n
|
||||||
|
0000000713 00000 n
|
||||||
|
0000000829 00000 n
|
||||||
|
trailer
|
||||||
|
<< /Size 8
|
||||||
|
/Root 7 0 R
|
||||||
|
/Info 6 0 R
|
||||||
|
>>
|
||||||
|
startxref
|
||||||
|
881
|
||||||
|
%%EOF
|
||||||
@@ -2,18 +2,16 @@
|
|||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "1x",
|
"filename" : "Computer.pdf",
|
||||||
"filename" : "limelight_computer_1x.png"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "2x",
|
"scale" : "2x"
|
||||||
"filename" : "limelight_computer_2x.png"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "3x",
|
"scale" : "3x"
|
||||||
"filename" : "limelight_computer_3x.png"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"info" : {
|
"info" : {
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 22 KiB |
@@ -2,17 +2,15 @@
|
|||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"filename" : "pull-arrow-light-14x31@1x.png",
|
"filename" : "error.pdf",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"filename" : "pull-arrow-light-28x61@2x.png",
|
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"filename" : "pull-arrow-light-42x92@3x.png",
|
|
||||||
"scale" : "3x"
|
"scale" : "3x"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
%PDF-1.5
|
||||||
|
%µí®û
|
||||||
|
4 0 obj
|
||||||
|
<< /Length 5 0 R
|
||||||
|
/Filter /FlateDecode
|
||||||
|
>>
|
||||||
|
stream
|
||||||
|
xœeŒÑ Ã0Dÿ5Å-Uªì(kt„Rhò¤Ýª8µ �t︓Bb�å¯B)öù̸=ó—Œõž1fã¸òä�W˜sW½P’jçjXbó†kG Ý’rî%c?õì=ÓãÍ?ÜðÒ~Óƒ~®L+·
|
||||||
|
endstream
|
||||||
|
endobj
|
||||||
|
5 0 obj
|
||||||
|
115
|
||||||
|
endobj
|
||||||
|
3 0 obj
|
||||||
|
<<
|
||||||
|
/ExtGState <<
|
||||||
|
/a0 << /CA 1 /ca 1 >>
|
||||||
|
>>
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
2 0 obj
|
||||||
|
<< /Type /Page % 1
|
||||||
|
/Parent 1 0 R
|
||||||
|
/MediaBox [ 0 0 75 75 ]
|
||||||
|
/Contents 4 0 R
|
||||||
|
/Group <<
|
||||||
|
/Type /Group
|
||||||
|
/S /Transparency
|
||||||
|
/I true
|
||||||
|
/CS /DeviceRGB
|
||||||
|
>>
|
||||||
|
/Resources 3 0 R
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
1 0 obj
|
||||||
|
<< /Type /Pages
|
||||||
|
/Kids [ 2 0 R ]
|
||||||
|
/Count 1
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
6 0 obj
|
||||||
|
<< /Producer (cairo 1.16.0 (https://cairographics.org))
|
||||||
|
/CreationDate (D:20190830180932-07'00)
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
7 0 obj
|
||||||
|
<< /Type /Catalog
|
||||||
|
/Pages 1 0 R
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
xref
|
||||||
|
0 8
|
||||||
|
0000000000 65535 f
|
||||||
|
0000000517 00000 n
|
||||||
|
0000000301 00000 n
|
||||||
|
0000000229 00000 n
|
||||||
|
0000000015 00000 n
|
||||||
|
0000000207 00000 n
|
||||||
|
0000000582 00000 n
|
||||||
|
0000000698 00000 n
|
||||||
|
trailer
|
||||||
|
<< /Size 8
|
||||||
|
/Root 7 0 R
|
||||||
|
/Info 6 0 R
|
||||||
|
>>
|
||||||
|
startxref
|
||||||
|
750
|
||||||
|
%%EOF
|
||||||
@@ -2,18 +2,17 @@
|
|||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "1x",
|
"filename" : "L2_60x60@1x.png",
|
||||||
"filename" : "L2_60x60@1x.png"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "2x",
|
"filename" : "L2_120x120@2x.png",
|
||||||
"filename" : "L2_120x120@2x.png"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "3x",
|
"scale" : "3x"
|
||||||
"filename" : "L2_180x180@3x.png"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"info" : {
|
"info" : {
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "lock.pdf",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
%PDF-1.5
|
||||||
|
%µí®û
|
||||||
|
4 0 obj
|
||||||
|
<< /Length 5 0 R
|
||||||
|
/Filter /FlateDecode
|
||||||
|
>>
|
||||||
|
stream
|
||||||
|
xœUQKnÅ Üû¾@]ÌÇðŽñŽP!õu‘,ÚÞ_*ø¥"‚ÐÌØcZë�×Öξ�q¯Ÿ¾$|ýBÊ␍×wb+Ć�À<¨ßiÒ£ŠvW:9œ˜�O™•¸*˜ZÎ šfwΰ çåœ;␍ΆïZ§ÙÛá¸Ñ£×¢¨›y‚xœõ)Ñσd§r³�ËzÚa™Ç2ºUãòõp�oâè(|T'–JIrpóiI+4²Æ8´6§…êªÀd€5‘ÄxÏ␍v•Η¢_:'áiÔò tíöËïºk'¶vÎèP{ŽÙûЂ®¼$X2q|h“ê†Wv¦Q«?¹¹«‚ù_ùŸý Oøp…&
|
||||||
|
endstream
|
||||||
|
endobj
|
||||||
|
5 0 obj
|
||||||
|
267
|
||||||
|
endobj
|
||||||
|
3 0 obj
|
||||||
|
<<
|
||||||
|
/ExtGState <<
|
||||||
|
/a0 << /CA 1 /ca 1 >>
|
||||||
|
>>
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
2 0 obj
|
||||||
|
<< /Type /Page % 1
|
||||||
|
/Parent 1 0 R
|
||||||
|
/MediaBox [ 0 0 75 75 ]
|
||||||
|
/Contents 4 0 R
|
||||||
|
/Group <<
|
||||||
|
/Type /Group
|
||||||
|
/S /Transparency
|
||||||
|
/I true
|
||||||
|
/CS /DeviceRGB
|
||||||
|
>>
|
||||||
|
/Resources 3 0 R
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
1 0 obj
|
||||||
|
<< /Type /Pages
|
||||||
|
/Kids [ 2 0 R ]
|
||||||
|
/Count 1
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
6 0 obj
|
||||||
|
<< /Producer (cairo 1.16.0 (https://cairographics.org))
|
||||||
|
/CreationDate (D:20190830180930-07'00)
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
7 0 obj
|
||||||
|
<< /Type /Catalog
|
||||||
|
/Pages 1 0 R
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
xref
|
||||||
|
0 8
|
||||||
|
0000000000 65535 f
|
||||||
|
0000000669 00000 n
|
||||||
|
0000000453 00000 n
|
||||||
|
0000000381 00000 n
|
||||||
|
0000000015 00000 n
|
||||||
|
0000000359 00000 n
|
||||||
|
0000000734 00000 n
|
||||||
|
0000000850 00000 n
|
||||||
|
trailer
|
||||||
|
<< /Size 8
|
||||||
|
/Root 7 0 R
|
||||||
|
/Info 6 0 R
|
||||||
|
>>
|
||||||
|
startxref
|
||||||
|
902
|
||||||
|
%%EOF
|
||||||
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 901 B |
|
Before Width: | Height: | Size: 812 B |
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -2,18 +2,17 @@
|
|||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "1x",
|
"filename" : "R2_60x60@1x.png",
|
||||||
"filename" : "R2_60x60@1x.png"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "2x",
|
"filename" : "R2_120x120@2x.png",
|
||||||
"filename" : "R2_120x120@2x.png"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "3x",
|
"scale" : "3x"
|
||||||
"filename" : "R2_120x120@3x.png"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"info" : {
|
"info" : {
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 8.7 KiB |
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "settings.pdf",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
%PDF-1.5
|
||||||
|
%µí®û
|
||||||
|
4 0 obj
|
||||||
|
<< /Length 5 0 R
|
||||||
|
/Filter /FlateDecode
|
||||||
|
>>
|
||||||
|
stream
|
||||||
|
xœeTIŽÜ0¼óú@‰Z,=#OÌÌA}Hò ,.== °»d®Å¢j*òûVåq�t?èÕ„ßï÷ôýgIï¨]y¤V3�ôHç2Ù¡?¿ÁøvÐKÞm¹¥Þòåö÷ƒþ|=òp,EØÏ„ö÷N4vnbjÏGª#OxRå\ÇL¯68Z+€™ß‰�p´÷ä¼*G”iÆî3ÃÕ}ÓQ¯Þã ^Ê‘ûÜÿÄ<O¬ER„úR\¸Jï-ï2�‘G
|
||||||
|
´2×␍OA nŸ;J^CXFR¾V»!PïJ” 8 šÂ�øv8␍Ö,G³Î␍<Q°À.¾×¥VÛ�|ÄX¯Üa\2zí37)qT+fä‚䌗iGJ‚«¦ílyVîÒÏÖK�iZã¦Wû¦ˆ Ζ+¿L·±5jX�~µP‚J«šH�)»~6xïè␍^¼´@i°ŒÎE_wbަ.e‹«%V^PsÙg.ÙâêŒê´^ºrw€J0Ê„—°$Ã!@’ti^[•µiZØÅ€¬»‡PADt?áTúÃqê"zÔáô¤B�횕Ԕ Šz[±²naä·÷*ô Ë "·ÊÁ+Ã$úÇ;Hæ®*ò°2�á@qlÂóÙÁ³w‚␍WªÒωѣV9vYø2ºhžW„«Ê0™1s7pAz(—+©\ÎÞÃ�ÚU=êD±
|
||||||
|
lèk"␍š}�Ę•-_²—m }%ßO0ë›®½Ïë»-X·Ëö¾Ùú€yÖ␍¶üÖZÛy^)&†¸nL(¯WÑÿWÓý ¿¾?1
|
||||||
|
endstream
|
||||||
|
endobj
|
||||||
|
5 0 obj
|
||||||
|
608
|
||||||
|
endobj
|
||||||
|
3 0 obj
|
||||||
|
<<
|
||||||
|
/ExtGState <<
|
||||||
|
/a0 << /CA 1 /ca 1 >>
|
||||||
|
>>
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
2 0 obj
|
||||||
|
<< /Type /Page % 1
|
||||||
|
/Parent 1 0 R
|
||||||
|
/MediaBox [ 0 0 75 75 ]
|
||||||
|
/Contents 4 0 R
|
||||||
|
/Group <<
|
||||||
|
/Type /Group
|
||||||
|
/S /Transparency
|
||||||
|
/I true
|
||||||
|
/CS /DeviceRGB
|
||||||
|
>>
|
||||||
|
/Resources 3 0 R
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
1 0 obj
|
||||||
|
<< /Type /Pages
|
||||||
|
/Kids [ 2 0 R ]
|
||||||
|
/Count 1
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
6 0 obj
|
||||||
|
<< /Producer (cairo 1.16.0 (https://cairographics.org))
|
||||||
|
/CreationDate (D:20190830202122-07'00)
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
7 0 obj
|
||||||
|
<< /Type /Catalog
|
||||||
|
/Pages 1 0 R
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
xref
|
||||||
|
0 8
|
||||||
|
0000000000 65535 f
|
||||||
|
0000001010 00000 n
|
||||||
|
0000000794 00000 n
|
||||||
|
0000000722 00000 n
|
||||||
|
0000000015 00000 n
|
||||||
|
0000000700 00000 n
|
||||||
|
0000001075 00000 n
|
||||||
|
0000001191 00000 n
|
||||||
|
trailer
|
||||||
|
<< /Size 8
|
||||||
|
/Root 7 0 R
|
||||||
|
/Info 6 0 R
|
||||||
|
>>
|
||||||
|
startxref
|
||||||
|
1243
|
||||||
|
%%EOF
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "up_arrow.pdf",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
%PDF-1.5
|
||||||
|
%µí®û
|
||||||
|
4 0 obj
|
||||||
|
<< /Length 5 0 R
|
||||||
|
/Filter /FlateDecode
|
||||||
|
>>
|
||||||
|
stream
|
||||||
|
xœUŽQ
|
||||||
|
ƒ@Dÿsй€1ÙÍF=†G(…êÇú¡½?-e`ƒÌ$
|
||||||
|
5Ö¼ZIqh›Ð¾Ó—4qAî¨ó ÓsTdã¹dl>üØ�H… {º‰n|–KÏâé,Ë}°?‡ê5ôª™ÿ^øÐH;U$}
|
||||||
|
endstream
|
||||||
|
endobj
|
||||||
|
5 0 obj
|
||||||
|
114
|
||||||
|
endobj
|
||||||
|
3 0 obj
|
||||||
|
<<
|
||||||
|
/ExtGState <<
|
||||||
|
/a0 << /CA 1 /ca 1 >>
|
||||||
|
>>
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
2 0 obj
|
||||||
|
<< /Type /Page % 1
|
||||||
|
/Parent 1 0 R
|
||||||
|
/MediaBox [ 0 0 75 75 ]
|
||||||
|
/Contents 4 0 R
|
||||||
|
/Group <<
|
||||||
|
/Type /Group
|
||||||
|
/S /Transparency
|
||||||
|
/I true
|
||||||
|
/CS /DeviceRGB
|
||||||
|
>>
|
||||||
|
/Resources 3 0 R
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
1 0 obj
|
||||||
|
<< /Type /Pages
|
||||||
|
/Kids [ 2 0 R ]
|
||||||
|
/Count 1
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
6 0 obj
|
||||||
|
<< /Producer (cairo 1.16.0 (https://cairographics.org))
|
||||||
|
/CreationDate (D:20190830203401-07'00)
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
7 0 obj
|
||||||
|
<< /Type /Catalog
|
||||||
|
/Pages 1 0 R
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
xref
|
||||||
|
0 8
|
||||||
|
0000000000 65535 f
|
||||||
|
0000000516 00000 n
|
||||||
|
0000000300 00000 n
|
||||||
|
0000000228 00000 n
|
||||||
|
0000000015 00000 n
|
||||||
|
0000000206 00000 n
|
||||||
|
0000000581 00000 n
|
||||||
|
0000000697 00000 n
|
||||||
|
trailer
|
||||||
|
<< /Size 8
|
||||||
|
/Root 7 0 R
|
||||||
|
/Info 6 0 R
|
||||||
|
>>
|
||||||
|
startxref
|
||||||
|
749
|
||||||
|
%%EOF
|
||||||
@@ -2,22 +2,25 @@
|
|||||||
// Controller.h
|
// Controller.h
|
||||||
// Moonlight
|
// Moonlight
|
||||||
//
|
//
|
||||||
// Created by Diego Waxemberg on 2/1/15.
|
// Created by Cameron Gutman on 2/11/19.
|
||||||
// Copyright (c) 2015 Moonlight Stream. All rights reserved.
|
// Copyright © 2019 Moonlight Game Streaming Project. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
@import GameController;
|
||||||
|
|
||||||
@interface Controller : NSObject
|
@interface Controller : NSObject
|
||||||
|
|
||||||
@property (nonatomic) int playerIndex;
|
@property (nullable, nonatomic, retain) GCController* gamepad;
|
||||||
@property (nonatomic) int lastButtonFlags;
|
@property (nonatomic) int playerIndex;
|
||||||
@property (nonatomic) int emulatingButtonFlags;
|
@property (nonatomic) int lastButtonFlags;
|
||||||
@property (nonatomic) char lastLeftTrigger;
|
@property (nonatomic) int emulatingButtonFlags;
|
||||||
@property (nonatomic) char lastRightTrigger;
|
@property (nonatomic) unsigned char lastLeftTrigger;
|
||||||
@property (nonatomic) short lastLeftStickX;
|
@property (nonatomic) unsigned char lastRightTrigger;
|
||||||
@property (nonatomic) short lastLeftStickY;
|
@property (nonatomic) short lastLeftStickX;
|
||||||
@property (nonatomic) short lastRightStickX;
|
@property (nonatomic) short lastLeftStickY;
|
||||||
@property (nonatomic) short lastRightStickY;
|
@property (nonatomic) short lastRightStickX;
|
||||||
|
@property (nonatomic) short lastRightStickY;
|
||||||
|
@property (nonatomic) unsigned short lowFreqMotor;
|
||||||
|
@property (nonatomic) unsigned short highFreqMotor;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -2,14 +2,12 @@
|
|||||||
// Controller.m
|
// Controller.m
|
||||||
// Moonlight
|
// Moonlight
|
||||||
//
|
//
|
||||||
// Created by Diego Waxemberg on 2/1/15.
|
// Created by Cameron Gutman on 2/11/19.
|
||||||
// Copyright (c) 2015 Moonlight Stream. All rights reserved.
|
// Copyright © 2019 Moonlight Game Streaming Project. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#import "Controller.h"
|
#include "Controller.h"
|
||||||
|
|
||||||
@implementation Controller
|
@implementation Controller
|
||||||
@synthesize playerIndex;
|
|
||||||
@synthesize lastButtonFlags, emulatingButtonFlags, lastLeftTrigger, lastRightTrigger;
|
|
||||||
@synthesize lastLeftStickX, lastLeftStickY, lastRightStickX, lastRightStickY;
|
|
||||||
@end
|
@end
|
||||||
@@ -6,16 +6,28 @@
|
|||||||
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
|
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
// Swift
|
||||||
|
#if !TARGET_OS_IPHONE
|
||||||
|
#import "Gamepad.h"
|
||||||
|
#endif
|
||||||
|
#import "StreamConfiguration.h"
|
||||||
#import "Controller.h"
|
#import "Controller.h"
|
||||||
|
|
||||||
@class OnScreenControls;
|
@class OnScreenControls;
|
||||||
|
|
||||||
@interface ControllerSupport : NSObject
|
@interface ControllerSupport : NSObject
|
||||||
|
|
||||||
-(id) init;
|
-(id) initWithConfig:(StreamConfiguration*)streamConfig;
|
||||||
|
|
||||||
|
#if TARGET_OS_IPHONE
|
||||||
-(void) initAutoOnScreenControlMode:(OnScreenControls*)osc;
|
-(void) initAutoOnScreenControlMode:(OnScreenControls*)osc;
|
||||||
-(void) cleanup;
|
-(void) cleanup;
|
||||||
|
-(Controller*) getOscController;
|
||||||
|
#else
|
||||||
|
-(void) assignGamepad:(struct Gamepad_device *)gamepad;
|
||||||
|
-(void) removeGamepad:(struct Gamepad_device *)gamepad;
|
||||||
|
-(NSMutableDictionary*) getControllers;
|
||||||
|
#endif
|
||||||
|
|
||||||
-(void) updateLeftStick:(Controller*)controller x:(short)x y:(short)y;
|
-(void) updateLeftStick:(Controller*)controller x:(short)x y:(short)y;
|
||||||
-(void) updateRightStick:(Controller*)controller x:(short)x y:(short)y;
|
-(void) updateRightStick:(Controller*)controller x:(short)x y:(short)y;
|
||||||
@@ -30,6 +42,10 @@
|
|||||||
|
|
||||||
-(void) updateFinished:(Controller*)controller;
|
-(void) updateFinished:(Controller*)controller;
|
||||||
|
|
||||||
|
-(void) rumble:(unsigned short)controllerNumber lowFreqMotor:(unsigned short)lowFreqMotor highFreqMotor:(unsigned short)highFreqMotor;
|
||||||
|
|
||||||
|
+(int) getConnectedGamepadMask:(StreamConfiguration*)streamConfig;
|
||||||
|
|
||||||
@property (nonatomic, strong) id connectObserver;
|
@property (nonatomic, strong) id connectObserver;
|
||||||
@property (nonatomic, strong) id disconnectObserver;
|
@property (nonatomic, strong) id disconnectObserver;
|
||||||
|
|
||||||
|
|||||||
@@ -7,28 +7,77 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#import "ControllerSupport.h"
|
#import "ControllerSupport.h"
|
||||||
#import "OnScreenControls.h"
|
|
||||||
#import "Controller.h"
|
#import "Controller.h"
|
||||||
|
|
||||||
|
#import "OnScreenControls.h"
|
||||||
|
#if !TARGET_OS_IPHONE
|
||||||
|
#import "Gamepad.h"
|
||||||
|
#import "Control.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#import "DataManager.h"
|
||||||
#include "Limelight.h"
|
#include "Limelight.h"
|
||||||
|
|
||||||
@import GameController;
|
@import GameController;
|
||||||
|
@import AudioToolbox;
|
||||||
|
|
||||||
@implementation ControllerSupport {
|
@implementation ControllerSupport {
|
||||||
NSLock *_controllerStreamLock;
|
NSLock *_controllerStreamLock;
|
||||||
NSMutableDictionary *_controllers;
|
NSMutableDictionary *_controllers;
|
||||||
|
NSTimer *_rumbleTimer;
|
||||||
|
|
||||||
OnScreenControls *_osc;
|
OnScreenControls *_osc;
|
||||||
|
|
||||||
char _controllerNumbers;
|
// This controller object is shared between on-screen controls
|
||||||
|
// and player 0
|
||||||
|
Controller *_player0osc;
|
||||||
|
|
||||||
#define EMULATING_SELECT 0x1
|
#define EMULATING_SELECT 0x1
|
||||||
#define EMULATING_SPECIAL 0x2
|
#define EMULATING_SPECIAL 0x2
|
||||||
|
|
||||||
|
bool _oscEnabled;
|
||||||
|
char _controllerNumbers;
|
||||||
|
bool _multiController;
|
||||||
}
|
}
|
||||||
|
|
||||||
// UPDATE_BUTTON_FLAG(controller, flag, pressed)
|
// UPDATE_BUTTON_FLAG(controller, flag, pressed)
|
||||||
#define UPDATE_BUTTON_FLAG(controller, x, y) \
|
#define UPDATE_BUTTON_FLAG(controller, x, y) \
|
||||||
((y) ? [self setButtonFlag:controller flags:x] : [self clearButtonFlag:controller flags:x])
|
((y) ? [self setButtonFlag:controller flags:x] : [self clearButtonFlag:controller flags:x])
|
||||||
|
|
||||||
|
-(void) rumbleController: (Controller*)controller
|
||||||
|
{
|
||||||
|
#if 0
|
||||||
|
// Only vibrate if the amplitude is large enough
|
||||||
|
if (controller.lowFreqMotor > 0x5000 || controller.highFreqMotor > 0x5000) {
|
||||||
|
// If the gamepad is nil (on-screen controls) or it's attached to the device,
|
||||||
|
// then vibrate the device itself
|
||||||
|
if (controller.gamepad == nil || [controller.gamepad isAttachedToDevice]) {
|
||||||
|
AudioServicesPlayAlertSound(kSystemSoundID_Vibrate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void) rumble:(unsigned short)controllerNumber lowFreqMotor:(unsigned short)lowFreqMotor highFreqMotor:(unsigned short)highFreqMotor
|
||||||
|
{
|
||||||
|
Controller* controller = [_controllers objectForKey:[NSNumber numberWithInteger:controllerNumber]];
|
||||||
|
if (controller == nil && controllerNumber == 0 && _oscEnabled) {
|
||||||
|
// No physical controller, but we have on-screen controls
|
||||||
|
controller = _player0osc;
|
||||||
|
}
|
||||||
|
if (controller == nil) {
|
||||||
|
// No connected controller for this player
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the motor levels for the rumble timer to grab next iteration
|
||||||
|
controller.lowFreqMotor = lowFreqMotor;
|
||||||
|
controller.highFreqMotor = highFreqMotor;
|
||||||
|
|
||||||
|
// Rumble now to ensure short vibrations aren't missed
|
||||||
|
[self rumbleController:controller];
|
||||||
|
}
|
||||||
|
|
||||||
-(void) updateLeftStick:(Controller*)controller x:(short)x y:(short)y
|
-(void) updateLeftStick:(Controller*)controller x:(short)x y:(short)y
|
||||||
{
|
{
|
||||||
@synchronized(controller) {
|
@synchronized(controller) {
|
||||||
@@ -105,17 +154,16 @@
|
|||||||
-(void) updateButtonFlags:(Controller*)controller flags:(int)flags
|
-(void) updateButtonFlags:(Controller*)controller flags:(int)flags
|
||||||
{
|
{
|
||||||
@synchronized(controller) {
|
@synchronized(controller) {
|
||||||
int releasedButtons = (controller.lastButtonFlags ^ flags) & ~flags;
|
|
||||||
int pressedButtons = (controller.lastButtonFlags ^ flags) & flags;
|
|
||||||
|
|
||||||
controller.lastButtonFlags = flags;
|
controller.lastButtonFlags = flags;
|
||||||
|
|
||||||
// This must be called before handleSpecialCombosPressed
|
// This must be called before handleSpecialCombosPressed
|
||||||
// because we clear the original button flags there
|
// because we clear the original button flags there
|
||||||
|
int releasedButtons = (controller.lastButtonFlags ^ flags) & ~flags;
|
||||||
|
int pressedButtons = (controller.lastButtonFlags ^ flags) & flags;
|
||||||
|
|
||||||
[self handleSpecialCombosReleased:controller releasedButtons:releasedButtons];
|
[self handleSpecialCombosReleased:controller releasedButtons:releasedButtons];
|
||||||
|
|
||||||
[self handleSpecialCombosPressed:controller pressedButtons:pressedButtons];
|
[self handleSpecialCombosPressed:controller pressedButtons:pressedButtons];
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,11 +187,16 @@
|
|||||||
{
|
{
|
||||||
[_controllerStreamLock lock];
|
[_controllerStreamLock lock];
|
||||||
@synchronized(controller) {
|
@synchronized(controller) {
|
||||||
LiSendMultiControllerEvent(controller.playerIndex, controller.lastButtonFlags, controller.lastLeftTrigger, controller.lastRightTrigger, controller.lastLeftStickX, controller.lastLeftStickY, controller.lastRightStickX, controller.lastRightStickY);
|
// Player 1 is always present for OSC
|
||||||
|
LiSendMultiControllerEvent(_multiController ? controller.playerIndex : 0,
|
||||||
|
(_multiController ? _controllerNumbers : 1) | (_oscEnabled ? 1 : 0), controller.lastButtonFlags, controller.lastLeftTrigger, controller.lastRightTrigger, controller.lastLeftStickX, controller.lastLeftStickY, controller.lastRightStickX, controller.lastRightStickY);
|
||||||
}
|
}
|
||||||
[_controllerStreamLock unlock];
|
[_controllerStreamLock unlock];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||||
|
|
||||||
-(void) unregisterControllerCallbacks:(GCController*) controller
|
-(void) unregisterControllerCallbacks:(GCController*) controller
|
||||||
{
|
{
|
||||||
if (controller != NULL) {
|
if (controller != NULL) {
|
||||||
@@ -161,21 +214,32 @@
|
|||||||
-(void) registerControllerCallbacks:(GCController*) controller
|
-(void) registerControllerCallbacks:(GCController*) controller
|
||||||
{
|
{
|
||||||
if (controller != NULL) {
|
if (controller != NULL) {
|
||||||
controller.controllerPausedHandler = ^(GCController *controller) {
|
// On iOS 13, we want to use the new buttonMenu property which lets users hold down Start.
|
||||||
Controller* limeController = [_controllers objectForKey:[NSNumber numberWithInteger:controller.playerIndex]];
|
// On prior versions, we must use the controllerPausedHandler.
|
||||||
[self setButtonFlag:limeController flags:PLAY_FLAG];
|
if (@available(iOS 13.0, tvOS 13.0, *)) {
|
||||||
[self updateFinished:limeController];
|
// Nothing - buttonMenu is already handled in valueChangedHandler
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
controller.controllerPausedHandler = ^(GCController *controller) {
|
||||||
|
Controller* limeController = [self->_controllers objectForKey:[NSNumber numberWithInteger:controller.playerIndex]];
|
||||||
|
|
||||||
// Pause for 100 ms
|
// Get off the main thread
|
||||||
usleep(100 * 1000);
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
|
||||||
|
[self setButtonFlag:limeController flags:PLAY_FLAG];
|
||||||
|
[self updateFinished:limeController];
|
||||||
|
|
||||||
[self clearButtonFlag:limeController flags:PLAY_FLAG];
|
// Pause for 100 ms
|
||||||
[self updateFinished:limeController];
|
usleep(100 * 1000);
|
||||||
};
|
|
||||||
|
[self clearButtonFlag:limeController flags:PLAY_FLAG];
|
||||||
|
[self updateFinished:limeController];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (controller.extendedGamepad != NULL) {
|
if (controller.extendedGamepad != NULL) {
|
||||||
controller.extendedGamepad.valueChangedHandler = ^(GCExtendedGamepad *gamepad, GCControllerElement *element) {
|
controller.extendedGamepad.valueChangedHandler = ^(GCExtendedGamepad *gamepad, GCControllerElement *element) {
|
||||||
Controller* limeController = [_controllers objectForKey:[NSNumber numberWithInteger:gamepad.controller.playerIndex]];
|
Controller* limeController = [self->_controllers objectForKey:[NSNumber numberWithInteger:gamepad.controller.playerIndex]];
|
||||||
short leftStickX, leftStickY;
|
short leftStickX, leftStickY;
|
||||||
short rightStickX, rightStickY;
|
short rightStickX, rightStickY;
|
||||||
char leftTrigger, rightTrigger;
|
char leftTrigger, rightTrigger;
|
||||||
@@ -193,6 +257,26 @@
|
|||||||
UPDATE_BUTTON_FLAG(limeController, LB_FLAG, gamepad.leftShoulder.pressed);
|
UPDATE_BUTTON_FLAG(limeController, LB_FLAG, gamepad.leftShoulder.pressed);
|
||||||
UPDATE_BUTTON_FLAG(limeController, RB_FLAG, gamepad.rightShoulder.pressed);
|
UPDATE_BUTTON_FLAG(limeController, RB_FLAG, gamepad.rightShoulder.pressed);
|
||||||
|
|
||||||
|
// Yay, iOS 12.1 now supports analog stick buttons
|
||||||
|
if (@available(iOS 12.1, tvOS 12.1, *)) {
|
||||||
|
if (gamepad.leftThumbstickButton != nil) {
|
||||||
|
UPDATE_BUTTON_FLAG(limeController, LS_CLK_FLAG, gamepad.leftThumbstickButton.pressed);
|
||||||
|
}
|
||||||
|
if (gamepad.rightThumbstickButton != nil) {
|
||||||
|
UPDATE_BUTTON_FLAG(limeController, RS_CLK_FLAG, gamepad.rightThumbstickButton.pressed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (@available(iOS 13.0, tvOS 13.0, *)) {
|
||||||
|
// Menu button is mandatory, so no need to check for nil
|
||||||
|
UPDATE_BUTTON_FLAG(limeController, PLAY_FLAG, gamepad.buttonMenu.pressed);
|
||||||
|
|
||||||
|
// Options button is optional (only present on Xbox One S and PS4 gamepads)
|
||||||
|
if (gamepad.buttonOptions != nil) {
|
||||||
|
UPDATE_BUTTON_FLAG(limeController, BACK_FLAG, gamepad.buttonOptions.pressed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
leftStickX = gamepad.leftThumbstick.xAxis.value * 0x7FFE;
|
leftStickX = gamepad.leftThumbstick.xAxis.value * 0x7FFE;
|
||||||
leftStickY = gamepad.leftThumbstick.yAxis.value * 0x7FFE;
|
leftStickY = gamepad.leftThumbstick.yAxis.value * 0x7FFE;
|
||||||
|
|
||||||
@@ -210,7 +294,7 @@
|
|||||||
}
|
}
|
||||||
else if (controller.gamepad != NULL) {
|
else if (controller.gamepad != NULL) {
|
||||||
controller.gamepad.valueChangedHandler = ^(GCGamepad *gamepad, GCControllerElement *element) {
|
controller.gamepad.valueChangedHandler = ^(GCGamepad *gamepad, GCControllerElement *element) {
|
||||||
Controller* limeController = [_controllers objectForKey:[NSNumber numberWithInteger:gamepad.controller.playerIndex]];
|
Controller* limeController = [self->_controllers objectForKey:[NSNumber numberWithInteger:gamepad.controller.playerIndex]];
|
||||||
UPDATE_BUTTON_FLAG(limeController, A_FLAG, gamepad.buttonA.pressed);
|
UPDATE_BUTTON_FLAG(limeController, A_FLAG, gamepad.buttonA.pressed);
|
||||||
UPDATE_BUTTON_FLAG(limeController, B_FLAG, gamepad.buttonB.pressed);
|
UPDATE_BUTTON_FLAG(limeController, B_FLAG, gamepad.buttonB.pressed);
|
||||||
UPDATE_BUTTON_FLAG(limeController, X_FLAG, gamepad.buttonX.pressed);
|
UPDATE_BUTTON_FLAG(limeController, X_FLAG, gamepad.buttonX.pressed);
|
||||||
@@ -249,6 +333,18 @@
|
|||||||
if (controller != NULL) {
|
if (controller != NULL) {
|
||||||
if (controller.extendedGamepad != NULL) {
|
if (controller.extendedGamepad != NULL) {
|
||||||
level = OnScreenControlsLevelAutoGCExtendedGamepad;
|
level = OnScreenControlsLevelAutoGCExtendedGamepad;
|
||||||
|
if (@available(iOS 12.1, tvOS 12.1, *)) {
|
||||||
|
if (controller.extendedGamepad.leftThumbstickButton != nil &&
|
||||||
|
controller.extendedGamepad.rightThumbstickButton != nil) {
|
||||||
|
level = OnScreenControlsLevelAutoGCExtendedGamepadWithStickButtons;
|
||||||
|
if (@available(iOS 13.0, tvOS 13.0, *)) {
|
||||||
|
if (controller.extendedGamepad.buttonOptions != nil) {
|
||||||
|
// Has L3/R3 and Select, so we can show nothing :)
|
||||||
|
level = OnScreenControlsLevelOff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else if (controller.gamepad != NULL) {
|
else if (controller.gamepad != NULL) {
|
||||||
@@ -258,7 +354,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if TARGET_OS_IPHONE
|
||||||
[_osc setLevel:level];
|
[_osc setLevel:level];
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
-(void) initAutoOnScreenControlMode:(OnScreenControls*)osc
|
-(void) initAutoOnScreenControlMode:(OnScreenControls*)osc
|
||||||
@@ -272,9 +370,20 @@
|
|||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
if (!(_controllerNumbers & (1 << i))) {
|
if (!(_controllerNumbers & (1 << i))) {
|
||||||
_controllerNumbers |= (1 << i);
|
_controllerNumbers |= (1 << i);
|
||||||
Controller* limeController = [[Controller alloc] init];
|
|
||||||
controller.playerIndex = i;
|
controller.playerIndex = i;
|
||||||
limeController.playerIndex = i;
|
|
||||||
|
Controller* limeController;
|
||||||
|
|
||||||
|
if (i == 0) {
|
||||||
|
// Player 0 shares a controller object with the on-screen controls
|
||||||
|
limeController = _player0osc;
|
||||||
|
} else {
|
||||||
|
limeController = [[Controller alloc] init];
|
||||||
|
limeController.playerIndex = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
limeController.gamepad = controller;
|
||||||
|
|
||||||
[_controllers setObject:limeController forKey:[NSNumber numberWithInteger:controller.playerIndex]];
|
[_controllers setObject:limeController forKey:[NSNumber numberWithInteger:controller.playerIndex]];
|
||||||
|
|
||||||
Log(LOG_I, @"Assigning controller index: %d", i);
|
Log(LOG_I, @"Assigning controller index: %d", i);
|
||||||
@@ -283,25 +392,155 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
-(id) init
|
#if TARGET_OS_IPHONE
|
||||||
|
-(Controller*) getOscController {
|
||||||
|
return _player0osc;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
-(NSMutableDictionary*) getControllers {
|
||||||
|
return _controllers;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void) assignGamepad:(struct Gamepad_device *)gamepad {
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
if (!(_controllerNumbers & (1 << i))) {
|
||||||
|
_controllerNumbers |= (1 << i);
|
||||||
|
gamepad->deviceID = i;
|
||||||
|
NSLog(@"Gamepad device id: %u assigned", gamepad->deviceID);
|
||||||
|
Controller* limeController;
|
||||||
|
limeController = [[Controller alloc] init];
|
||||||
|
limeController.playerIndex = i;
|
||||||
|
|
||||||
|
[_controllers setObject:limeController forKey:[NSNumber numberWithInteger:i]];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void) removeGamepad:(struct Gamepad_device *)gamepad {
|
||||||
|
_controllerNumbers &= ~(1 << gamepad->deviceID);
|
||||||
|
Log(LOG_I, @"Unassigning controller index: %ld", (long)gamepad->deviceID);
|
||||||
|
|
||||||
|
// Inform the server of the updated active gamepads before removing this controller
|
||||||
|
[self updateFinished:[_controllers objectForKey:[NSNumber numberWithInteger:gamepad->deviceID]]];
|
||||||
|
[_controllers removeObjectForKey:[NSNumber numberWithInteger:gamepad->deviceID]];
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
+(bool) isSupportedGamepad:(GCController*) controller {
|
||||||
|
return controller.extendedGamepad != nil || controller.gamepad != nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma clang diagnostic pop
|
||||||
|
|
||||||
|
+(int) getGamepadCount {
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
for (GCController* controller in [GCController controllers]) {
|
||||||
|
if ([ControllerSupport isSupportedGamepad:controller]) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
+(int) getConnectedGamepadMask:(StreamConfiguration*)streamConfig {
|
||||||
|
int mask = 0;
|
||||||
|
|
||||||
|
if (streamConfig.multiController) {
|
||||||
|
int i = 0;
|
||||||
|
for (GCController* controller in [GCController controllers]) {
|
||||||
|
if ([ControllerSupport isSupportedGamepad:controller]) {
|
||||||
|
mask |= 1 << i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Some games don't deal with having controller reconnected
|
||||||
|
// properly so always report controller 1 if not in MC mode
|
||||||
|
mask = 0x1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if TARGET_OS_IPHONE
|
||||||
|
DataManager* dataMan = [[DataManager alloc] init];
|
||||||
|
OnScreenControlsLevel level = (OnScreenControlsLevel)[[dataMan getSettings].onscreenControls integerValue];
|
||||||
|
|
||||||
|
// Even if no gamepads are present, we will always count one
|
||||||
|
// if OSC is enabled.
|
||||||
|
if (level != OnScreenControlsLevelOff) {
|
||||||
|
mask |= 0x1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void) rumbleTimer
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
Controller* controller = [_controllers objectForKey:[NSNumber numberWithInteger:i]];
|
||||||
|
if (controller == nil && i == 0 && _oscEnabled) {
|
||||||
|
// No physical controller, but we have on-screen controls
|
||||||
|
controller = _player0osc;
|
||||||
|
}
|
||||||
|
if (controller == nil) {
|
||||||
|
// No connected controller for this player
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
[self rumbleController:controller];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-(id) initWithConfig:(StreamConfiguration*)streamConfig
|
||||||
{
|
{
|
||||||
self = [super init];
|
self = [super init];
|
||||||
|
|
||||||
_controllerStreamLock = [[NSLock alloc] init];
|
_controllerStreamLock = [[NSLock alloc] init];
|
||||||
_controllers = [[NSMutableDictionary alloc] init];
|
_controllers = [[NSMutableDictionary alloc] init];
|
||||||
_controllerNumbers = 0;
|
_controllerNumbers = 0;
|
||||||
|
_multiController = streamConfig.multiController;
|
||||||
|
|
||||||
|
_player0osc = [[Controller alloc] init];
|
||||||
|
_player0osc.playerIndex = 0;
|
||||||
|
|
||||||
|
#if TARGET_OS_IPHONE
|
||||||
|
DataManager* dataMan = [[DataManager alloc] init];
|
||||||
|
_oscEnabled = (OnScreenControlsLevel)[[dataMan getSettings].onscreenControls integerValue] != OnScreenControlsLevelOff;
|
||||||
|
#else
|
||||||
|
_oscEnabled = false;
|
||||||
|
initGamepad(self);
|
||||||
|
Gamepad_detectDevices();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
_rumbleTimer = [NSTimer scheduledTimerWithTimeInterval:0.20
|
||||||
|
target:self
|
||||||
|
selector:@selector(rumbleTimer)
|
||||||
|
userInfo:nil
|
||||||
|
repeats:YES];
|
||||||
|
|
||||||
|
Log(LOG_I, @"Number of supported controllers connected: %d", [ControllerSupport getGamepadCount]);
|
||||||
|
Log(LOG_I, @"Multi-controller: %d", _multiController);
|
||||||
|
|
||||||
Log(LOG_I, @"Number of controllers connected: %ld", (long)[[GCController controllers] count]);
|
|
||||||
for (GCController* controller in [GCController controllers]) {
|
for (GCController* controller in [GCController controllers]) {
|
||||||
[self assignController:controller];
|
if ([ControllerSupport isSupportedGamepad:controller]) {
|
||||||
[self registerControllerCallbacks:controller];
|
[self assignController:controller];
|
||||||
[self updateAutoOnScreenControlMode];
|
[self registerControllerCallbacks:controller];
|
||||||
|
[self updateAutoOnScreenControlMode];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.connectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCControllerDidConnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
self.connectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCControllerDidConnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||||
Log(LOG_I, @"Controller connected!");
|
Log(LOG_I, @"Controller connected!");
|
||||||
|
|
||||||
GCController* controller = note.object;
|
GCController* controller = note.object;
|
||||||
|
|
||||||
|
if (![ControllerSupport isSupportedGamepad:controller]) {
|
||||||
|
// Ignore micro gamepads and motion controllers
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
[self assignController:controller];
|
[self assignController:controller];
|
||||||
|
|
||||||
// Register callbacks on the new controller
|
// Register callbacks on the new controller
|
||||||
@@ -314,26 +553,42 @@
|
|||||||
Log(LOG_I, @"Controller disconnected!");
|
Log(LOG_I, @"Controller disconnected!");
|
||||||
|
|
||||||
GCController* controller = note.object;
|
GCController* controller = note.object;
|
||||||
|
|
||||||
|
if (![ControllerSupport isSupportedGamepad:controller]) {
|
||||||
|
// Ignore micro gamepads and motion controllers
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
[self unregisterControllerCallbacks:controller];
|
[self unregisterControllerCallbacks:controller];
|
||||||
[_controllers removeObjectForKey:[NSNumber numberWithInteger:controller.playerIndex]];
|
self->_controllerNumbers &= ~(1 << controller.playerIndex);
|
||||||
_controllerNumbers &= ~(1 << controller.playerIndex);
|
|
||||||
Log(LOG_I, @"Unassigning controller index: %ld", (long)controller.playerIndex);
|
Log(LOG_I, @"Unassigning controller index: %ld", (long)controller.playerIndex);
|
||||||
|
|
||||||
|
// Unset the GCController on this object (in case it is the OSC, which will persist)
|
||||||
|
Controller* limeController = [self->_controllers objectForKey:[NSNumber numberWithInteger:controller.playerIndex]];
|
||||||
|
limeController.gamepad = nil;
|
||||||
|
|
||||||
|
// Inform the server of the updated active gamepads before removing this controller
|
||||||
|
[self updateFinished:limeController];
|
||||||
|
[self->_controllers removeObjectForKey:[NSNumber numberWithInteger:controller.playerIndex]];
|
||||||
|
|
||||||
// Re-evaluate the on-screen control mode
|
// Re-evaluate the on-screen control mode
|
||||||
[self updateAutoOnScreenControlMode];
|
[self updateAutoOnScreenControlMode];
|
||||||
}];
|
}];
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
-(void) cleanup
|
-(void) cleanup
|
||||||
{
|
{
|
||||||
|
[_rumbleTimer invalidate];
|
||||||
|
_rumbleTimer = nil;
|
||||||
[[NSNotificationCenter defaultCenter] removeObserver:self.connectObserver];
|
[[NSNotificationCenter defaultCenter] removeObserver:self.connectObserver];
|
||||||
[[NSNotificationCenter defaultCenter] removeObserver:self.disconnectObserver];
|
[[NSNotificationCenter defaultCenter] removeObserver:self.disconnectObserver];
|
||||||
[_controllers removeAllObjects];
|
[_controllers removeAllObjects];
|
||||||
_controllerNumbers = 0;
|
_controllerNumbers = 0;
|
||||||
for (GCController* controller in [GCController controllers]) {
|
for (GCController* controller in [GCController controllers]) {
|
||||||
[self unregisterControllerCallbacks:controller];
|
if ([ControllerSupport isSupportedGamepad:controller]) {
|
||||||
|
[self unregisterControllerCallbacks:controller];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
//
|
||||||
|
// KeyboardSupport.h
|
||||||
|
// Moonlight
|
||||||
|
//
|
||||||
|
// Created by Diego Waxemberg on 8/25/18.
|
||||||
|
// Copyright © 2018 Moonlight Game Streaming Project. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
@interface KeyboardSupport : NSObject
|
||||||
|
|
||||||
|
struct KeyEvent {
|
||||||
|
u_short keycode;
|
||||||
|
u_short modifierKeycode;
|
||||||
|
u_char modifier;
|
||||||
|
};
|
||||||
|
|
||||||
|
+ (struct KeyEvent) translateKeyEvent:(unichar) inputChar withModifierFlags:(UIKeyModifierFlags)modifierFlags;
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -0,0 +1,191 @@
|
|||||||
|
//
|
||||||
|
// KeyboardSupport.m
|
||||||
|
// Moonlight
|
||||||
|
//
|
||||||
|
// Created by Diego Waxemberg on 8/25/18.
|
||||||
|
// Copyright © 2018 Moonlight Game Streaming Project. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "KeyboardSupport.h"
|
||||||
|
#include <Limelight.h>
|
||||||
|
|
||||||
|
@implementation KeyboardSupport
|
||||||
|
|
||||||
|
+ (struct KeyEvent)translateKeyEvent:(unichar)inputChar withModifierFlags:(UIKeyModifierFlags)modifierFlags {
|
||||||
|
struct KeyEvent event;
|
||||||
|
event.keycode = 0;
|
||||||
|
event.modifier = 0;
|
||||||
|
event.modifierKeycode = 0;
|
||||||
|
|
||||||
|
switch (modifierFlags) {
|
||||||
|
case UIKeyModifierAlphaShift:
|
||||||
|
case UIKeyModifierShift:
|
||||||
|
[KeyboardSupport addShiftModifier:&event];
|
||||||
|
break;
|
||||||
|
case UIKeyModifierControl:
|
||||||
|
[KeyboardSupport addControlModifier:&event];
|
||||||
|
break;
|
||||||
|
case UIKeyModifierAlternate:
|
||||||
|
[KeyboardSupport addAltModifier:&event];
|
||||||
|
break;
|
||||||
|
case UIKeyModifierCommand:
|
||||||
|
case UIKeyModifierNumericPad:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (inputChar >= 0x30 && inputChar <= 0x39) {
|
||||||
|
// Numbers 0-9
|
||||||
|
event.keycode = inputChar;
|
||||||
|
} else if (inputChar >= 0x41 && inputChar <= 0x5A) {
|
||||||
|
// Capital letters
|
||||||
|
event.keycode = inputChar;
|
||||||
|
[KeyboardSupport addShiftModifier:&event];
|
||||||
|
} else if (inputChar >= 0x61 && inputChar <= 0x7A) {
|
||||||
|
// Lower case letters
|
||||||
|
event.keycode = inputChar - (0x61 - 0x41);
|
||||||
|
} switch (inputChar) {
|
||||||
|
case ' ': // Spacebar
|
||||||
|
event.keycode = 0x20;
|
||||||
|
break;
|
||||||
|
case '-': // Hyphen '-'
|
||||||
|
event.keycode = 0xBD;
|
||||||
|
break;
|
||||||
|
case '/': // Forward slash '/'
|
||||||
|
event.keycode = 0xBF;
|
||||||
|
break;
|
||||||
|
case ':': // Colon ':'
|
||||||
|
event.keycode = 0xBA;
|
||||||
|
[KeyboardSupport addShiftModifier:&event];
|
||||||
|
break;
|
||||||
|
case ';': // Semi-colon ';'
|
||||||
|
event.keycode = 0xBA;
|
||||||
|
break;
|
||||||
|
case '(': // Open parenthesis '('
|
||||||
|
event.keycode = 0x39; // '9'
|
||||||
|
[KeyboardSupport addShiftModifier:&event];
|
||||||
|
break;
|
||||||
|
case ')': // Close parenthesis ')'
|
||||||
|
event.keycode = 0x30; // '0'
|
||||||
|
[KeyboardSupport addShiftModifier:&event];
|
||||||
|
break;
|
||||||
|
case '$': // Dollar sign '$'
|
||||||
|
event.keycode = 0x34; // '4'
|
||||||
|
[KeyboardSupport addShiftModifier:&event];
|
||||||
|
break;
|
||||||
|
case '&': // Ampresand '&'
|
||||||
|
event.keycode = 0x37; // '7'
|
||||||
|
[KeyboardSupport addShiftModifier:&event];
|
||||||
|
break;
|
||||||
|
case '@': // At-sign '@'
|
||||||
|
event.keycode = 0x32; // '2'
|
||||||
|
[KeyboardSupport addShiftModifier:&event];
|
||||||
|
break;
|
||||||
|
case '"':
|
||||||
|
event.keycode = 0xDE;
|
||||||
|
[KeyboardSupport addShiftModifier:&event];
|
||||||
|
break;
|
||||||
|
case '\'':
|
||||||
|
event.keycode = 0xDE;
|
||||||
|
break;
|
||||||
|
case '!':
|
||||||
|
event.keycode = 0x31; // '1'
|
||||||
|
[KeyboardSupport addShiftModifier:&event];
|
||||||
|
break;
|
||||||
|
case '?':
|
||||||
|
event.keycode = 0xBF; // '/'
|
||||||
|
[KeyboardSupport addShiftModifier:&event];
|
||||||
|
break;
|
||||||
|
case ',':
|
||||||
|
event.keycode = 0xBC;
|
||||||
|
break;
|
||||||
|
case '<':
|
||||||
|
event.keycode = 0xBC;
|
||||||
|
[KeyboardSupport addShiftModifier:&event];
|
||||||
|
break;
|
||||||
|
case '.':
|
||||||
|
event.keycode = 0xBE;
|
||||||
|
break;
|
||||||
|
case '>':
|
||||||
|
event.keycode = 0xBE;
|
||||||
|
[KeyboardSupport addShiftModifier:&event];
|
||||||
|
break;
|
||||||
|
case '[':
|
||||||
|
event.keycode = 0xDB;
|
||||||
|
break;
|
||||||
|
case ']':
|
||||||
|
event.keycode = 0xDD;
|
||||||
|
break;
|
||||||
|
case '{':
|
||||||
|
event.keycode = 0xDB;
|
||||||
|
[KeyboardSupport addShiftModifier:&event];
|
||||||
|
break;
|
||||||
|
case '}':
|
||||||
|
event.keycode = 0xDD;
|
||||||
|
[KeyboardSupport addShiftModifier:&event];
|
||||||
|
break;
|
||||||
|
case '#':
|
||||||
|
event.keycode = 0x33; // '3'
|
||||||
|
[KeyboardSupport addShiftModifier:&event];
|
||||||
|
break;
|
||||||
|
case '%':
|
||||||
|
event.keycode = 0x35; // '5'
|
||||||
|
[KeyboardSupport addShiftModifier:&event];
|
||||||
|
break;
|
||||||
|
case '^':
|
||||||
|
event.keycode = 0x36; // '6'
|
||||||
|
[KeyboardSupport addShiftModifier:&event];
|
||||||
|
break;
|
||||||
|
case '*':
|
||||||
|
event.keycode = 0x38; // '8'
|
||||||
|
[KeyboardSupport addShiftModifier:&event];
|
||||||
|
break;
|
||||||
|
case '+':
|
||||||
|
event.keycode = 0xBB;
|
||||||
|
[KeyboardSupport addShiftModifier:&event];
|
||||||
|
break;
|
||||||
|
case '=':
|
||||||
|
event.keycode = 0xBB;
|
||||||
|
break;
|
||||||
|
case '_':
|
||||||
|
event.keycode = 0xBD;
|
||||||
|
[KeyboardSupport addShiftModifier:&event];
|
||||||
|
break;
|
||||||
|
case '\\':
|
||||||
|
event.keycode = 0xDC;
|
||||||
|
break;
|
||||||
|
case '|':
|
||||||
|
event.keycode = 0xDC;
|
||||||
|
[KeyboardSupport addShiftModifier:&event];
|
||||||
|
break;
|
||||||
|
case '~':
|
||||||
|
event.keycode = 0xC0;
|
||||||
|
[KeyboardSupport addShiftModifier:&event];
|
||||||
|
break;
|
||||||
|
case '`':
|
||||||
|
event.keycode = 0xC0;
|
||||||
|
break;
|
||||||
|
case '\t':
|
||||||
|
event.keycode = 0x09;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (void) addShiftModifier:(struct KeyEvent*)event {
|
||||||
|
event->modifier = MODIFIER_SHIFT;
|
||||||
|
event->modifierKeycode = 0x10;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (void) addControlModifier:(struct KeyEvent*)event {
|
||||||
|
event->modifier = MODIFIER_CTRL;
|
||||||
|
event->modifierKeycode = 0x11;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (void) addAltModifier:(struct KeyEvent*)event {
|
||||||
|
event->modifier = MODIFIER_ALT;
|
||||||
|
event->modifierKeycode = 0x12;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
//
|
||||||
|
// Use this file to import your target's public headers that you would like to expose to Swift.
|
||||||
|
//
|
||||||
|
|
||||||
@@ -22,12 +22,15 @@ typedef NS_ENUM(NSInteger, OnScreenControlsLevel) {
|
|||||||
// Internal levels selected by ControllerSupport
|
// Internal levels selected by ControllerSupport
|
||||||
OnScreenControlsLevelAutoGCGamepad,
|
OnScreenControlsLevelAutoGCGamepad,
|
||||||
OnScreenControlsLevelAutoGCExtendedGamepad,
|
OnScreenControlsLevelAutoGCExtendedGamepad,
|
||||||
|
OnScreenControlsLevelAutoGCExtendedGamepadWithStickButtons
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#if TARGET_OS_IPHONE
|
||||||
- (id) initWithView:(UIView*)view controllerSup:(ControllerSupport*)controllerSupport swipeDelegate:(id<EdgeDetectionDelegate>)edgeDelegate;
|
- (id) initWithView:(UIView*)view controllerSup:(ControllerSupport*)controllerSupport swipeDelegate:(id<EdgeDetectionDelegate>)edgeDelegate;
|
||||||
- (BOOL) handleTouchDownEvent:(NSSet*)touches;
|
- (BOOL) handleTouchDownEvent:(NSSet*)touches;
|
||||||
- (BOOL) handleTouchUpEvent:(NSSet*)touches;
|
- (BOOL) handleTouchUpEvent:(NSSet*)touches;
|
||||||
- (BOOL) handleTouchMovedEvent:(NSSet*)touches;
|
- (BOOL) handleTouchMovedEvent:(NSSet*)touches;
|
||||||
- (void) setLevel:(OnScreenControlsLevel)level;
|
- (void) setLevel:(OnScreenControlsLevel)level;
|
||||||
|
#endif
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -41,10 +41,7 @@
|
|||||||
UITouch* _bTouch;
|
UITouch* _bTouch;
|
||||||
UITouch* _xTouch;
|
UITouch* _xTouch;
|
||||||
UITouch* _yTouch;
|
UITouch* _yTouch;
|
||||||
UITouch* _upTouch;
|
UITouch* _dpadTouch;
|
||||||
UITouch* _downTouch;
|
|
||||||
UITouch* _leftTouch;
|
|
||||||
UITouch* _rightTouch;
|
|
||||||
UITouch* _lsTouch;
|
UITouch* _lsTouch;
|
||||||
UITouch* _rsTouch;
|
UITouch* _rsTouch;
|
||||||
UITouch* _startTouch;
|
UITouch* _startTouch;
|
||||||
@@ -119,12 +116,11 @@ static float L3_Y;
|
|||||||
self = [self init];
|
self = [self init];
|
||||||
_view = view;
|
_view = view;
|
||||||
_controllerSupport = controllerSupport;
|
_controllerSupport = controllerSupport;
|
||||||
_controller = [[Controller alloc] init];
|
_controller = [controllerSupport getOscController];
|
||||||
_controller.playerIndex = 0;
|
|
||||||
_edgeDelegate = swipeDelegate;
|
_edgeDelegate = swipeDelegate;
|
||||||
_deadTouches = [[NSMutableArray alloc] init];
|
_deadTouches = [[NSMutableArray alloc] init];
|
||||||
|
|
||||||
_iPad = (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad);
|
_iPad = ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad);
|
||||||
_controlArea = CGRectMake(0, 0, _view.frame.size.width, _view.frame.size.height);
|
_controlArea = CGRectMake(0, 0, _view.frame.size.width, _view.frame.size.height);
|
||||||
if (_iPad)
|
if (_iPad)
|
||||||
{
|
{
|
||||||
@@ -203,15 +199,27 @@ static float L3_Y;
|
|||||||
[self hideSticks];
|
[self hideSticks];
|
||||||
[self drawL3R3];
|
[self drawL3R3];
|
||||||
break;
|
break;
|
||||||
|
case OnScreenControlsLevelAutoGCExtendedGamepadWithStickButtons:
|
||||||
|
// This variant of GCExtendedGamepad has L3 and R3 but
|
||||||
|
// is still missing Select
|
||||||
|
[self setupExtendedGamepadControls];
|
||||||
|
|
||||||
|
[self hideButtons];
|
||||||
|
[self hideBumpers];
|
||||||
|
[self hideTriggers];
|
||||||
|
[self hideL3R3];
|
||||||
|
[self drawStartSelect];
|
||||||
|
[self hideSticks];
|
||||||
|
break;
|
||||||
case OnScreenControlsLevelSimple:
|
case OnScreenControlsLevelSimple:
|
||||||
[self setupSimpleControls];
|
[self setupSimpleControls];
|
||||||
|
|
||||||
[self drawTriggers];
|
[self hideTriggers];
|
||||||
[self drawStartSelect];
|
[self hideL3R3];
|
||||||
[self drawL3R3];
|
|
||||||
[self hideButtons];
|
|
||||||
[self hideBumpers];
|
[self hideBumpers];
|
||||||
[self hideSticks];
|
[self hideSticks];
|
||||||
|
[self drawStartSelect];
|
||||||
|
[self drawButtons];
|
||||||
break;
|
break;
|
||||||
case OnScreenControlsLevelFull:
|
case OnScreenControlsLevelFull:
|
||||||
[self setupComplexControls];
|
[self setupComplexControls];
|
||||||
@@ -239,8 +247,22 @@ static float L3_Y;
|
|||||||
// Start with the default complex layout
|
// Start with the default complex layout
|
||||||
[self setupComplexControls];
|
[self setupComplexControls];
|
||||||
|
|
||||||
|
START_X = _controlArea.size.width * .95 + _controlArea.origin.x;
|
||||||
START_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
|
START_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
|
||||||
|
SELECT_X = _controlArea.size.width * .05 + _controlArea.origin.x;
|
||||||
SELECT_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
|
SELECT_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
|
||||||
|
|
||||||
|
L3_Y = _controlArea.size.height * .85 + _controlArea.origin.y;
|
||||||
|
R3_Y = _controlArea.size.height * .85 + _controlArea.origin.y;
|
||||||
|
|
||||||
|
if (_iPad) {
|
||||||
|
L3_X = _controlArea.size.width * .15 + _controlArea.origin.x;
|
||||||
|
R3_X = _controlArea.size.width * .85 + _controlArea.origin.x;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
L3_X = _controlArea.size.width * .25 + _controlArea.origin.x;
|
||||||
|
R3_X = _controlArea.size.width * .75 + _controlArea.origin.x;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// For GCGamepad controls we move triggers, start, and select
|
// For GCGamepad controls we move triggers, start, and select
|
||||||
@@ -249,10 +271,30 @@ static float L3_Y;
|
|||||||
// Start with the default complex layout
|
// Start with the default complex layout
|
||||||
[self setupComplexControls];
|
[self setupComplexControls];
|
||||||
|
|
||||||
START_X = _controlArea.size.width * .6 + _controlArea.origin.x;
|
L2_Y = _controlArea.size.height * .75 + _controlArea.origin.y;
|
||||||
START_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
|
L2_X = _controlArea.size.width * .05 + _controlArea.origin.x;
|
||||||
|
|
||||||
SELECT_X = _controlArea.size.width * .4 + _controlArea.origin.x;
|
R2_Y = _controlArea.size.height * .75 + _controlArea.origin.y;
|
||||||
|
R2_X = _controlArea.size.width * .95 + _controlArea.origin.x;
|
||||||
|
|
||||||
|
START_X = _controlArea.size.width * .95 + _controlArea.origin.x;
|
||||||
|
START_Y = _controlArea.size.height * .95 + _controlArea.origin.y;
|
||||||
|
SELECT_X = _controlArea.size.width * .05 + _controlArea.origin.x;
|
||||||
|
SELECT_Y = _controlArea.size.height * .95 + _controlArea.origin.y;
|
||||||
|
|
||||||
|
if (_iPad) {
|
||||||
|
// The analog sticks are kept closer to the sides on iPad
|
||||||
|
LS_CENTER_X = _controlArea.size.width * .15 + _controlArea.origin.x;
|
||||||
|
RS_CENTER_X = _controlArea.size.width * .85 + _controlArea.origin.x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For simple controls we move the triggers and buttons to the bottom
|
||||||
|
- (void) setupSimpleControls {
|
||||||
|
// Start with the default complex layout
|
||||||
|
[self setupComplexControls];
|
||||||
|
|
||||||
|
START_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
|
||||||
SELECT_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
|
SELECT_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
|
||||||
|
|
||||||
L2_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
|
L2_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
|
||||||
@@ -262,47 +304,18 @@ static float L3_Y;
|
|||||||
R2_X = _controlArea.size.width * .9 + _controlArea.origin.x;
|
R2_X = _controlArea.size.width * .9 + _controlArea.origin.x;
|
||||||
|
|
||||||
if (_iPad) {
|
if (_iPad) {
|
||||||
START_X = _controlArea.size.width * .75 + _controlArea.origin.x;
|
// Lower the D-pad and buttons on iPad
|
||||||
START_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
|
D_PAD_CENTER_Y = _controlArea.size.height * .75 + _controlArea.origin.y;
|
||||||
|
BUTTON_CENTER_Y = _controlArea.size.height * .75 + _controlArea.origin.y;
|
||||||
|
|
||||||
SELECT_X = _controlArea.size.width * .25 + _controlArea.origin.x;
|
// Move Start and Select closer to sides
|
||||||
SELECT_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
|
SELECT_X = _controlArea.size.width * .2 + _controlArea.origin.x;
|
||||||
|
START_X = _controlArea.size.width * .8 + _controlArea.origin.x;
|
||||||
// The analog sticks are kept closer to the sides on iPad
|
}
|
||||||
LS_CENTER_X = _controlArea.size.width * .18 + _controlArea.origin.x;
|
else {
|
||||||
LS_CENTER_Y = _controlArea.size.height * .75 + _controlArea.origin.y;
|
SELECT_X = _controlArea.size.width * .4 + _controlArea.origin.x;
|
||||||
RS_CENTER_X = _controlArea.size.width * .82 + _controlArea.origin.x;
|
|
||||||
RS_CENTER_Y = _controlArea.size.height * .75 + _controlArea.origin.y;
|
|
||||||
} else {
|
|
||||||
START_X = _controlArea.size.width * .6 + _controlArea.origin.x;
|
START_X = _controlArea.size.width * .6 + _controlArea.origin.x;
|
||||||
START_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
|
|
||||||
|
|
||||||
SELECT_X = _controlArea.size.width * .4 + _controlArea.origin.x;
|
|
||||||
SELECT_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
|
|
||||||
|
|
||||||
LS_CENTER_X = _controlArea.size.width * .25 + _controlArea.origin.x;
|
|
||||||
LS_CENTER_Y = _controlArea.size.height * .75 + _controlArea.origin.y;
|
|
||||||
RS_CENTER_X = _controlArea.size.width * .75 + _controlArea.origin.x;
|
|
||||||
RS_CENTER_Y = _controlArea.size.height * .75 + _controlArea.origin.y;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// For simple controls we move the triggers and buttons to the bottom
|
|
||||||
- (void) setupSimpleControls {
|
|
||||||
// Start with the default complex layout
|
|
||||||
[self setupComplexControls];
|
|
||||||
|
|
||||||
START_X = _controlArea.size.width * .6 + _controlArea.origin.x;
|
|
||||||
START_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
|
|
||||||
|
|
||||||
SELECT_X = _controlArea.size.width * .4 + _controlArea.origin.x;
|
|
||||||
SELECT_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
|
|
||||||
|
|
||||||
L2_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
|
|
||||||
L2_X = _controlArea.size.width * .1 + _controlArea.origin.x;
|
|
||||||
|
|
||||||
R2_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
|
|
||||||
R2_X = _controlArea.size.width * .9 + _controlArea.origin.x;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void) setupComplexControls
|
- (void) setupComplexControls
|
||||||
@@ -316,9 +329,9 @@ static float L3_Y;
|
|||||||
{
|
{
|
||||||
// The analog sticks are kept closer to the sides on iPad
|
// The analog sticks are kept closer to the sides on iPad
|
||||||
LS_CENTER_X = _controlArea.size.width * .22 + _controlArea.origin.x;
|
LS_CENTER_X = _controlArea.size.width * .22 + _controlArea.origin.x;
|
||||||
LS_CENTER_Y = _controlArea.size.height * .75 + _controlArea.origin.y;
|
LS_CENTER_Y = _controlArea.size.height * .80 + _controlArea.origin.y;
|
||||||
RS_CENTER_X = _controlArea.size.width * .77 + _controlArea.origin.x;
|
RS_CENTER_X = _controlArea.size.width * .77 + _controlArea.origin.x;
|
||||||
RS_CENTER_Y = _controlArea.size.height * .75 + _controlArea.origin.y;
|
RS_CENTER_Y = _controlArea.size.height * .80 + _controlArea.origin.y;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -333,18 +346,24 @@ static float L3_Y;
|
|||||||
SELECT_X = _controlArea.size.width * .1 + _controlArea.origin.x;
|
SELECT_X = _controlArea.size.width * .1 + _controlArea.origin.x;
|
||||||
SELECT_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
|
SELECT_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
|
||||||
|
|
||||||
L1_X = _controlArea.size.width * .1 + _controlArea.origin.x;
|
|
||||||
L1_Y = _controlArea.size.height * .27 + _controlArea.origin.y;
|
L1_Y = _controlArea.size.height * .27 + _controlArea.origin.y;
|
||||||
L2_X = _controlArea.size.width * .1 + _controlArea.origin.x;
|
|
||||||
L2_Y = _controlArea.size.height * .1 + _controlArea.origin.y;
|
L2_Y = _controlArea.size.height * .1 + _controlArea.origin.y;
|
||||||
R1_X = _controlArea.size.width * .9 + _controlArea.origin.x;
|
|
||||||
R1_Y = _controlArea.size.height * .27 + _controlArea.origin.y;
|
R1_Y = _controlArea.size.height * .27 + _controlArea.origin.y;
|
||||||
R2_X = _controlArea.size.width * .9 + _controlArea.origin.x;
|
|
||||||
R2_Y = _controlArea.size.height * .1 + _controlArea.origin.y;
|
R2_Y = _controlArea.size.height * .1 + _controlArea.origin.y;
|
||||||
L3_X = _controlArea.size.width * .25 + _controlArea.origin.x;
|
|
||||||
L3_Y = _controlArea.size.height * .75 + _controlArea.origin.y;
|
if (_iPad) {
|
||||||
R3_X = _controlArea.size.width * .75 + _controlArea.origin.x;
|
// Move L/R buttons closer to the side on iPad
|
||||||
R3_Y = _controlArea.size.height * .75 + _controlArea.origin.y;
|
L1_X = _controlArea.size.width * .05 + _controlArea.origin.x;
|
||||||
|
L2_X = _controlArea.size.width * .05 + _controlArea.origin.x;
|
||||||
|
R1_X = _controlArea.size.width * .95 + _controlArea.origin.x;
|
||||||
|
R2_X = _controlArea.size.width * .95 + _controlArea.origin.x;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
L1_X = _controlArea.size.width * .1 + _controlArea.origin.x;
|
||||||
|
L2_X = _controlArea.size.width * .1 + _controlArea.origin.x;
|
||||||
|
R1_X = _controlArea.size.width * .9 + _controlArea.origin.x;
|
||||||
|
R2_X = _controlArea.size.width * .9 + _controlArea.origin.x;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void) drawButtons {
|
- (void) drawButtons {
|
||||||
@@ -570,6 +589,26 @@ static float L3_Y;
|
|||||||
[_controllerSupport updateRightStick:_controller x:0x7FFE * xStickVal y:0x7FFE * -yStickVal];
|
[_controllerSupport updateRightStick:_controller x:0x7FFE * xStickVal y:0x7FFE * -yStickVal];
|
||||||
|
|
||||||
updated = true;
|
updated = true;
|
||||||
|
} else if (touch == _dpadTouch) {
|
||||||
|
[_controllerSupport clearButtonFlag:_controller
|
||||||
|
flags:UP_FLAG | DOWN_FLAG | LEFT_FLAG | RIGHT_FLAG];
|
||||||
|
|
||||||
|
// Allow the user to slide their finger to another d-pad button
|
||||||
|
if ([_upButton.presentationLayer hitTest:touchLocation]) {
|
||||||
|
[_controllerSupport setButtonFlag:_controller flags:UP_FLAG];
|
||||||
|
updated = true;
|
||||||
|
} else if ([_downButton.presentationLayer hitTest:touchLocation]) {
|
||||||
|
[_controllerSupport setButtonFlag:_controller flags:DOWN_FLAG];
|
||||||
|
updated = true;
|
||||||
|
} else if ([_leftButton.presentationLayer hitTest:touchLocation]) {
|
||||||
|
[_controllerSupport setButtonFlag:_controller flags:LEFT_FLAG];
|
||||||
|
updated = true;
|
||||||
|
} else if ([_rightButton.presentationLayer hitTest:touchLocation]) {
|
||||||
|
[_controllerSupport setButtonFlag:_controller flags:RIGHT_FLAG];
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
buttonTouch = true;
|
||||||
} else if (touch == _aTouch) {
|
} else if (touch == _aTouch) {
|
||||||
buttonTouch = true;
|
buttonTouch = true;
|
||||||
} else if (touch == _bTouch) {
|
} else if (touch == _bTouch) {
|
||||||
@@ -578,14 +617,6 @@ static float L3_Y;
|
|||||||
buttonTouch = true;
|
buttonTouch = true;
|
||||||
} else if (touch == _yTouch) {
|
} else if (touch == _yTouch) {
|
||||||
buttonTouch = true;
|
buttonTouch = true;
|
||||||
} else if (touch == _upTouch) {
|
|
||||||
buttonTouch = true;
|
|
||||||
} else if (touch == _downTouch) {
|
|
||||||
buttonTouch = true;
|
|
||||||
} else if (touch == _leftTouch) {
|
|
||||||
buttonTouch = true;
|
|
||||||
} else if (touch == _rightTouch) {
|
|
||||||
buttonTouch = true;
|
|
||||||
} else if (touch == _startTouch) {
|
} else if (touch == _startTouch) {
|
||||||
buttonTouch = true;
|
buttonTouch = true;
|
||||||
} else if (touch == _selectTouch) {
|
} else if (touch == _selectTouch) {
|
||||||
@@ -637,19 +668,19 @@ static float L3_Y;
|
|||||||
updated = true;
|
updated = true;
|
||||||
} else if ([_upButton.presentationLayer hitTest:touchLocation]) {
|
} else if ([_upButton.presentationLayer hitTest:touchLocation]) {
|
||||||
[_controllerSupport setButtonFlag:_controller flags:UP_FLAG];
|
[_controllerSupport setButtonFlag:_controller flags:UP_FLAG];
|
||||||
_upTouch = touch;
|
_dpadTouch = touch;
|
||||||
updated = true;
|
updated = true;
|
||||||
} else if ([_downButton.presentationLayer hitTest:touchLocation]) {
|
} else if ([_downButton.presentationLayer hitTest:touchLocation]) {
|
||||||
[_controllerSupport setButtonFlag:_controller flags:DOWN_FLAG];
|
[_controllerSupport setButtonFlag:_controller flags:DOWN_FLAG];
|
||||||
_downTouch = touch;
|
_dpadTouch = touch;
|
||||||
updated = true;
|
updated = true;
|
||||||
} else if ([_leftButton.presentationLayer hitTest:touchLocation]) {
|
} else if ([_leftButton.presentationLayer hitTest:touchLocation]) {
|
||||||
[_controllerSupport setButtonFlag:_controller flags:LEFT_FLAG];
|
[_controllerSupport setButtonFlag:_controller flags:LEFT_FLAG];
|
||||||
_leftTouch = touch;
|
_dpadTouch = touch;
|
||||||
updated = true;
|
updated = true;
|
||||||
} else if ([_rightButton.presentationLayer hitTest:touchLocation]) {
|
} else if ([_rightButton.presentationLayer hitTest:touchLocation]) {
|
||||||
[_controllerSupport setButtonFlag:_controller flags:RIGHT_FLAG];
|
[_controllerSupport setButtonFlag:_controller flags:RIGHT_FLAG];
|
||||||
_rightTouch = touch;
|
_dpadTouch = touch;
|
||||||
updated = true;
|
updated = true;
|
||||||
} else if ([_startButton.presentationLayer hitTest:touchLocation]) {
|
} else if ([_startButton.presentationLayer hitTest:touchLocation]) {
|
||||||
[_controllerSupport setButtonFlag:_controller flags:PLAY_FLAG];
|
[_controllerSupport setButtonFlag:_controller flags:PLAY_FLAG];
|
||||||
@@ -755,21 +786,10 @@ static float L3_Y;
|
|||||||
[_controllerSupport clearButtonFlag:_controller flags:Y_FLAG];
|
[_controllerSupport clearButtonFlag:_controller flags:Y_FLAG];
|
||||||
_yTouch = nil;
|
_yTouch = nil;
|
||||||
updated = true;
|
updated = true;
|
||||||
} else if (touch == _upTouch) {
|
} else if (touch == _dpadTouch) {
|
||||||
[_controllerSupport clearButtonFlag:_controller flags:UP_FLAG];
|
[_controllerSupport clearButtonFlag:_controller
|
||||||
_upTouch = nil;
|
flags:UP_FLAG | DOWN_FLAG | LEFT_FLAG | RIGHT_FLAG];
|
||||||
updated = true;
|
_dpadTouch = nil;
|
||||||
} else if (touch == _downTouch) {
|
|
||||||
[_controllerSupport clearButtonFlag:_controller flags:DOWN_FLAG];
|
|
||||||
_downTouch = nil;
|
|
||||||
updated = true;
|
|
||||||
} else if (touch == _leftTouch) {
|
|
||||||
[_controllerSupport clearButtonFlag:_controller flags:LEFT_FLAG];
|
|
||||||
_leftTouch = nil;
|
|
||||||
updated = true;
|
|
||||||
} else if (touch == _rightTouch) {
|
|
||||||
[_controllerSupport clearButtonFlag:_controller flags:RIGHT_FLAG];
|
|
||||||
_rightTouch = nil;
|
|
||||||
updated = true;
|
updated = true;
|
||||||
} else if (touch == _startTouch) {
|
} else if (touch == _startTouch) {
|
||||||
[_controllerSupport clearButtonFlag:_controller flags:PLAY_FLAG];
|
[_controllerSupport clearButtonFlag:_controller flags:PLAY_FLAG];
|
||||||
@@ -836,26 +856,31 @@ static float L3_Y;
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL) isInDeadZone:(UITouch*) touch {
|
- (BOOL) isInDeadZone:(UITouch*) touch {
|
||||||
switch (_level) {
|
// Dynamically evaluate deadzones based on the controls
|
||||||
case OnScreenControlsLevelFull:
|
// on screen at the time
|
||||||
return [self isDpadDeadZone:touch]
|
if (_leftButton.superlayer != nil && [self isDpadDeadZone:touch]) {
|
||||||
|| [self isAbxyDeadZone:touch]
|
return true;
|
||||||
|| [self isTriggerDeadZone:touch]
|
|
||||||
|| [self isBumperDeadZone:touch]
|
|
||||||
|| [self isStartSelectDeadZone:touch];
|
|
||||||
case OnScreenControlsLevelSimple:
|
|
||||||
return [self isTriggerDeadZone:touch]
|
|
||||||
|| [self isStartSelectDeadZone:touch]
|
|
||||||
|| [self isL3R3DeadZone:touch];
|
|
||||||
case OnScreenControlsLevelAutoGCExtendedGamepad:
|
|
||||||
return [self isL3R3DeadZone:touch]
|
|
||||||
|| [self isStartSelectDeadZone:touch];
|
|
||||||
case OnScreenControlsLevelAutoGCGamepad:
|
|
||||||
return [self isTriggerDeadZone:touch]
|
|
||||||
|| [self isStartSelectDeadZone:touch];
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
else if (_aButton.superlayer != nil && [self isAbxyDeadZone:touch]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (_l2Button.superlayer != nil && [self isTriggerDeadZone:touch]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (_l1Button.superlayer != nil && [self isBumperDeadZone:touch]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (_startButton.superlayer != nil && [self isStartSelectDeadZone:touch]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (_l3Button.superlayer != nil && [self isL3R3DeadZone:touch]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (_leftStickBackground.superlayer != nil && [self isStickDeadZone:touch]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL) isDpadDeadZone:(UITouch*) touch {
|
- (BOOL) isDpadDeadZone:(UITouch*) touch {
|
||||||
@@ -926,6 +951,19 @@ static float L3_Y;
|
|||||||
endY:_view.frame.origin.y + _view.frame.size.height];
|
endY:_view.frame.origin.y + _view.frame.size.height];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (BOOL) isStickDeadZone:(UITouch*) touch {
|
||||||
|
return [self isDeadZone:touch
|
||||||
|
startX:_leftStickBackground.frame.origin.x - 15
|
||||||
|
startY:_leftStickBackground.frame.origin.y - 15
|
||||||
|
endX:_leftStickBackground.frame.origin.x + _leftStickBackground.frame.size.width + 15
|
||||||
|
endY:_view.frame.origin.y + _view.frame.size.height]
|
||||||
|
|| [self isDeadZone:touch
|
||||||
|
startX:_rightStickBackground.frame.origin.x - 15
|
||||||
|
startY:_rightStickBackground.frame.origin.y - 15
|
||||||
|
endX:_rightStickBackground.frame.origin.x + _rightStickBackground.frame.size.width + 15
|
||||||
|
endY:_view.frame.origin.y + _view.frame.size.height];
|
||||||
|
}
|
||||||
|
|
||||||
- (BOOL) isDeadZone:(UITouch*) touch startX:(float)deadZoneStartX startY:(float)deadZoneStartY endX:(float)deadZoneEndX endY:(float)deadZoneEndY {
|
- (BOOL) isDeadZone:(UITouch*) touch startX:(float)deadZoneStartX startY:(float)deadZoneStartY endX:(float)deadZoneEndX endY:(float)deadZoneEndY {
|
||||||
deadZoneStartX -= DEAD_ZONE_PADDING;
|
deadZoneStartX -= DEAD_ZONE_PADDING;
|
||||||
deadZoneStartY -= DEAD_ZONE_PADDING;
|
deadZoneStartY -= DEAD_ZONE_PADDING;
|
||||||
|
|||||||