Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02dc978049 | ||
|
|
064723092b | ||
|
|
ca0efbdb5b | ||
|
|
5cc4076d94 | ||
|
|
938f4b4589 | ||
|
|
7c79fcc6a2 | ||
|
|
159ffdbfde | ||
|
|
a52f344d00 | ||
|
|
85af0f7562 | ||
|
|
da47035525 | ||
|
|
db9a4ab7c2 | ||
|
|
a528ce7e2a | ||
|
|
1e0714ce70 | ||
|
|
997525c71e | ||
|
|
b88cf14b4e | ||
|
|
1cfabac4b5 | ||
|
|
42518301f7 | ||
|
|
022352c166 | ||
|
|
557765f2c0 | ||
|
|
c3ad7578fe | ||
|
|
7df349ad7c | ||
|
|
855f48b455 | ||
|
|
dd5d233993 | ||
|
|
796f87713f | ||
|
|
38259af3f1 | ||
|
|
08279d825c | ||
|
|
7465835808 | ||
|
|
303303bf6b | ||
|
|
9b28981b59 | ||
|
|
5501d85762 | ||
|
|
cc799768ef | ||
|
|
ae1c290b79 | ||
|
|
62c6661267 | ||
|
|
485c5e10db | ||
|
|
e69cd3dbbc | ||
|
|
e05a6e4ec1 | ||
|
|
77485f2d8d | ||
|
|
951289a802 | ||
|
|
6f40922704 | ||
|
|
27ac6c0128 | ||
|
|
1f543325f8 | ||
|
|
73d4199917 | ||
|
|
e0808d6bfc | ||
|
|
86919bd263 | ||
|
|
401d9fbae7 | ||
|
|
bbc89011d4 | ||
|
|
a2f470ddeb | ||
|
|
b20079a165 | ||
|
|
01b349d8d0 | ||
|
|
089b424f65 | ||
|
|
c51caba1ec | ||
|
|
cb26398414 | ||
|
|
8cd931cf50 | ||
|
|
06fc3ec5dc | ||
|
|
3ebcc86f0d | ||
|
|
b81ea6b874 | ||
|
|
75a7b9e477 | ||
|
|
ce25a66dc5 | ||
|
|
9366c763d6 | ||
|
|
6974cda328 | ||
|
|
0a59ce0ca9 | ||
|
|
628252ed28 | ||
|
|
2c76654841 | ||
|
|
f20d90791a | ||
|
|
2df60e571c | ||
|
|
0879bf0583 | ||
|
|
7c77a385cf | ||
|
|
b9dbfdd82f | ||
|
|
5d9fa4d003 | ||
|
|
40938d198f | ||
|
|
a46e7fa100 | ||
|
|
043bf735e8 | ||
|
|
a342c294a1 | ||
|
|
f8a76e4584 | ||
|
|
d6ee43dab5 | ||
|
|
884deb9244 | ||
|
|
514e415956 | ||
|
|
95dcbf6024 | ||
|
|
9d0dc49fd2 | ||
|
|
7249854641 | ||
|
|
11da9e0eea | ||
|
|
4f9eb6ea04 | ||
|
|
f883f5a2b5 | ||
|
|
3fb328e238 | ||
|
|
ff40bc105e | ||
|
|
957ce6095e | ||
|
|
d1c35144e1 | ||
|
|
65bf4ca6b1 | ||
|
|
ef5c3d36d3 | ||
|
|
d48690b320 | ||
|
|
b279caa00b | ||
|
|
cc16186eed | ||
|
|
20d0087bdc | ||
|
|
4f3f27287c | ||
|
|
9f7ebdc771 | ||
|
|
ba723816a1 | ||
|
|
c57e89a0bd | ||
|
|
7ddf4e12ed | ||
|
|
4f265cd09f | ||
|
|
69081367e4 | ||
|
|
83fd8225e4 | ||
|
|
2c422c77fb | ||
|
|
5c47cb8908 | ||
|
|
924e79d00f | ||
|
|
bdb7d08c57 | ||
|
|
64803d9715 | ||
|
|
2462126963 | ||
|
|
0e40e4795a | ||
|
|
5c844d3d1f | ||
|
|
6d7a8df54f | ||
|
|
9eae79f69c | ||
|
|
a04e1ebb3b | ||
|
|
dbb3087078 | ||
|
|
eefe8522c4 | ||
|
|
cf665ec774 | ||
|
|
da4f41291e | ||
|
|
9a03dd184a | ||
|
|
4f03dd8c08 | ||
|
|
a2b15ed2ac | ||
|
|
1c90ee607a | ||
|
|
4f7b81335d | ||
|
|
eab25181e3 | ||
|
|
9b234bc867 | ||
|
|
c7badef9d7 | ||
|
|
365f61b393 | ||
|
|
02e088ddb2 | ||
|
|
be0c071fb5 | ||
|
|
71b953cdd2 | ||
|
|
3494962bc0 | ||
|
|
7c65bfcc77 | ||
|
|
bff3fd5f6c | ||
|
|
c8907b8ab8 | ||
|
|
66f8b6f40c | ||
|
|
6eaf9274b8 | ||
|
|
dbc83234e9 | ||
|
|
054b3ae45e | ||
|
|
1f5ecdf1ea | ||
|
|
490abc320c | ||
|
|
f99381df81 | ||
|
|
842b6b3c76 | ||
|
|
f9a2eb022f | ||
|
|
d896101ed2 | ||
|
|
2ae79c5827 | ||
|
|
c21f638399 | ||
|
|
6d94c8515e | ||
|
|
65e40279ea | ||
|
|
0bd81b8261 | ||
|
|
ac801e9bfb | ||
|
|
6678488edc | ||
|
|
a80fa5cfbb | ||
|
|
b9e0b28adc | ||
|
|
eb801b553f | ||
|
|
9cc7972d11 | ||
|
|
1b2a2ebddf | ||
|
|
3b0d80bce2 | ||
|
|
9ea5e2ec99 | ||
|
|
e79faba87d | ||
|
|
11b180d1e3 | ||
|
|
54d1851c78 | ||
|
|
0d57bd1015 | ||
|
|
3a77bf3e65 | ||
|
|
d7673075b2 | ||
|
|
6fb6b9bf38 | ||
|
|
6435afd229 | ||
|
|
bfdfdc3050 | ||
|
|
d12c103117 | ||
|
|
e3bce9cec3 | ||
|
|
fc73663253 | ||
|
|
b7ba94f77a | ||
|
|
29ba055024 | ||
|
|
7f7770a42c | ||
|
|
77c6ca2993 | ||
|
|
edf449b708 | ||
|
|
72292dde94 | ||
|
|
ff4f9e2bcb | ||
|
|
701c83d79c | ||
|
|
87be7080cb | ||
|
|
736485616c | ||
|
|
f21c58306e | ||
|
|
7d6cb247b8 | ||
|
|
bd582aa6c0 | ||
|
|
445c026ea9 | ||
|
|
bd313d97cb | ||
|
|
5055a6db1d |
@@ -0,0 +1,45 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build (${{ matrix.platform }})
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- scheme: Moonlight
|
||||
sdk: iphonesimulator
|
||||
platform: iOS
|
||||
- scheme: Moonlight TV
|
||||
sdk: appletvsimulator
|
||||
platform: tvOS
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Download ${{ matrix.platform }} platform
|
||||
run: xcodebuild -downloadPlatform ${{ matrix.platform }}
|
||||
|
||||
- name: Build Debug
|
||||
run: xcodebuild -project Moonlight.xcodeproj -scheme "${{ matrix.scheme }}" -configuration Debug -sdk ${{ matrix.sdk }} clean build
|
||||
|
||||
- name: Build Release
|
||||
run: xcodebuild -project Moonlight.xcodeproj -scheme "${{ matrix.scheme }}" -configuration Release -sdk ${{ matrix.sdk }} clean build
|
||||
@@ -1,4 +1,6 @@
|
||||
**/xcuserdata/
|
||||
**/Moonlight.xcscmblueprint
|
||||
**/xcschemes/*.xcscheme
|
||||
.DS_Store
|
||||
Build
|
||||
DerivedData
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
source config.sh
|
||||
|
||||
# Number of CPUs (for make -j)
|
||||
NCPU=`sysctl -n hw.ncpu`
|
||||
if test x$NJOB = x; then
|
||||
NJOB=$NCPU
|
||||
fi
|
||||
|
||||
PLATFORMBASE=$(xcode-select -print-path)"/Platforms"
|
||||
|
||||
SCRIPT_DIR=$( (cd -P $(dirname $0) && pwd) )
|
||||
DIST_DIR_BASE=${DIST_DIR_BASE:="$SCRIPT_DIR/dist"}
|
||||
|
||||
if [ ! -d opus ]
|
||||
then
|
||||
echo "opus source directory does not exist, run sync.sh"
|
||||
fi
|
||||
|
||||
# PATH=${SCRIPT_DIR}/gas-preprocessor/:$PATH
|
||||
|
||||
for ARCH in $ARCHS
|
||||
do
|
||||
OPUS_DIR=opus-$ARCH
|
||||
if [ ! -d $OPUS_DIR ]
|
||||
then
|
||||
echo "Directory $OPUS_DIR does not exist, run sync.sh"
|
||||
exit 1
|
||||
fi
|
||||
echo "Compiling source for $ARCH in directory $OPUS_DIR"
|
||||
echo "cd $OPUS_DIR"
|
||||
cd $OPUS_DIR
|
||||
|
||||
DIST_DIR=$DIST_DIR_BASE-$ARCH
|
||||
echo "mkdir -p $DIST_DIR"
|
||||
mkdir -p $DIST_DIR
|
||||
CFLAGS_ARCH=$ARCH
|
||||
case $ARCH in
|
||||
armv7)
|
||||
EXTRA_FLAGS="--with-pic --enable-fixed-point"
|
||||
EXTRA_CFLAGS="-mcpu=cortex-a8 -mfpu=neon -miphoneos-version-min=7.1"
|
||||
PLATFORM="${PLATFORMBASE}/iPhoneOS.platform"
|
||||
IOSSDK=iPhoneOS
|
||||
;;
|
||||
armv7s)
|
||||
EXTRA_FLAGS="--with-pic --enable-fixed-point"
|
||||
EXTRA_CFLAGS="-mcpu=cortex-a9 -mfpu=neon -miphoneos-version-min=7.1"
|
||||
PLATFORM="${PLATFORMBASE}/iPhoneOS.platform"
|
||||
IOSSDK=iPhoneOS
|
||||
;;
|
||||
aarch64)
|
||||
CFLAGS_ARCH="arm64"
|
||||
EXTRA_FLAGS="--with-pic --enable-fixed-point"
|
||||
EXTRA_CFLAGS="-miphoneos-version-min=7.1"
|
||||
PLATFORM="${PLATFORMBASE}/iPhoneOS.platform"
|
||||
IOSSDK=iPhoneOS
|
||||
;;
|
||||
x86_64)
|
||||
EXTRA_FLAGS="--with-pic"
|
||||
EXTRA_CFLAGS="-miphoneos-version-min=7.1"
|
||||
PLATFORM="${PLATFORMBASE}/iPhoneSimulator.platform"
|
||||
IOSSDK=iPhoneSimulator
|
||||
;;
|
||||
i386)
|
||||
EXTRA_FLAGS="--with-pic"
|
||||
EXTRA_CFLAGS="-miphoneos-version-min=7.1"
|
||||
PLATFORM="${PLATFORMBASE}/iPhoneSimulator.platform"
|
||||
IOSSDK=iPhoneSimulator
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported architecture ${ARCH}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "Configuring opus for $ARCH..."
|
||||
echo "./autogen.sh"
|
||||
./autogen.sh
|
||||
|
||||
CFLAGS="-g -O2 -pipe -arch ${CFLAGS_ARCH} \
|
||||
-isysroot ${PLATFORM}/Developer/SDKs/${IOSSDK}.sdk \
|
||||
-I${PLATFORM}/Developer/SDKs/${IOSSDK}.sdk/usr/include \
|
||||
${EXTRA_CFLAGS}"
|
||||
LDFLAGS="-arch ${CFLAGS_ARCH} \
|
||||
-isysroot ${PLATFORM}/Developer/SDKs/${IOSSDK}.sdk \
|
||||
-L${PLATFORM}/Developer/SDKs/${IOSSDK}.sdk/usr/lib"
|
||||
|
||||
echo "CFLAGS=$CFLAGS"
|
||||
echo "LDFLAGS=$LDFLAGS"
|
||||
|
||||
export CFLAGS
|
||||
export LDFLAGS
|
||||
|
||||
export CXXCPP="/usr/bin/cpp"
|
||||
export CPP="$CXXCPP"
|
||||
export CXX="/usr/bin/gcc"
|
||||
export CC="/usr/bin/gcc"
|
||||
export LD="/usr/bin/ld"
|
||||
export AR="/usr/bin/ar"
|
||||
export AS="/usr/bin/ls"
|
||||
export NM="/usr/bin/nm"
|
||||
export RANLIB="/usr/bin/ranlib"
|
||||
export STRIP="/usr/bin/strip"
|
||||
echo "./configure \
|
||||
--prefix=$DIST_DIR \
|
||||
--host=${ARCH}-apple-darwin \
|
||||
--with-sysroot=${PLATFORM}/Developer/SDKs/${IOSSDK}.sdk \
|
||||
--enable-static=yes \
|
||||
--enable-shared=no \
|
||||
--disable-doc \
|
||||
${EXTRA_FLAGS}"
|
||||
|
||||
./configure \
|
||||
--prefix=$DIST_DIR \
|
||||
--host=${ARCH}-apple-darwin \
|
||||
--with-sysroot=${PLATFORM}/Developer/SDKs/${IOSSDK}.sdk \
|
||||
--enable-static=yes \
|
||||
--enable-shared=no \
|
||||
--disable-doc \
|
||||
${EXTRA_FLAGS}
|
||||
|
||||
echo "Installing opus for $ARCH..."
|
||||
echo "make clean"
|
||||
make clean
|
||||
echo "make -j$NJOB V=1"
|
||||
make -j$NJOB V=1
|
||||
echo "make install"
|
||||
make install
|
||||
|
||||
echo "cd $SCRIPT_DIR"
|
||||
cd $SCRIPT_DIR
|
||||
|
||||
if [ -d $DIST_DIR/bin ]
|
||||
then
|
||||
rm -rf $DIST_DIR/bin
|
||||
fi
|
||||
if [ -d $DIST_DIR/share ]
|
||||
then
|
||||
rm -rf $DIST_DIR/share
|
||||
fi
|
||||
done
|
||||
@@ -1,51 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
source config.sh
|
||||
|
||||
for ARCH in $ARCHS
|
||||
do
|
||||
|
||||
if [ -d dist-$ARCH ]
|
||||
then
|
||||
MAIN_ARCH=$ARCH
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$MAIN_ARCH" ]
|
||||
then
|
||||
echo "Please compile an architecture"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
OUTPUT_DIR="dist-uarch"
|
||||
rm -rf $OUTPUT_DIR
|
||||
|
||||
mkdir -p $OUTPUT_DIR/lib $OUTPUT_DIR/include
|
||||
|
||||
for LIB in dist-$MAIN_ARCH/lib/*.a
|
||||
do
|
||||
LIB=`basename $LIB`
|
||||
LIPO_CREATE=""
|
||||
for ARCH in $ARCHS
|
||||
do
|
||||
LIPO_ARCH=$ARCH
|
||||
if [ "$ARCH" = "aarch64" ];
|
||||
then
|
||||
LIPO_ARCH="arm64"
|
||||
fi
|
||||
if [ -d dist-$ARCH ]
|
||||
then
|
||||
LIPO_CREATE="$LIPO_CREATE-arch $LIPO_ARCH dist-$ARCH/lib/$LIB "
|
||||
fi
|
||||
done
|
||||
OUTPUT="$OUTPUT_DIR/lib/$LIB"
|
||||
echo "Creating: $OUTPUT"
|
||||
xcrun -sdk iphoneos lipo -create $LIPO_CREATE -output $OUTPUT
|
||||
xcrun -sdk iphoneos lipo -info $OUTPUT
|
||||
done
|
||||
|
||||
echo "Copying headers from dist-$MAIN_ARCH..."
|
||||
cp -R dist-$MAIN_ARCH/include/* $OUTPUT_DIR/include
|
||||
@@ -1,36 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
OPUS_DIR="opus"
|
||||
IOSSDK_VER=8.1
|
||||
ARCHS="armv7 armv7s aarch64 i386 x86_64"
|
||||
|
||||
remove_arch() {
|
||||
OLD_ARCHS="$ARCHS"
|
||||
NEW_ARCHS=""
|
||||
REMOVAL="$1"
|
||||
|
||||
for ARCH in $OLD_ARCHS; do
|
||||
if [ "$ARCH" != "$REMOVAL" ] ; then
|
||||
NEW_ARCHS="$NEW_ARCHS $ARCH"
|
||||
fi
|
||||
done
|
||||
|
||||
ARCHS=$NEW_ARCHS
|
||||
}
|
||||
|
||||
# armv7s is only supported in iOS 6.0+
|
||||
CHECK=`echo $IOSSDK_VER '>= 6.0' | bc -l`
|
||||
if [ "$CHECK" = "0" ] ; then
|
||||
remove_arch "armv7s"
|
||||
fi
|
||||
|
||||
# armv6 is not supported any more since iOS 6.0
|
||||
CHECK=`echo $IOSSDK_VER '< 6.0' | bc -l`
|
||||
if [ "$CHECK" = "0" ] ; then
|
||||
remove_arch "armv6"
|
||||
fi
|
||||
|
||||
echo 'Architectures to build:' $ARCHS
|
||||
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
source config.sh
|
||||
|
||||
SCRIPT_DIR=$( (cd -P $(dirname $0) && pwd) )
|
||||
DIST_DIR_BASE=${DIST_DIR_BASE:="$SCRIPT_DIR/dist"}
|
||||
|
||||
cd opus
|
||||
git checkout master
|
||||
git pull origin master
|
||||
cd ..
|
||||
|
||||
for ARCH in $ARCHS
|
||||
do
|
||||
OPUS_DIR=opus-$ARCH
|
||||
echo "Syncing source for $ARCH to directory $OPUS_DIR"
|
||||
rsync opus/ $OPUS_DIR/ --exclude '.*' -a --delete
|
||||
done
|
||||
@@ -1,104 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
SYSROOT_iPHONE="/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk"
|
||||
SYSROOT_SIMULATOR="/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk"
|
||||
CC_IPHONE="xcrun -sdk iphoneos clang"
|
||||
CC_SIMULATOR="xcrun -sdk iphonesimulator clang"
|
||||
function build_one {
|
||||
./configure \
|
||||
--prefix=$PREFIX \
|
||||
--disable-ffmpeg \
|
||||
--disable-ffplay \
|
||||
--disable-ffprobe \
|
||||
--disable-ffserver \
|
||||
--disable-armv5te \
|
||||
--disable-armv6 \
|
||||
--disable-doc \
|
||||
--disable-everything \
|
||||
--disable-debug \
|
||||
--enable-decoder=h264 \
|
||||
--enable-avresample \
|
||||
--enable-cross-compile \
|
||||
--sysroot=$SYSROOT \
|
||||
--target-os=darwin \
|
||||
--cc="$CC" \
|
||||
--extra-cflags="$CFLAGS" \
|
||||
--extra-ldflags="$LDFLAGS" \
|
||||
--enable-pic \
|
||||
$ADDI_FLAGS
|
||||
make clean && make -j4 && make install
|
||||
}
|
||||
|
||||
|
||||
# armv7
|
||||
function build_armv7 {
|
||||
PREFIX="armv7"
|
||||
SYSROOT=$SYSROOT_iPHONE
|
||||
CC=$CC_IPHONE
|
||||
CFLAGS="-arch armv7 -mfpu=neon -miphoneos-version-min=7.1 -fpic"
|
||||
LDFLAGS="-arch armv7 -isysroot $SYSROOT_iPHONE -miphoneos-version-min=7.1"
|
||||
ADDI_FLAGS="--arch=arm --cpu=cortex-a9"
|
||||
build_one
|
||||
}
|
||||
|
||||
# armv7s
|
||||
function build_armv7s {
|
||||
PREFIX="armv7s"
|
||||
SYSROOT=$SYSROOT_iPHONE
|
||||
CC=$CC_IPHONE
|
||||
CFLAGS="-arch armv7s -mfpu=neon -miphoneos-version-min=7.1"
|
||||
LDFLAGS="-arch armv7s -isysroot $SYSROOT_iPHONE -miphoneos-version-min=7.1"
|
||||
ADDI_FLAGS="--arch=arm --cpu=cortex-a9"
|
||||
build_one
|
||||
}
|
||||
|
||||
# i386
|
||||
function build_i386 {
|
||||
PREFIX="i386"
|
||||
SYSROOT=$SYSROOT_SIMULATOR
|
||||
CC=$CC_SIMULATOR
|
||||
CFLAGS="-arch i386"
|
||||
LDFLAGS="-arch i386 -isysroot $SYSROOT_SIMULATOR -mios-simulator-version-min=7.1"
|
||||
ADDI_FLAGS="--arch=i386 --cpu=i386 --disable-asm"
|
||||
build_one
|
||||
}
|
||||
|
||||
|
||||
# create fat library
|
||||
function build_universal {
|
||||
cd armv7/lib
|
||||
for file in *.a
|
||||
do
|
||||
cd ../..
|
||||
xcrun -sdk iphoneos lipo -output universal/lib/$file -create \
|
||||
-arch armv7 armv7/lib/$file \
|
||||
-arch armv7s armv7s/lib/$file \
|
||||
-arch i386 i386/lib/$file
|
||||
echo "Universal $file created."
|
||||
cd -
|
||||
done
|
||||
cd ../..
|
||||
}
|
||||
|
||||
echo "FFmpeg directory?"
|
||||
read SOURCE
|
||||
|
||||
cd $SOURCE
|
||||
|
||||
if [ "$1" = "clean" ]
|
||||
then
|
||||
rm -r armv7 armv7s i386 universal
|
||||
make clean
|
||||
exit 0
|
||||
fi
|
||||
mkdir armv7
|
||||
mkdir armv7s
|
||||
mkdir i386
|
||||
mkdir -p universal/lib
|
||||
|
||||
build_armv7
|
||||
build_armv7s
|
||||
build_i386
|
||||
build_universal
|
||||
echo "Ouput files in $SOURCE/armv7 $SOURCE/armv7s $SOURCE/i386 $SOURCE/universal"
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
#!/bin/bash
|
||||
# Builds libopus for iOS or tvOS.
|
||||
#
|
||||
# Copyright 2012 Mike Tigas <mike@tig.as>
|
||||
#
|
||||
# Based on work by Felix Schulze on 16.12.10.
|
||||
# Copyright 2010 Felix Schulze. All rights reserved.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
###########################################################################
|
||||
# Choose your libopus version and your currently-installed iOS SDK version:
|
||||
#
|
||||
TARGET="AppleTV" # "iPhone"
|
||||
VERSION="1.3.1"
|
||||
SDKVERSION="16.1"
|
||||
MINVERSIONDEF="-mtvos-version-min=12.0" # "-miphoneos-version-min=12.0"
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Don't change anything under this line!
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
# by default, we won't build for debugging purposes
|
||||
if [ "${DEBUG}" == "true" ]; then
|
||||
echo "Compiling for debugging ..."
|
||||
OPT_CFLAGS="-O0 -fno-inline -g"
|
||||
OPT_LDFLAGS=""
|
||||
OPT_CONFIG_ARGS="--enable-assertions --disable-asm"
|
||||
else
|
||||
OPT_CFLAGS="-O3 -g"
|
||||
OPT_LDFLAGS=""
|
||||
OPT_CONFIG_ARGS=""
|
||||
fi
|
||||
|
||||
|
||||
# No need to change this since xcode build will only compile in the
|
||||
# necessary bits from the libraries we create
|
||||
ARCHS="x86_64 arm64"
|
||||
|
||||
DEVELOPER=`xcode-select -print-path`
|
||||
#DEVELOPER="/Applications/Xcode.app/Contents/Developer"
|
||||
|
||||
cd "`dirname \"$0\"`"
|
||||
REPOROOT=$(pwd)
|
||||
|
||||
# Where we'll end up storing things in the end
|
||||
OUTPUTDIR="${REPOROOT}/dependencies"
|
||||
mkdir -p ${OUTPUTDIR}/include
|
||||
mkdir -p ${OUTPUTDIR}/lib
|
||||
|
||||
|
||||
BUILDDIR="${REPOROOT}/build"
|
||||
|
||||
# where we will keep our sources and build from.
|
||||
SRCDIR="${BUILDDIR}/src"
|
||||
mkdir -p $SRCDIR
|
||||
# where we will store intermediary builds
|
||||
INTERDIR="${BUILDDIR}/built"
|
||||
mkdir -p $INTERDIR
|
||||
|
||||
########################################
|
||||
|
||||
cd $SRCDIR
|
||||
|
||||
# Exit the script if an error happens
|
||||
set -e
|
||||
|
||||
if [ ! -e "${SRCDIR}/opus-${VERSION}.tar.gz" ]; then
|
||||
echo "Downloading opus-${VERSION}.tar.gz"
|
||||
curl -LO http://downloads.xiph.org/releases/opus/opus-${VERSION}.tar.gz
|
||||
fi
|
||||
echo "Using opus-${VERSION}.tar.gz"
|
||||
|
||||
tar zxf opus-${VERSION}.tar.gz -C $SRCDIR
|
||||
cd "${SRCDIR}/opus-${VERSION}"
|
||||
|
||||
set +e # don't bail out of bash script if ccache doesn't exist
|
||||
CCACHE=`which ccache`
|
||||
if [ $? == "0" ]; then
|
||||
echo "Building with ccache: $CCACHE"
|
||||
CCACHE="${CCACHE} "
|
||||
else
|
||||
echo "Building without ccache"
|
||||
CCACHE=""
|
||||
fi
|
||||
set -e # back to regular "bail out on error" mode
|
||||
|
||||
export ORIGINALPATH=$PATH
|
||||
|
||||
for ARCH in ${ARCHS}
|
||||
do
|
||||
if [ "${ARCH}" == "i386" ] || [ "${ARCH}" == "x86_64" ]; then
|
||||
PLATFORM="${TARGET}Simulator"
|
||||
EXTRA_CFLAGS="-arch ${ARCH}"
|
||||
EXTRA_CONFIG="--host=x86_64-apple-darwin"
|
||||
else
|
||||
PLATFORM="${TARGET}OS"
|
||||
EXTRA_CFLAGS="-arch ${ARCH}"
|
||||
EXTRA_CONFIG="--host=arm-apple-darwin"
|
||||
fi
|
||||
|
||||
mkdir -p "${INTERDIR}/${PLATFORM}${SDKVERSION}-${ARCH}.sdk"
|
||||
|
||||
./configure --disable-shared --enable-static --with-pic --disable-extra-programs --disable-doc ${EXTRA_CONFIG} \
|
||||
--prefix="${INTERDIR}/${PLATFORM}${SDKVERSION}-${ARCH}.sdk" \
|
||||
LDFLAGS="$LDFLAGS ${OPT_LDFLAGS} -fPIE ${MINVERSIONDEF} -L${OUTPUTDIR}/lib" \
|
||||
CFLAGS="$CFLAGS ${EXTRA_CFLAGS} ${OPT_CFLAGS} -fPIE ${MINVERSIONDEF} -I${OUTPUTDIR}/include -isysroot ${DEVELOPER}/Platforms/${PLATFORM}.platform/Developer/SDKs/${PLATFORM}${SDKVERSION}.sdk" \
|
||||
|
||||
# Build the application and install it to the fake SDK intermediary dir
|
||||
# we have set up. Make sure to clean up afterward because we will re-use
|
||||
# this source tree to cross-compile other targets.
|
||||
make -j$(nproc)
|
||||
make install
|
||||
make clean
|
||||
done
|
||||
|
||||
########################################
|
||||
|
||||
echo "Build library..."
|
||||
|
||||
# These are the libs that comprise libopus.
|
||||
OUTPUT_LIBS="libopus.a"
|
||||
for OUTPUT_LIB in ${OUTPUT_LIBS}; do
|
||||
INPUT_LIBS=""
|
||||
for ARCH in ${ARCHS}; do
|
||||
if [ "${ARCH}" == "i386" ] || [ "${ARCH}" == "x86_64" ];
|
||||
then
|
||||
PLATFORM="${TARGET}Simulator"
|
||||
else
|
||||
PLATFORM="${TARGET}OS"
|
||||
fi
|
||||
INPUT_ARCH_LIB="${INTERDIR}/${PLATFORM}${SDKVERSION}-${ARCH}.sdk/lib/${OUTPUT_LIB}"
|
||||
if [ -e $INPUT_ARCH_LIB ]; then
|
||||
INPUT_LIBS="${INPUT_LIBS} ${INPUT_ARCH_LIB}"
|
||||
fi
|
||||
done
|
||||
# Combine the three architectures into a universal library.
|
||||
if [ -n "$INPUT_LIBS" ]; then
|
||||
lipo -create $INPUT_LIBS \
|
||||
-output "${OUTPUTDIR}/lib/${OUTPUT_LIB}"
|
||||
else
|
||||
echo "$OUTPUT_LIB does not exist, skipping (are the dependencies installed?)"
|
||||
fi
|
||||
done
|
||||
|
||||
for ARCH in ${ARCHS}; do
|
||||
if [ "${ARCH}" == "i386" ] || [ "${ARCH}" == "x86_64" ];
|
||||
then
|
||||
PLATFORM="${TARGET}Simulator"
|
||||
else
|
||||
PLATFORM="${TARGET}OS"
|
||||
fi
|
||||
cp -R ${INTERDIR}/${PLATFORM}${SDKVERSION}-${ARCH}.sdk/include/* ${OUTPUTDIR}/include/
|
||||
if [ $? == "0" ]; then
|
||||
# We only need to copy the headers over once. (So break out of forloop
|
||||
# once we get first success.)
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
####################
|
||||
|
||||
echo "Building done."
|
||||
echo "Cleaning up..."
|
||||
rm -fr ${INTERDIR}
|
||||
rm -fr "${SRCDIR}/opus-${VERSION}"
|
||||
echo "Done."
|
||||
@@ -1,124 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Yay shell scripting! This script builds a static version of
|
||||
# OpenSSL ${OPENSSL_VERSION} for iOS 7.0 that contains code for
|
||||
# armv6, armv7, arm7s and i386.
|
||||
|
||||
#set -x
|
||||
|
||||
# Setup paths to stuff we need
|
||||
|
||||
OPENSSL_VERSION="1.0.1h"
|
||||
|
||||
DEVELOPER="/Applications/Xcode.app/Contents/Developer"
|
||||
|
||||
SDK_VERSION="7.1"
|
||||
MIN_VERSION="7.1"
|
||||
|
||||
IPHONEOS_PLATFORM="${DEVELOPER}/Platforms/iPhoneOS.platform"
|
||||
IPHONEOS_SDK="${IPHONEOS_PLATFORM}/Developer/SDKs/iPhoneOS.sdk"
|
||||
IPHONEOS_GCC="/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang"
|
||||
|
||||
IPHONESIMULATOR_PLATFORM="${DEVELOPER}/Platforms/iPhoneSimulator.platform"
|
||||
IPHONESIMULATOR_SDK="${IPHONESIMULATOR_PLATFORM}/Developer/SDKs/iPhoneSimulator.sdk"
|
||||
IPHONESIMULATOR_GCC="/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang"
|
||||
|
||||
# Make sure things actually exist
|
||||
|
||||
if [ ! -d "$IPHONEOS_PLATFORM" ]; then
|
||||
echo "Cannot find $IPHONEOS_PLATFORM"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$IPHONEOS_SDK" ]; then
|
||||
echo "Cannot find $IPHONEOS_SDK"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -x "$IPHONEOS_GCC" ]; then
|
||||
echo "Cannot find $IPHONEOS_GCC"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$IPHONESIMULATOR_PLATFORM" ]; then
|
||||
echo "Cannot find $IPHONESIMULATOR_PLATFORM"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$IPHONESIMULATOR_SDK" ]; then
|
||||
echo "Cannot find $IPHONESIMULATOR_SDK"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -x "$IPHONESIMULATOR_GCC" ]; then
|
||||
echo "Cannot find $IPHONESIMULATOR_GCC"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Clean up whatever was left from our previous build
|
||||
|
||||
rm -rf include lib
|
||||
rm -rf /tmp/openssl-${OPENSSL_VERSION}-*
|
||||
rm -rf /tmp/openssl-${OPENSSL_VERSION}-*.*-log
|
||||
|
||||
build()
|
||||
{
|
||||
TARGET=$1
|
||||
ARCH=$2
|
||||
GCC=$3
|
||||
SDK=$4
|
||||
EXTRA=$5
|
||||
rm -rf "openssl-${OPENSSL_VERSION}"
|
||||
tar xfz "openssl-${OPENSSL_VERSION}.tar.gz"
|
||||
pushd .
|
||||
cd "openssl-${OPENSSL_VERSION}"
|
||||
./Configure ${TARGET} --openssldir="/tmp/openssl-${OPENSSL_VERSION}-${ARCH}" ${EXTRA} &> "/tmp/openssl-${OPENSSL_VERSION}-${ARCH}.log"
|
||||
perl -i -pe 's|static volatile sig_atomic_t intr_signal|static volatile int intr_signal|' crypto/ui/ui_openssl.c
|
||||
perl -i -pe "s|^CC= gcc|CC= ${GCC} -arch ${ARCH} -miphoneos-version-min=${MIN_VERSION}|g" Makefile
|
||||
perl -i -pe "s|^CFLAG= (.*)|CFLAG= -isysroot ${SDK} \$1|g" Makefile
|
||||
make &> "/tmp/openssl-${OPENSSL_VERSION}-${ARCH}.build-log"
|
||||
make install &> "/tmp/openssl-${OPENSSL_VERSION}-${ARCH}.install-log"
|
||||
popd
|
||||
rm -rf "openssl-${OPENSSL_VERSION}"
|
||||
}
|
||||
|
||||
mkdir openssl
|
||||
cd openssl
|
||||
if [ ! -e ${OPENSSL_VERSION}.tar.gz ]; then
|
||||
echo "Downloading ${OPENSSL_VERSION}.tar.gz"
|
||||
curl -O http://www.openssl.org/source/openssl-${OPENSSL_VERSION}.tar.gz
|
||||
else
|
||||
echo "Using ${OPENSSL_VERSION}.tar.gz"
|
||||
fi
|
||||
|
||||
|
||||
build "BSD-generic32" "armv7" "${IPHONEOS_GCC}" "${IPHONEOS_SDK}" ""
|
||||
build "BSD-generic32" "armv7s" "${IPHONEOS_GCC}" "${IPHONEOS_SDK}" ""
|
||||
build "BSD-generic64" "arm64" "${IPHONEOS_GCC}" "${IPHONEOS_SDK}" ""
|
||||
build "BSD-generic32" "i386" "${IPHONESIMULATOR_GCC}" "${IPHONESIMULATOR_SDK}" ""
|
||||
build "BSD-generic64" "x86_64" "${IPHONESIMULATOR_GCC}" "${IPHONESIMULATOR_SDK}" "-DOPENSSL_NO_ASM"
|
||||
|
||||
#
|
||||
|
||||
mkdir include
|
||||
cp -r /tmp/openssl-${OPENSSL_VERSION}-i386/include/openssl include/
|
||||
|
||||
mkdir lib
|
||||
lipo \
|
||||
"/tmp/openssl-${OPENSSL_VERSION}-armv7/lib/libcrypto.a" \
|
||||
"/tmp/openssl-${OPENSSL_VERSION}-armv7s/lib/libcrypto.a" \
|
||||
"/tmp/openssl-${OPENSSL_VERSION}-arm64/lib/libcrypto.a" \
|
||||
"/tmp/openssl-${OPENSSL_VERSION}-i386/lib/libcrypto.a" \
|
||||
"/tmp/openssl-${OPENSSL_VERSION}-x86_64/lib/libcrypto.a" \
|
||||
-create -output lib/libcrypto.a
|
||||
lipo \
|
||||
"/tmp/openssl-${OPENSSL_VERSION}-armv7/lib/libssl.a" \
|
||||
"/tmp/openssl-${OPENSSL_VERSION}-armv7s/lib/libssl.a" \
|
||||
"/tmp/openssl-${OPENSSL_VERSION}-arm64/lib/libssl.a" \
|
||||
"/tmp/openssl-${OPENSSL_VERSION}-i386/lib/libssl.a" \
|
||||
"/tmp/openssl-${OPENSSL_VERSION}-x86_64/lib/libssl.a" \
|
||||
-create -output lib/libssl.a
|
||||
|
||||
rm -rf "/tmp/openssl-${OPENSSL_VERSION}-*"
|
||||
rm -rf "/tmp/openssl-${OPENSSL_VERSION}-*.*-log"
|
||||
|
||||
@@ -672,3 +672,11 @@ may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
|
||||
App Store Distribution Exception
|
||||
|
||||
As additional permission under section 7, you are allowed to distribute the
|
||||
software through an app store, even if that store has restrictive terms and
|
||||
conditions that are incompatible with the GPL, provided that the source is
|
||||
also available under the GPL with or without this permission through a channel
|
||||
without those restrictive terms and conditions.
|
||||
|
||||
@@ -96,6 +96,7 @@ static NSData* p12 = nil;
|
||||
|
||||
bio = BIO_new(BIO_s_mem());
|
||||
i2d_X509_bio(bio, x509);
|
||||
X509_free(x509);
|
||||
|
||||
BUF_MEM* mem;
|
||||
BIO_get_mem_ptr(bio, &mem);
|
||||
@@ -222,6 +223,7 @@ static NSData* p12 = nil;
|
||||
+ (NSData *)getSignatureFromCert:(NSData *)cert {
|
||||
BIO* bio = BIO_new_mem_buf([cert bytes], (int)[cert length]);
|
||||
X509* x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
|
||||
BIO_free(bio);
|
||||
|
||||
if (!x509) {
|
||||
Log(LOG_E, @"Unable to parse certificate in memory!");
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <OpenSSL/provider.h>
|
||||
#include <OpenSSL/rsa.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <OpenSSL/rand.h>
|
||||
|
||||
static const int NUM_BITS = 2048;
|
||||
static const int SERIAL = 0;
|
||||
@@ -61,22 +63,30 @@ void mkcert(X509 **x509p, EVP_PKEY **pkeyp, int bits, int serial, int years) {
|
||||
}
|
||||
|
||||
struct CertKeyPair generateCertKeyPair(void) {
|
||||
BIO *bio_err;
|
||||
X509 *x509 = NULL;
|
||||
EVP_PKEY *pkey = NULL;
|
||||
PKCS12 *p12 = NULL;
|
||||
|
||||
bio_err = BIO_new_fp(stderr, BIO_NOCLOSE);
|
||||
|
||||
mkcert(&x509, &pkey, NUM_BITS, SERIAL, NUM_YEARS);
|
||||
|
||||
p12 = PKCS12_create("limelight", "GameStream", pkey, x509, NULL, 0, 0, 0, 0, 0);
|
||||
|
||||
char* pass = "limelight";
|
||||
p12 = PKCS12_create(pass,
|
||||
"GameStream",
|
||||
pkey,
|
||||
x509,
|
||||
NULL,
|
||||
NID_pbe_WithSHA1And3_Key_TripleDES_CBC,
|
||||
-1, // disable certificate encryption
|
||||
2048,
|
||||
-1, // disable the automatic MAC
|
||||
0);
|
||||
// MAC it ourselves with SHA1 since iOS refuses to load anything else.
|
||||
PKCS12_set_mac(p12, pass, -1, NULL, 0, 1, EVP_sha1());
|
||||
|
||||
if (p12 == NULL) {
|
||||
printf("Error generating a valid PKCS12 certificate.\n");
|
||||
}
|
||||
|
||||
BIO_free(bio_err);
|
||||
|
||||
return (CertKeyPair){x509, pkey, p12};
|
||||
}
|
||||
|
||||
|
||||
@@ -17,11 +17,14 @@
|
||||
framerate:(NSInteger)framerate
|
||||
height:(NSInteger)height
|
||||
width:(NSInteger)width
|
||||
audioConfig:(NSInteger)audioConfig
|
||||
onscreenControls:(NSInteger)onscreenControls
|
||||
optimizeGames:(BOOL)optimizeGames
|
||||
multiController:(BOOL)multiController
|
||||
swapABXYButtons:(BOOL)swapABXYButtons
|
||||
audioOnPC:(BOOL)audioOnPC
|
||||
useHevc:(BOOL)useHevc
|
||||
preferredCodec:(uint32_t)preferredCodec
|
||||
useFramePacing:(BOOL)useFramePacing
|
||||
enableHdr:(BOOL)enableHdr
|
||||
btMouseSupport:(BOOL)btMouseSupport
|
||||
absoluteTouchMode:(BOOL)absoluteTouchMode
|
||||
|
||||
@@ -56,11 +56,14 @@
|
||||
framerate:(NSInteger)framerate
|
||||
height:(NSInteger)height
|
||||
width:(NSInteger)width
|
||||
audioConfig:(NSInteger)audioConfig
|
||||
onscreenControls:(NSInteger)onscreenControls
|
||||
optimizeGames:(BOOL)optimizeGames
|
||||
multiController:(BOOL)multiController
|
||||
swapABXYButtons:(BOOL)swapABXYButtons
|
||||
audioOnPC:(BOOL)audioOnPC
|
||||
useHevc:(BOOL)useHevc
|
||||
preferredCodec:(uint32_t)preferredCodec
|
||||
useFramePacing:(BOOL)useFramePacing
|
||||
enableHdr:(BOOL)enableHdr
|
||||
btMouseSupport:(BOOL)btMouseSupport
|
||||
absoluteTouchMode:(BOOL)absoluteTouchMode
|
||||
@@ -72,11 +75,14 @@
|
||||
settingsToSave.bitrate = [NSNumber numberWithInteger:bitrate];
|
||||
settingsToSave.height = [NSNumber numberWithInteger:height];
|
||||
settingsToSave.width = [NSNumber numberWithInteger:width];
|
||||
settingsToSave.audioConfig = [NSNumber numberWithInteger:audioConfig];
|
||||
settingsToSave.onscreenControls = [NSNumber numberWithInteger:onscreenControls];
|
||||
settingsToSave.optimizeGames = optimizeGames;
|
||||
settingsToSave.multiController = multiController;
|
||||
settingsToSave.swapABXYButtons = swapABXYButtons;
|
||||
settingsToSave.playAudioOnPC = audioOnPC;
|
||||
settingsToSave.useHevc = useHevc;
|
||||
settingsToSave.preferredCodec = preferredCodec;
|
||||
settingsToSave.useFramePacing = useFramePacing;
|
||||
settingsToSave.enableHdr = enableHdr;
|
||||
settingsToSave.btMouseSupport = btMouseSupport;
|
||||
settingsToSave.absoluteTouchMode = absoluteTouchMode;
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
@property (atomic) PairState pairState;
|
||||
@property (atomic, nullable, retain) NSString * activeAddress;
|
||||
@property (atomic, nullable, retain) NSString * currentGame;
|
||||
@property (atomic) unsigned short httpsPort;
|
||||
@property (atomic) BOOL isNvidiaServerSoftware;
|
||||
|
||||
@property (atomic, nullable, retain) NSData *serverCert;
|
||||
@property (atomic, nullable, retain) NSString *address;
|
||||
|
||||
@@ -34,6 +34,11 @@
|
||||
self.serverCodecModeSupport = host.serverCodecModeSupport;
|
||||
self.serverCert = host.serverCert;
|
||||
|
||||
// Older clients stored a non-URL-escaped IPv6 string. Try to detect that and fix it up.
|
||||
if (![self.ipv6Address containsString:@"["]) {
|
||||
self.ipv6Address = [Utils addressAndPortToAddressPortString:self.ipv6Address port:47989];
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
|
||||
@@ -16,10 +16,18 @@
|
||||
@property (nonatomic, retain) NSNumber * framerate;
|
||||
@property (nonatomic, retain) NSNumber * height;
|
||||
@property (nonatomic, retain) NSNumber * width;
|
||||
@property (nonatomic, retain) NSNumber * audioConfig;
|
||||
@property (nonatomic, retain) NSNumber * onscreenControls;
|
||||
@property (nonatomic, retain) NSString * uniqueId;
|
||||
@property (nonatomic) BOOL useHevc;
|
||||
@property (nonatomic) enum {
|
||||
CODEC_PREF_AUTO,
|
||||
CODEC_PREF_H264,
|
||||
CODEC_PREF_HEVC,
|
||||
CODEC_PREF_AV1,
|
||||
} preferredCodec;
|
||||
@property (nonatomic) BOOL useFramePacing;
|
||||
@property (nonatomic) BOOL multiController;
|
||||
@property (nonatomic) BOOL swapABXYButtons;
|
||||
@property (nonatomic) BOOL playAudioOnPC;
|
||||
@property (nonatomic) BOOL optimizeGames;
|
||||
@property (nonatomic) BOOL enableHdr;
|
||||
|
||||
@@ -34,11 +34,15 @@
|
||||
assert([self.bitrate intValue] != 0);
|
||||
self.framerate = [NSNumber numberWithInteger:[[NSUserDefaults standardUserDefaults] integerForKey:@"framerate"]];
|
||||
assert([self.framerate intValue] != 0);
|
||||
self.useHevc = [[NSUserDefaults standardUserDefaults] boolForKey:@"useHevc"];
|
||||
self.audioConfig = [NSNumber numberWithInteger:[[NSUserDefaults standardUserDefaults] integerForKey:@"audioConfig"]];
|
||||
assert([self.audioConfig intValue] != 0);
|
||||
self.preferredCodec = (typeof(self.preferredCodec))[[NSUserDefaults standardUserDefaults] integerForKey:@"preferredCodec"];
|
||||
self.useFramePacing = [[NSUserDefaults standardUserDefaults] integerForKey:@"useFramePacing"] != 0;
|
||||
self.playAudioOnPC = [[NSUserDefaults standardUserDefaults] boolForKey:@"audioOnPC"];
|
||||
self.enableHdr = [[NSUserDefaults standardUserDefaults] boolForKey:@"enableHdr"];
|
||||
self.optimizeGames = [[NSUserDefaults standardUserDefaults] boolForKey:@"optimizeGames"];
|
||||
self.multiController = [[NSUserDefaults standardUserDefaults] boolForKey:@"multipleControllers"];
|
||||
self.swapABXYButtons = [[NSUserDefaults standardUserDefaults] boolForKey:@"swapABXYButtons"];
|
||||
self.btMouseSupport = [[NSUserDefaults standardUserDefaults] boolForKey:@"btMouseSupport"];
|
||||
self.statsOverlay = [[NSUserDefaults standardUserDefaults] boolForKey:@"statsOverlay"];
|
||||
|
||||
@@ -56,6 +60,10 @@
|
||||
self.height = [NSNumber numberWithInteger:2160];
|
||||
self.width = [NSNumber numberWithInteger:3840];
|
||||
break;
|
||||
case 3:
|
||||
self.height = [NSNumber numberWithInteger:1440];
|
||||
self.width = [NSNumber numberWithInteger:2560];
|
||||
break;
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
@@ -65,11 +73,14 @@
|
||||
self.framerate = settings.framerate;
|
||||
self.height = settings.height;
|
||||
self.width = settings.width;
|
||||
self.useHevc = settings.useHevc;
|
||||
self.audioConfig = settings.audioConfig;
|
||||
self.preferredCodec = settings.preferredCodec;
|
||||
self.useFramePacing = settings.useFramePacing;
|
||||
self.playAudioOnPC = settings.playAudioOnPC;
|
||||
self.enableHdr = settings.enableHdr;
|
||||
self.optimizeGames = settings.optimizeGames;
|
||||
self.multiController = settings.multiController;
|
||||
self.swapABXYButtons = settings.swapABXYButtons;
|
||||
self.onscreenControls = settings.onscreenControls;
|
||||
self.btMouseSupport = settings.btMouseSupport;
|
||||
self.absoluteTouchMode = settings.absoluteTouchMode;
|
||||
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "AltIcon.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ControlIcon.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "DeleteIcon.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "doneIcon.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 405 B |
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "EscapeIcon.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ShiftIcon.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 719 B |
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "TabIcon.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "WindowsIcon.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -13,6 +13,11 @@
|
||||
|
||||
@interface Controller : NSObject
|
||||
|
||||
typedef struct {
|
||||
float lastX;
|
||||
float lastY;
|
||||
} controller_touch_context_t;
|
||||
|
||||
@property (nullable, nonatomic, retain) GCController* gamepad;
|
||||
@property (nonatomic) int playerIndex;
|
||||
@property (nonatomic) int lastButtonFlags;
|
||||
@@ -25,7 +30,24 @@
|
||||
@property (nonatomic) short lastRightStickX;
|
||||
@property (nonatomic) short lastRightStickY;
|
||||
|
||||
@property (nonatomic) controller_touch_context_t primaryTouch;
|
||||
@property (nonatomic) controller_touch_context_t secondaryTouch;
|
||||
|
||||
@property (nonatomic) HapticContext* _Nullable lowFreqMotor;
|
||||
@property (nonatomic) HapticContext* _Nullable highFreqMotor;
|
||||
@property (nonatomic) HapticContext* _Nullable leftTriggerMotor;
|
||||
@property (nonatomic) HapticContext* _Nullable rightTriggerMotor;
|
||||
|
||||
@property (nonatomic) NSTimer* _Nullable accelTimer;
|
||||
@property (nonatomic) GCAcceleration lastAccelSample;
|
||||
@property (nonatomic) NSTimer* _Nullable gyroTimer;
|
||||
@property (nonatomic) GCRotationRate lastGyroSample;
|
||||
|
||||
@property (nonatomic) NSTimer* _Nullable batteryTimer;
|
||||
@property (nonatomic) GCDeviceBatteryState lastBatteryState;
|
||||
@property (nonatomic) float lastBatteryLevel;
|
||||
|
||||
@property (nonatomic) BOOL reportedArrival;
|
||||
@property (nonatomic) Controller* _Nullable mergedWithController;
|
||||
|
||||
@end
|
||||
|
||||
@@ -11,16 +11,18 @@
|
||||
|
||||
@class OnScreenControls;
|
||||
|
||||
@protocol InputPresenceDelegate <NSObject>
|
||||
@protocol ControllerSupportDelegate <NSObject>
|
||||
|
||||
- (void) gamepadPresenceChanged;
|
||||
- (void) mousePresenceChanged;
|
||||
- (void) streamExitRequested;
|
||||
|
||||
@end
|
||||
|
||||
@interface ControllerSupport : NSObject
|
||||
|
||||
-(id) initWithConfig:(StreamConfiguration*)streamConfig presenceDelegate:(id<InputPresenceDelegate>)delegate;
|
||||
-(id) initWithConfig:(StreamConfiguration*)streamConfig delegate:(id<ControllerSupportDelegate>)delegate;
|
||||
-(void) connectionEstablished;
|
||||
|
||||
-(void) initAutoOnScreenControlMode:(OnScreenControls*)osc;
|
||||
-(void) cleanup;
|
||||
@@ -40,6 +42,9 @@
|
||||
-(void) updateFinished:(Controller*)controller;
|
||||
|
||||
-(void) rumble:(unsigned short)controllerNumber lowFreqMotor:(unsigned short)lowFreqMotor highFreqMotor:(unsigned short)highFreqMotor;
|
||||
-(void) rumbleTriggers:(uint16_t)controllerNumber leftTrigger:(uint16_t)leftTrigger rightTrigger:(uint16_t)rightTrigger;
|
||||
-(void) setMotionEventState:(uint16_t)controllerNumber motionType:(uint8_t)motionType reportRateHz:(uint16_t)reportRateHz;
|
||||
-(void) setControllerLed:(uint16_t)controllerNumber r:(uint8_t)r g:(uint8_t)g b:(uint8_t)b;
|
||||
|
||||
+(int) getConnectedGamepadMask:(StreamConfiguration*)streamConfig;
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
@import GameController;
|
||||
@import AudioToolbox;
|
||||
|
||||
static const double MOUSE_SPEED_DIVISOR = 2.5;
|
||||
static const double MOUSE_SPEED_DIVISOR = 1.25;
|
||||
|
||||
@implementation ControllerSupport {
|
||||
id _controllerConnectObserver;
|
||||
@@ -29,17 +29,15 @@ static const double MOUSE_SPEED_DIVISOR = 2.5;
|
||||
|
||||
NSLock *_controllerStreamLock;
|
||||
NSMutableDictionary *_controllers;
|
||||
id<InputPresenceDelegate> _presenceDelegate;
|
||||
id<ControllerSupportDelegate> _delegate;
|
||||
|
||||
float accumulatedDeltaX;
|
||||
float accumulatedDeltaY;
|
||||
float accumulatedScrollX;
|
||||
float accumulatedScrollY;
|
||||
|
||||
OnScreenControls *_osc;
|
||||
|
||||
// This controller object is shared between on-screen controls
|
||||
// and player 0
|
||||
Controller *_player0osc;
|
||||
Controller *_oscController;
|
||||
|
||||
#define EMULATING_SELECT 0x1
|
||||
#define EMULATING_SPECIAL 0x2
|
||||
@@ -47,18 +45,20 @@ static const double MOUSE_SPEED_DIVISOR = 2.5;
|
||||
bool _oscEnabled;
|
||||
char _controllerNumbers;
|
||||
bool _multiController;
|
||||
bool _swapABXYButtons;
|
||||
}
|
||||
|
||||
// UPDATE_BUTTON_FLAG(controller, flag, pressed)
|
||||
#define UPDATE_BUTTON_FLAG(controller, x, y) \
|
||||
((y) ? [self setButtonFlag:controller flags:x] : [self clearButtonFlag:controller flags:x])
|
||||
|
||||
#define MAX_MAGNITUDE(x, y) (abs(x) > abs(y) ? (x) : (y))
|
||||
|
||||
-(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;
|
||||
// TODO: Rumble emulation for OSC
|
||||
}
|
||||
if (controller == nil) {
|
||||
// No connected controller for this player
|
||||
@@ -69,6 +69,126 @@ static const double MOUSE_SPEED_DIVISOR = 2.5;
|
||||
[controller.highFreqMotor setMotorAmplitude:highFreqMotor];
|
||||
}
|
||||
|
||||
-(void) rumbleTriggers:(uint16_t)controllerNumber leftTrigger:(uint16_t)leftTrigger rightTrigger:(uint16_t)rightTrigger
|
||||
{
|
||||
Controller* controller = [_controllers objectForKey:[NSNumber numberWithInteger:controllerNumber]];
|
||||
if (controller == nil && controllerNumber == 0 && _oscEnabled) {
|
||||
// TODO: Trigger rumble emulation for OSC
|
||||
}
|
||||
if (controller == nil) {
|
||||
// No connected controller for this player
|
||||
return;
|
||||
}
|
||||
|
||||
[controller.leftTriggerMotor setMotorAmplitude:leftTrigger];
|
||||
[controller.rightTriggerMotor setMotorAmplitude:rightTrigger];
|
||||
}
|
||||
|
||||
- (void) setMotionEventState:(uint16_t)controllerNumber motionType:(uint8_t)motionType reportRateHz:(uint16_t)reportRateHz
|
||||
{
|
||||
if (@available(iOS 14.0, tvOS 14.0, *)) {
|
||||
Controller* controller = [_controllers objectForKey:[NSNumber numberWithInteger:controllerNumber]];
|
||||
if (controller == nil) {
|
||||
// No connected controller for this player
|
||||
return;
|
||||
}
|
||||
|
||||
if (controller.gamepad.motion == nil) {
|
||||
// No motion supported for this controller
|
||||
return;
|
||||
}
|
||||
|
||||
switch (motionType) {
|
||||
case LI_MOTION_TYPE_ACCEL:
|
||||
[controller.accelTimer invalidate];
|
||||
controller.accelTimer = nil;
|
||||
|
||||
if (reportRateHz && controller.gamepad.motion.hasGravityAndUserAcceleration) {
|
||||
// Reset the last motion sample
|
||||
GCAcceleration emptyAccelSample = {};
|
||||
controller.lastAccelSample = emptyAccelSample;
|
||||
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
controller.accelTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 / reportRateHz repeats:YES block:^(NSTimer *timer) {
|
||||
// Don't send duplicate samples
|
||||
GCAcceleration lastAccelSample = controller.lastAccelSample;
|
||||
GCAcceleration accelSample = controller.gamepad.motion.acceleration;
|
||||
if (memcmp(&accelSample, &lastAccelSample, sizeof(accelSample)) == 0) {
|
||||
return;
|
||||
}
|
||||
controller.lastAccelSample = accelSample;
|
||||
|
||||
// Convert g to m/s^2
|
||||
LiSendControllerMotionEvent((uint8_t)controllerNumber,
|
||||
LI_MOTION_TYPE_ACCEL,
|
||||
accelSample.x * -9.80665f,
|
||||
accelSample.y * -9.80665f,
|
||||
accelSample.z * -9.80665f);
|
||||
}];
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case LI_MOTION_TYPE_GYRO:
|
||||
[controller.gyroTimer invalidate];
|
||||
controller.gyroTimer = nil;
|
||||
|
||||
if (reportRateHz && controller.gamepad.motion.hasRotationRate) {
|
||||
// Reset the last motion sample
|
||||
GCRotationRate emptyGyroSample = {};
|
||||
controller.lastGyroSample = emptyGyroSample;
|
||||
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
controller.gyroTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 / reportRateHz repeats:YES block:^(NSTimer *timer) {
|
||||
// Don't send duplicate samples
|
||||
GCRotationRate lastGyroSample = controller.lastGyroSample;
|
||||
GCRotationRate gyroSample = controller.gamepad.motion.rotationRate;
|
||||
if (memcmp(&gyroSample, &lastGyroSample, sizeof(gyroSample)) == 0) {
|
||||
return;
|
||||
}
|
||||
controller.lastGyroSample = gyroSample;
|
||||
|
||||
// Convert rad/s to deg/s
|
||||
LiSendControllerMotionEvent((uint8_t)controllerNumber,
|
||||
LI_MOTION_TYPE_GYRO,
|
||||
gyroSample.x * 57.2957795f,
|
||||
gyroSample.z * 57.2957795f,
|
||||
gyroSample.y * -57.2957795f);
|
||||
}];
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Set the motion sensor state if they require manual activation
|
||||
if (controller.gamepad.motion.sensorsRequireManualActivation) {
|
||||
if (controller.gyroTimer || controller.accelTimer) {
|
||||
controller.gamepad.motion.sensorsActive = YES;
|
||||
}
|
||||
else {
|
||||
controller.gamepad.motion.sensorsActive = NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-(void) setControllerLed:(uint16_t)controllerNumber r:(uint8_t)r g:(uint8_t)g b:(uint8_t)b {
|
||||
if (@available(iOS 14.0, tvOS 14.0, *)) {
|
||||
Controller* controller = [_controllers objectForKey:[NSNumber numberWithInteger:controllerNumber]];
|
||||
if (controller == nil) {
|
||||
// No connected controller for this player
|
||||
return;
|
||||
}
|
||||
|
||||
if (controller.gamepad.light == nil) {
|
||||
// No LED control supported for this controller
|
||||
return;
|
||||
}
|
||||
|
||||
controller.gamepad.light.color = [[GCColor alloc] initWithRed:(r / 255.0f) green:(g / 255.0f) blue:(b / 255.0f)];
|
||||
}
|
||||
}
|
||||
|
||||
-(void) updateLeftStick:(Controller*)controller x:(short)x y:(short)y
|
||||
{
|
||||
@synchronized(controller) {
|
||||
@@ -196,15 +316,58 @@ static const double MOUSE_SPEED_DIVISOR = 2.5;
|
||||
}
|
||||
}
|
||||
|
||||
-(uint16_t) getActiveGamepadMask
|
||||
{
|
||||
return (_multiController ? _controllerNumbers : 1) | (_oscEnabled ? 1 : 0);
|
||||
}
|
||||
|
||||
-(void) updateFinished:(Controller*)controller
|
||||
{
|
||||
BOOL exitRequested = NO;
|
||||
|
||||
[_controllerStreamLock lock];
|
||||
@synchronized(controller) {
|
||||
// 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);
|
||||
// Handle Start+Select+L1+R1 gamepad quit combo
|
||||
if (controller.lastButtonFlags == (PLAY_FLAG | BACK_FLAG | LB_FLAG | RB_FLAG)) {
|
||||
controller.lastButtonFlags = 0;
|
||||
exitRequested = YES;
|
||||
}
|
||||
|
||||
// Only send controller events if we successfully reported controller arrival
|
||||
if ([self reportControllerArrival:controller]) {
|
||||
uint32_t buttonFlags = controller.lastButtonFlags;
|
||||
uint8_t leftTrigger = controller.lastLeftTrigger;
|
||||
uint8_t rightTrigger = controller.lastRightTrigger;
|
||||
int16_t leftStickX = controller.lastLeftStickX;
|
||||
int16_t leftStickY = controller.lastLeftStickY;
|
||||
int16_t rightStickX = controller.lastRightStickX;
|
||||
int16_t rightStickY = controller.lastRightStickY;
|
||||
|
||||
// If this is merged with another controller, combine the inputs
|
||||
if (controller.mergedWithController) {
|
||||
buttonFlags |= controller.mergedWithController.lastButtonFlags;
|
||||
leftTrigger = MAX(leftTrigger, controller.mergedWithController.lastLeftTrigger);
|
||||
rightTrigger = MAX(rightTrigger, controller.mergedWithController.lastRightTrigger);
|
||||
leftStickX = MAX_MAGNITUDE(leftStickX, controller.mergedWithController.lastLeftStickX);
|
||||
leftStickY = MAX_MAGNITUDE(leftStickY, controller.mergedWithController.lastLeftStickY);
|
||||
rightStickX = MAX_MAGNITUDE(rightStickX, controller.mergedWithController.lastRightStickX);
|
||||
rightStickY = MAX_MAGNITUDE(rightStickY, controller.mergedWithController.lastRightStickY);
|
||||
}
|
||||
|
||||
// Player 1 is always present for OSC
|
||||
LiSendMultiControllerEvent(_multiController ? controller.playerIndex : 0, [self getActiveGamepadMask],
|
||||
buttonFlags, leftTrigger, rightTrigger,
|
||||
leftStickX, leftStickY, rightStickX, rightStickY);
|
||||
}
|
||||
}
|
||||
[_controllerStreamLock unlock];
|
||||
|
||||
if (exitRequested) {
|
||||
// Invoke the delegate callback on the main thread
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self->_delegate streamExitRequested];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+(BOOL) hasKeyboardOrMouse {
|
||||
@@ -227,16 +390,13 @@ static const double MOUSE_SPEED_DIVISOR = 2.5;
|
||||
if (controller.extendedGamepad != NULL) {
|
||||
// Re-enable system gestures on the gamepad buttons now
|
||||
if (@available(iOS 14.0, tvOS 14.0, *)) {
|
||||
for (GCControllerElement* element in controller.extendedGamepad.allElements) {
|
||||
for (GCControllerElement* element in controller.physicalInputProfile.allElements) {
|
||||
element.preferredSystemGestureState = GCSystemGestureStateEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
controller.extendedGamepad.valueChangedHandler = NULL;
|
||||
}
|
||||
else if (controller.gamepad != NULL) {
|
||||
controller.gamepad.valueChangedHandler = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,12 +404,276 @@ static const double MOUSE_SPEED_DIVISOR = 2.5;
|
||||
{
|
||||
controller.lowFreqMotor = [HapticContext createContextForLowFreqMotor:controller.gamepad];
|
||||
controller.highFreqMotor = [HapticContext createContextForHighFreqMotor:controller.gamepad];
|
||||
controller.leftTriggerMotor = [HapticContext createContextForLeftTrigger:controller.gamepad];
|
||||
controller.rightTriggerMotor = [HapticContext createContextForRightTrigger:controller.gamepad];
|
||||
}
|
||||
|
||||
-(void) cleanupControllerHaptics:(Controller*) controller
|
||||
{
|
||||
[controller.lowFreqMotor cleanup];
|
||||
[controller.highFreqMotor cleanup];
|
||||
[controller.leftTriggerMotor cleanup];
|
||||
[controller.rightTriggerMotor cleanup];
|
||||
}
|
||||
|
||||
-(void) cleanupControllerMotion:(Controller*) controller
|
||||
{
|
||||
if (@available(iOS 14.0, tvOS 14.0, *)) {
|
||||
// Stop sensor sampling timers
|
||||
[controller.gyroTimer invalidate];
|
||||
[controller.accelTimer invalidate];
|
||||
|
||||
// Disable motion sensors if they require manual activation
|
||||
if (controller.gamepad && controller.gamepad.motion && controller.gamepad.motion.sensorsRequireManualActivation) {
|
||||
controller.gamepad.motion.sensorsActive = NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-(void) initializeControllerBattery:(Controller*) controller
|
||||
{
|
||||
if (@available(iOS 14.0, tvOS 14.0, *)) {
|
||||
if (controller.gamepad.battery) {
|
||||
// Poll for updated battery status every 30 seconds
|
||||
controller.batteryTimer = [NSTimer scheduledTimerWithTimeInterval:30 repeats:YES block:^(NSTimer *timer) {
|
||||
if (controller.lastBatteryState != controller.gamepad.battery.batteryState ||
|
||||
controller.lastBatteryLevel != controller.gamepad.battery.batteryLevel) {
|
||||
uint8_t batteryState;
|
||||
|
||||
switch (controller.gamepad.battery.batteryState) {
|
||||
case GCDeviceBatteryStateFull:
|
||||
batteryState = LI_BATTERY_STATE_FULL;
|
||||
break;
|
||||
case GCDeviceBatteryStateCharging:
|
||||
batteryState = LI_BATTERY_STATE_CHARGING;
|
||||
break;
|
||||
case GCDeviceBatteryStateDischarging:
|
||||
batteryState = LI_BATTERY_STATE_DISCHARGING;
|
||||
break;
|
||||
case GCDeviceBatteryStateUnknown:
|
||||
default:
|
||||
batteryState = LI_BATTERY_STATE_UNKNOWN;
|
||||
break;
|
||||
}
|
||||
|
||||
LiSendControllerBatteryEvent(controller.playerIndex, batteryState, (uint8_t)(controller.gamepad.battery.batteryLevel * 100));
|
||||
|
||||
controller.lastBatteryState = controller.gamepad.battery.batteryState;
|
||||
controller.lastBatteryLevel = controller.gamepad.battery.batteryLevel;
|
||||
}
|
||||
}];
|
||||
|
||||
// Fire the timer immediately to send the initial battery state
|
||||
[controller.batteryTimer fire];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-(void) cleanupControllerBattery:(Controller*) controller
|
||||
{
|
||||
if (@available(iOS 14.0, tvOS 14.0, *)) {
|
||||
[controller.batteryTimer invalidate];
|
||||
}
|
||||
}
|
||||
|
||||
-(BOOL) reportControllerArrival:(Controller*) limeController
|
||||
{
|
||||
// Only report arrival once
|
||||
if (limeController.reportedArrival) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
uint8_t type = LI_CTYPE_UNKNOWN;
|
||||
uint16_t capabilities = 0;
|
||||
uint32_t supportedButtonFlags = 0;
|
||||
|
||||
GCController *controller = limeController.gamepad;
|
||||
if (controller) {
|
||||
// This is a physical controller with a corresponding GCController object
|
||||
|
||||
// Start is always present
|
||||
supportedButtonFlags |= PLAY_FLAG;
|
||||
|
||||
// Detect buttons present in the GCExtendedGamepad profile
|
||||
if (controller.extendedGamepad.dpad) {
|
||||
supportedButtonFlags |= UP_FLAG | DOWN_FLAG | LEFT_FLAG | RIGHT_FLAG;
|
||||
}
|
||||
if (controller.extendedGamepad.leftShoulder) {
|
||||
supportedButtonFlags |= LB_FLAG;
|
||||
}
|
||||
if (controller.extendedGamepad.rightShoulder) {
|
||||
supportedButtonFlags |= RB_FLAG;
|
||||
}
|
||||
if (@available(iOS 13.0, tvOS 13.0, *)) {
|
||||
if (controller.extendedGamepad.buttonOptions) {
|
||||
supportedButtonFlags |= BACK_FLAG;
|
||||
}
|
||||
}
|
||||
if (@available(iOS 14.0, tvOS 14.0, *)) {
|
||||
if (controller.extendedGamepad.buttonHome) {
|
||||
supportedButtonFlags |= SPECIAL_FLAG;
|
||||
}
|
||||
}
|
||||
if (controller.extendedGamepad.buttonA) {
|
||||
supportedButtonFlags |= A_FLAG;
|
||||
}
|
||||
if (controller.extendedGamepad.buttonB) {
|
||||
supportedButtonFlags |= B_FLAG;
|
||||
}
|
||||
if (controller.extendedGamepad.buttonX) {
|
||||
supportedButtonFlags |= X_FLAG;
|
||||
}
|
||||
if (controller.extendedGamepad.buttonY) {
|
||||
supportedButtonFlags |= Y_FLAG;
|
||||
}
|
||||
if (@available(iOS 12.1, tvOS 12.1, *)) {
|
||||
if (controller.extendedGamepad.leftThumbstickButton) {
|
||||
supportedButtonFlags |= LS_CLK_FLAG;
|
||||
}
|
||||
if (controller.extendedGamepad.rightThumbstickButton) {
|
||||
supportedButtonFlags |= RS_CLK_FLAG;
|
||||
}
|
||||
}
|
||||
|
||||
if (@available(iOS 14.0, tvOS 14.0, *)) {
|
||||
// Xbox One/Series controller
|
||||
if (controller.physicalInputProfile.buttons[GCInputXboxPaddleOne]) {
|
||||
supportedButtonFlags |= PADDLE1_FLAG;
|
||||
}
|
||||
if (controller.physicalInputProfile.buttons[GCInputXboxPaddleTwo]) {
|
||||
supportedButtonFlags |= PADDLE2_FLAG;
|
||||
}
|
||||
if (controller.physicalInputProfile.buttons[GCInputXboxPaddleThree]) {
|
||||
supportedButtonFlags |= PADDLE3_FLAG;
|
||||
}
|
||||
if (controller.physicalInputProfile.buttons[GCInputXboxPaddleFour]) {
|
||||
supportedButtonFlags |= PADDLE4_FLAG;
|
||||
}
|
||||
if (@available(iOS 15.0, tvOS 15.0, *)) {
|
||||
if (controller.physicalInputProfile.buttons[GCInputButtonShare]) {
|
||||
supportedButtonFlags |= MISC_FLAG;
|
||||
}
|
||||
}
|
||||
|
||||
// DualShock/DualSense controller
|
||||
if (controller.physicalInputProfile.buttons[GCInputDualShockTouchpadButton]) {
|
||||
supportedButtonFlags |= TOUCHPAD_FLAG;
|
||||
}
|
||||
if (controller.physicalInputProfile.dpads[GCInputDualShockTouchpadOne]) {
|
||||
capabilities |= LI_CCAP_TOUCHPAD;
|
||||
}
|
||||
|
||||
if ([controller.extendedGamepad isKindOfClass:[GCXboxGamepad class]]) {
|
||||
type = LI_CTYPE_XBOX;
|
||||
}
|
||||
else if ([controller.extendedGamepad isKindOfClass:[GCDualShockGamepad class]]) {
|
||||
type = LI_CTYPE_PS;
|
||||
}
|
||||
|
||||
if (@available(iOS 14.5, tvOS 14.5, *)) {
|
||||
if ([controller.extendedGamepad isKindOfClass:[GCDualSenseGamepad class]]) {
|
||||
type = LI_CTYPE_PS;
|
||||
}
|
||||
}
|
||||
|
||||
// Detect supported haptics localities
|
||||
if (controller.haptics) {
|
||||
if ([controller.haptics.supportedLocalities containsObject:GCHapticsLocalityHandles]) {
|
||||
capabilities |= LI_CCAP_RUMBLE;
|
||||
}
|
||||
if ([controller.haptics.supportedLocalities containsObject:GCHapticsLocalityTriggers]) {
|
||||
capabilities |= LI_CCAP_TRIGGER_RUMBLE;
|
||||
}
|
||||
}
|
||||
|
||||
// Detect supported motion sensors
|
||||
if (controller.motion) {
|
||||
if (controller.motion.hasGravityAndUserAcceleration) {
|
||||
capabilities |= LI_CCAP_ACCEL;
|
||||
}
|
||||
if (controller.motion.hasRotationRate) {
|
||||
capabilities |= LI_CCAP_GYRO;
|
||||
}
|
||||
}
|
||||
|
||||
// Detect RGB LED support
|
||||
if (controller.light) {
|
||||
capabilities |= LI_CCAP_RGB_LED;
|
||||
}
|
||||
|
||||
// Detect battery support
|
||||
if (controller.battery) {
|
||||
capabilities |= LI_CCAP_BATTERY_STATE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// This is a virtual controller corresponding to our OSC
|
||||
|
||||
// TODO: Support various layouts and button labels on the OSC
|
||||
type = LI_CTYPE_XBOX;
|
||||
capabilities = 0;
|
||||
supportedButtonFlags =
|
||||
PLAY_FLAG | BACK_FLAG | UP_FLAG | DOWN_FLAG | LEFT_FLAG | RIGHT_FLAG |
|
||||
LB_FLAG | RB_FLAG | LS_CLK_FLAG | RS_CLK_FLAG | A_FLAG | B_FLAG | X_FLAG | Y_FLAG;
|
||||
}
|
||||
}
|
||||
|
||||
// Report the new controller to the host
|
||||
// NB: This will fail if the connection hasn't been fully established yet
|
||||
// and we will try again later.
|
||||
if (LiSendControllerArrivalEvent(controller.playerIndex,
|
||||
[self getActiveGamepadMask],
|
||||
type,
|
||||
supportedButtonFlags,
|
||||
capabilities) != 0) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Begin polling for battery status
|
||||
[self initializeControllerBattery:limeController];
|
||||
|
||||
// Remember that we've reported arrival already
|
||||
limeController.reportedArrival = YES;
|
||||
return YES;
|
||||
}
|
||||
|
||||
-(void) handleControllerTouchpad:(Controller*)controller touch:(GCControllerDirectionPad*)touch index:(int)index
|
||||
{
|
||||
controller_touch_context_t context = index == 0 ? controller.primaryTouch : controller.secondaryTouch;
|
||||
|
||||
// This magic is courtesy of SDL
|
||||
float normalizedX = (1.0f + touch.xAxis.value) * 0.5f;
|
||||
float normalizedY = 1.0f - (1.0f + touch.yAxis.value) * 0.5f;
|
||||
|
||||
// If we went from a touch to no touch, generate a touch up event
|
||||
if ((context.lastX || context.lastY) && (!touch.xAxis.value && !touch.yAxis.value)) {
|
||||
LiSendControllerTouchEvent(controller.playerIndex, LI_TOUCH_EVENT_UP, index, normalizedX, normalizedY, 1.0f);
|
||||
}
|
||||
else if (touch.xAxis.value || touch.yAxis.value) {
|
||||
// If we went from no touch to a touch, generate a touch down event
|
||||
if (!context.lastX && !context.lastY) {
|
||||
LiSendControllerTouchEvent(controller.playerIndex, LI_TOUCH_EVENT_DOWN, index, normalizedX, normalizedY, 1.0f);
|
||||
}
|
||||
else if (context.lastX != touch.xAxis.value || context.lastY != touch.yAxis.value) {
|
||||
// Otherwise it's just a move
|
||||
LiSendControllerTouchEvent(controller.playerIndex, LI_TOUCH_EVENT_MOVE, index, normalizedX, normalizedY, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
// We have to assign the whole struct because this is a property rather than a standard
|
||||
// field that we could modify through a pointer.
|
||||
if (index == 0) {
|
||||
controller.primaryTouch = (controller_touch_context_t) {
|
||||
touch.xAxis.value,
|
||||
touch.yAxis.value
|
||||
};
|
||||
}
|
||||
else {
|
||||
controller.secondaryTouch = (controller_touch_context_t) {
|
||||
touch.xAxis.value,
|
||||
touch.yAxis.value
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
-(void) registerControllerCallbacks:(GCController*) controller
|
||||
@@ -291,7 +715,7 @@ static const double MOUSE_SPEED_DIVISOR = 2.5;
|
||||
// Disable system gestures on the gamepad to avoid interfering
|
||||
// with in-game controller actions
|
||||
if (@available(iOS 14.0, tvOS 14.0, *)) {
|
||||
for (GCControllerElement* element in controller.extendedGamepad.allElements) {
|
||||
for (GCControllerElement* element in controller.physicalInputProfile.allElements) {
|
||||
element.preferredSystemGestureState = GCSystemGestureStateDisabled;
|
||||
}
|
||||
}
|
||||
@@ -302,10 +726,18 @@ static const double MOUSE_SPEED_DIVISOR = 2.5;
|
||||
short rightStickX, rightStickY;
|
||||
unsigned char leftTrigger, rightTrigger;
|
||||
|
||||
UPDATE_BUTTON_FLAG(limeController, A_FLAG, gamepad.buttonA.pressed);
|
||||
UPDATE_BUTTON_FLAG(limeController, B_FLAG, gamepad.buttonB.pressed);
|
||||
UPDATE_BUTTON_FLAG(limeController, X_FLAG, gamepad.buttonX.pressed);
|
||||
UPDATE_BUTTON_FLAG(limeController, Y_FLAG, gamepad.buttonY.pressed);
|
||||
if (self->_swapABXYButtons) {
|
||||
UPDATE_BUTTON_FLAG(limeController, B_FLAG, gamepad.buttonA.pressed);
|
||||
UPDATE_BUTTON_FLAG(limeController, A_FLAG, gamepad.buttonB.pressed);
|
||||
UPDATE_BUTTON_FLAG(limeController, Y_FLAG, gamepad.buttonX.pressed);
|
||||
UPDATE_BUTTON_FLAG(limeController, X_FLAG, gamepad.buttonY.pressed);
|
||||
}
|
||||
else {
|
||||
UPDATE_BUTTON_FLAG(limeController, A_FLAG, gamepad.buttonA.pressed);
|
||||
UPDATE_BUTTON_FLAG(limeController, B_FLAG, gamepad.buttonB.pressed);
|
||||
UPDATE_BUTTON_FLAG(limeController, X_FLAG, gamepad.buttonX.pressed);
|
||||
UPDATE_BUTTON_FLAG(limeController, Y_FLAG, gamepad.buttonY.pressed);
|
||||
}
|
||||
|
||||
UPDATE_BUTTON_FLAG(limeController, UP_FLAG, gamepad.dpad.up.pressed);
|
||||
UPDATE_BUTTON_FLAG(limeController, DOWN_FLAG, gamepad.dpad.down.pressed);
|
||||
@@ -341,6 +773,40 @@ static const double MOUSE_SPEED_DIVISOR = 2.5;
|
||||
if (gamepad.buttonHome != nil) {
|
||||
UPDATE_BUTTON_FLAG(limeController, SPECIAL_FLAG, gamepad.buttonHome.pressed);
|
||||
}
|
||||
|
||||
// Xbox One/Series controllers
|
||||
if (gamepad.controller.physicalInputProfile.buttons[GCInputXboxPaddleOne]) {
|
||||
UPDATE_BUTTON_FLAG(limeController, PADDLE1_FLAG, gamepad.controller.physicalInputProfile.buttons[GCInputXboxPaddleOne].pressed);
|
||||
}
|
||||
if (gamepad.controller.physicalInputProfile.buttons[GCInputXboxPaddleTwo]) {
|
||||
UPDATE_BUTTON_FLAG(limeController, PADDLE2_FLAG, gamepad.controller.physicalInputProfile.buttons[GCInputXboxPaddleTwo].pressed);
|
||||
}
|
||||
if (gamepad.controller.physicalInputProfile.buttons[GCInputXboxPaddleThree]) {
|
||||
UPDATE_BUTTON_FLAG(limeController, PADDLE3_FLAG, gamepad.controller.physicalInputProfile.buttons[GCInputXboxPaddleThree].pressed);
|
||||
}
|
||||
if (gamepad.controller.physicalInputProfile.buttons[GCInputXboxPaddleFour]) {
|
||||
UPDATE_BUTTON_FLAG(limeController, PADDLE4_FLAG, gamepad.controller.physicalInputProfile.buttons[GCInputXboxPaddleFour].pressed);
|
||||
}
|
||||
if (@available(iOS 15.0, tvOS 15.0, *)) {
|
||||
if (gamepad.controller.physicalInputProfile.buttons[GCInputButtonShare]) {
|
||||
UPDATE_BUTTON_FLAG(limeController, MISC_FLAG, gamepad.controller.physicalInputProfile.buttons[GCInputButtonShare].pressed);
|
||||
}
|
||||
}
|
||||
|
||||
// DualShock/DualSense controllers
|
||||
if (gamepad.controller.physicalInputProfile.buttons[GCInputDualShockTouchpadButton]) {
|
||||
UPDATE_BUTTON_FLAG(limeController, TOUCHPAD_FLAG, gamepad.controller.physicalInputProfile.buttons[GCInputDualShockTouchpadButton].pressed);
|
||||
}
|
||||
if (gamepad.controller.physicalInputProfile.dpads[GCInputDualShockTouchpadOne]) {
|
||||
[self handleControllerTouchpad:limeController
|
||||
touch:gamepad.controller.physicalInputProfile.dpads[GCInputDualShockTouchpadOne]
|
||||
index:0];
|
||||
}
|
||||
if (gamepad.controller.physicalInputProfile.dpads[GCInputDualShockTouchpadTwo]) {
|
||||
[self handleControllerTouchpad:limeController
|
||||
touch:gamepad.controller.physicalInputProfile.dpads[GCInputDualShockTouchpadTwo]
|
||||
index:1];
|
||||
}
|
||||
}
|
||||
|
||||
leftStickX = gamepad.leftThumbstick.xAxis.value * 0x7FFE;
|
||||
@@ -358,25 +824,6 @@ static const double MOUSE_SPEED_DIVISOR = 2.5;
|
||||
[self updateFinished:limeController];
|
||||
};
|
||||
}
|
||||
else if (controller.gamepad != NULL) {
|
||||
controller.gamepad.valueChangedHandler = ^(GCGamepad *gamepad, GCControllerElement *element) {
|
||||
Controller* limeController = [self->_controllers objectForKey:[NSNumber numberWithInteger:gamepad.controller.playerIndex]];
|
||||
UPDATE_BUTTON_FLAG(limeController, A_FLAG, gamepad.buttonA.pressed);
|
||||
UPDATE_BUTTON_FLAG(limeController, B_FLAG, gamepad.buttonB.pressed);
|
||||
UPDATE_BUTTON_FLAG(limeController, X_FLAG, gamepad.buttonX.pressed);
|
||||
UPDATE_BUTTON_FLAG(limeController, Y_FLAG, gamepad.buttonY.pressed);
|
||||
|
||||
UPDATE_BUTTON_FLAG(limeController, UP_FLAG, gamepad.dpad.up.pressed);
|
||||
UPDATE_BUTTON_FLAG(limeController, DOWN_FLAG, gamepad.dpad.down.pressed);
|
||||
UPDATE_BUTTON_FLAG(limeController, LEFT_FLAG, gamepad.dpad.left.pressed);
|
||||
UPDATE_BUTTON_FLAG(limeController, RIGHT_FLAG, gamepad.dpad.right.pressed);
|
||||
|
||||
UPDATE_BUTTON_FLAG(limeController, LB_FLAG, gamepad.leftShoulder.pressed);
|
||||
UPDATE_BUTTON_FLAG(limeController, RB_FLAG, gamepad.rightShoulder.pressed);
|
||||
|
||||
[self updateFinished:limeController];
|
||||
};
|
||||
}
|
||||
} else {
|
||||
Log(LOG_W, @"Tried to register controller callbacks on NULL controller");
|
||||
}
|
||||
@@ -392,6 +839,11 @@ static const double MOUSE_SPEED_DIVISOR = 2.5;
|
||||
for (GCControllerButtonInput* auxButton in mouse.mouseInput.auxiliaryButtons) {
|
||||
auxButton.pressedChangedHandler = nil;
|
||||
}
|
||||
|
||||
#if TARGET_OS_TV
|
||||
mouse.mouseInput.scroll.xAxis.valueChangedHandler = nil;
|
||||
mouse.mouseInput.scroll.yAxis.valueChangedHandler = nil;
|
||||
#endif
|
||||
}
|
||||
|
||||
-(void) registerMouseCallbacks:(GCMouse*) mouse API_AVAILABLE(ios(14.0)) {
|
||||
@@ -438,6 +890,18 @@ static const double MOUSE_SPEED_DIVISOR = 2.5;
|
||||
// in iPadOS 15 where discrete scroll events are dropped. tvOS only supports
|
||||
// GCMouse for mice, so we will have to just use it and hope for the best.
|
||||
#if TARGET_OS_TV
|
||||
mouse.mouseInput.scroll.xAxis.valueChangedHandler = ^(GCControllerAxisInput * _Nonnull axis, float value) {
|
||||
self->accumulatedScrollX += value;
|
||||
|
||||
short truncatedScrollX = (short)self->accumulatedScrollX;
|
||||
|
||||
if (truncatedScrollX != 0) {
|
||||
// Direction is reversed from vertical scrolling
|
||||
LiSendHighResHScrollEvent(-truncatedScrollX * 20);
|
||||
|
||||
self->accumulatedScrollX -= truncatedScrollX;
|
||||
}
|
||||
};
|
||||
mouse.mouseInput.scroll.yAxis.valueChangedHandler = ^(GCControllerAxisInput * _Nonnull axis, float value) {
|
||||
self->accumulatedScrollY += value;
|
||||
|
||||
@@ -483,10 +947,6 @@ static const double MOUSE_SPEED_DIVISOR = 2.5;
|
||||
}
|
||||
break;
|
||||
}
|
||||
else if (controller.gamepad != NULL) {
|
||||
level = OnScreenControlsLevelAutoGCGamepad;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -511,24 +971,20 @@ static const double MOUSE_SPEED_DIVISOR = 2.5;
|
||||
[self updateAutoOnScreenControlMode];
|
||||
}
|
||||
|
||||
-(void) assignController:(GCController*)controller {
|
||||
-(Controller*) assignController:(GCController*)controller {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (!(_controllerNumbers & (1 << i))) {
|
||||
_controllerNumbers |= (1 << i);
|
||||
controller.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;
|
||||
}
|
||||
|
||||
Controller* limeController = [[Controller alloc] init];
|
||||
limeController.playerIndex = i;
|
||||
limeController.supportedEmulationFlags = EMULATING_SPECIAL | EMULATING_SELECT;
|
||||
limeController.gamepad = controller;
|
||||
|
||||
// If this is player 0, it shares state with the OSC
|
||||
limeController.mergedWithController = _oscController;
|
||||
_oscController.mergedWithController = limeController;
|
||||
|
||||
if (@available(iOS 13.0, tvOS 13.0, *)) {
|
||||
if (controller.extendedGamepad != nil &&
|
||||
@@ -552,17 +1008,19 @@ static const double MOUSE_SPEED_DIVISOR = 2.5;
|
||||
[_controllers setObject:limeController forKey:[NSNumber numberWithInteger:controller.playerIndex]];
|
||||
|
||||
Log(LOG_I, @"Assigning controller index: %d", i);
|
||||
break;
|
||||
return limeController;
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
-(Controller*) getOscController {
|
||||
return _player0osc;
|
||||
return _oscController;
|
||||
}
|
||||
|
||||
+(bool) isSupportedGamepad:(GCController*) controller {
|
||||
return controller.extendedGamepad != nil || controller.gamepad != nil;
|
||||
return controller.extendedGamepad != nil;
|
||||
}
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
@@ -615,7 +1073,7 @@ static const double MOUSE_SPEED_DIVISOR = 2.5;
|
||||
return _controllers.count;
|
||||
}
|
||||
|
||||
-(id) initWithConfig:(StreamConfiguration*)streamConfig presenceDelegate:(id<InputPresenceDelegate>)delegate
|
||||
-(id) initWithConfig:(StreamConfiguration*)streamConfig delegate:(id<ControllerSupportDelegate>)delegate
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
@@ -623,10 +1081,11 @@ static const double MOUSE_SPEED_DIVISOR = 2.5;
|
||||
_controllers = [[NSMutableDictionary alloc] init];
|
||||
_controllerNumbers = 0;
|
||||
_multiController = streamConfig.multiController;
|
||||
_presenceDelegate = delegate;
|
||||
_swapABXYButtons = streamConfig.swapABXYButtons;
|
||||
_delegate = delegate;
|
||||
|
||||
_player0osc = [[Controller alloc] init];
|
||||
_player0osc.playerIndex = 0;
|
||||
_oscController = [[Controller alloc] init];
|
||||
_oscController.playerIndex = 0;
|
||||
|
||||
DataManager* dataMan = [[DataManager alloc] init];
|
||||
_oscEnabled = (OnScreenControlsLevel)[[dataMan getSettings].onscreenControls integerValue] != OnScreenControlsLevelOff;
|
||||
@@ -638,6 +1097,9 @@ static const double MOUSE_SPEED_DIVISOR = 2.5;
|
||||
if ([ControllerSupport isSupportedGamepad:controller]) {
|
||||
[self assignController:controller];
|
||||
[self registerControllerCallbacks:controller];
|
||||
|
||||
// Note: We cannot report controller arrival to the host here,
|
||||
// because the connection has not been established yet.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -657,16 +1119,20 @@ static const double MOUSE_SPEED_DIVISOR = 2.5;
|
||||
return;
|
||||
}
|
||||
|
||||
[self assignController:controller];
|
||||
|
||||
// Register callbacks on the new controller
|
||||
[self registerControllerCallbacks:controller];
|
||||
|
||||
// Re-evaluate the on-screen control mode
|
||||
[self updateAutoOnScreenControlMode];
|
||||
|
||||
// Notify the delegate
|
||||
[self->_presenceDelegate gamepadPresenceChanged];
|
||||
Controller* limeController = [self assignController:controller];
|
||||
if (limeController) {
|
||||
// Register callbacks on the new controller
|
||||
[self registerControllerCallbacks:controller];
|
||||
|
||||
// Report the controller arrival to the host if we're connected
|
||||
[self reportControllerArrival:limeController];
|
||||
|
||||
// Re-evaluate the on-screen control mode
|
||||
[self updateAutoOnScreenControlMode];
|
||||
|
||||
// Notify the delegate
|
||||
[self->_delegate gamepadPresenceChanged];
|
||||
}
|
||||
}];
|
||||
_controllerDisconnectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCControllerDidDisconnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
Log(LOG_I, @"Controller disconnected!");
|
||||
@@ -682,23 +1148,33 @@ static const double MOUSE_SPEED_DIVISOR = 2.5;
|
||||
self->_controllerNumbers &= ~(1 << 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]];
|
||||
|
||||
// Stop haptics on this controller
|
||||
[self cleanupControllerHaptics:limeController];
|
||||
|
||||
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
|
||||
[self updateAutoOnScreenControlMode];
|
||||
|
||||
// Notify the delegate
|
||||
[self->_presenceDelegate gamepadPresenceChanged];
|
||||
if (limeController) {
|
||||
// Stop haptics on this controller
|
||||
[self cleanupControllerHaptics:limeController];
|
||||
|
||||
// Stop motion reports on this controller
|
||||
[self cleanupControllerMotion:limeController];
|
||||
|
||||
// Stop battery reports on this controller
|
||||
[self cleanupControllerBattery:limeController];
|
||||
|
||||
// Disassociate this controller from any controllers merged with it
|
||||
if (limeController.mergedWithController) {
|
||||
assert(limeController.mergedWithController.mergedWithController == limeController);
|
||||
limeController.mergedWithController.mergedWithController = 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
|
||||
[self updateAutoOnScreenControlMode];
|
||||
|
||||
// Notify the delegate
|
||||
[self->_delegate gamepadPresenceChanged];
|
||||
}
|
||||
}];
|
||||
|
||||
if (@available(iOS 14.0, tvOS 14.0, *)) {
|
||||
@@ -714,7 +1190,7 @@ static const double MOUSE_SPEED_DIVISOR = 2.5;
|
||||
[self updateAutoOnScreenControlMode];
|
||||
|
||||
// Notify the delegate
|
||||
[self->_presenceDelegate mousePresenceChanged];
|
||||
[self->_delegate mousePresenceChanged];
|
||||
}];
|
||||
_mouseDisconnectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCMouseDidDisconnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
Log(LOG_I, @"Mouse disconnected!");
|
||||
@@ -728,7 +1204,7 @@ static const double MOUSE_SPEED_DIVISOR = 2.5;
|
||||
[self updateAutoOnScreenControlMode];
|
||||
|
||||
// Notify the delegate
|
||||
[self->_presenceDelegate mousePresenceChanged];
|
||||
[self->_delegate mousePresenceChanged];
|
||||
}];
|
||||
_keyboardConnectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCKeyboardDidConnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
Log(LOG_I, @"Keyboard connected!");
|
||||
@@ -747,6 +1223,14 @@ static const double MOUSE_SPEED_DIVISOR = 2.5;
|
||||
return self;
|
||||
}
|
||||
|
||||
-(void) connectionEstablished
|
||||
{
|
||||
for (Controller* controller in [_controllers allValues]) {
|
||||
// Report the controller arrival to the host if we haven't done so yet
|
||||
[self reportControllerArrival:controller];
|
||||
}
|
||||
}
|
||||
|
||||
-(void) cleanup
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:_controllerConnectObserver];
|
||||
@@ -767,6 +1251,8 @@ static const double MOUSE_SPEED_DIVISOR = 2.5;
|
||||
|
||||
for (Controller* controller in [_controllers allValues]) {
|
||||
[self cleanupControllerHaptics:controller];
|
||||
[self cleanupControllerMotion:controller];
|
||||
[self cleanupControllerBattery:controller];
|
||||
}
|
||||
[_controllers removeAllObjects];
|
||||
|
||||
|
||||
@@ -16,5 +16,7 @@
|
||||
|
||||
+(HapticContext*) createContextForHighFreqMotor:(GCController*)gamepad;
|
||||
+(HapticContext*) createContextForLowFreqMotor:(GCController*)gamepad;
|
||||
+(HapticContext*) createContextForLeftTrigger:(GCController*)gamepad;
|
||||
+(HapticContext*) createContextForRightTrigger:(GCController*)gamepad;
|
||||
|
||||
@end
|
||||
|
||||
@@ -89,6 +89,11 @@
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (![[gamepad.haptics supportedLocalities] containsObject:locality]) {
|
||||
Log(LOG_W, @"Controller %d does not support haptic locality: %@", gamepad.playerIndex, locality);
|
||||
return nil;
|
||||
}
|
||||
|
||||
_playerIndex = gamepad.playerIndex;
|
||||
_hapticEngine = [gamepad.haptics createEngineWithLocality:locality];
|
||||
|
||||
@@ -144,4 +149,22 @@
|
||||
}
|
||||
}
|
||||
|
||||
+(HapticContext*) createContextForLeftTrigger:(GCController*)gamepad {
|
||||
if (@available(iOS 14.0, tvOS 14.0, *)) {
|
||||
return [[HapticContext alloc] initWithGamepad:gamepad locality:GCHapticsLocalityLeftTrigger];
|
||||
}
|
||||
else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
+(HapticContext*) createContextForRightTrigger:(GCController*)gamepad {
|
||||
if (@available(iOS 14.0, tvOS 14.0, *)) {
|
||||
return [[HapticContext alloc] initWithGamepad:gamepad locality:GCHapticsLocalityRightTrigger];
|
||||
}
|
||||
else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// KeyboardInputField.h
|
||||
// Moonlight
|
||||
//
|
||||
// Created by Cameron Gutman on 12/2/22.
|
||||
// Copyright © 2022 Moonlight Game Streaming Project. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface KeyboardInputField : UITextField
|
||||
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// KeyboardInputField.m
|
||||
// Moonlight
|
||||
//
|
||||
// Created by Cameron Gutman on 12/2/22.
|
||||
// Copyright © 2022 Moonlight Game Streaming Project. All rights reserved.
|
||||
//
|
||||
|
||||
#import "KeyboardInputField.h"
|
||||
|
||||
@implementation KeyboardInputField
|
||||
|
||||
- (UIEditingInteractionConfiguration) editingInteractionConfiguration {
|
||||
// Suppress the Undo menu that appears with a 3 finger tap
|
||||
return UIEditingInteractionConfigurationNone;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -16,6 +16,7 @@ struct KeyEvent {
|
||||
u_char modifier;
|
||||
};
|
||||
|
||||
+ (BOOL)sendKeyEventForPress:(UIPress*)press down:(BOOL)down API_AVAILABLE(ios(13.4));
|
||||
+ (BOOL)sendKeyEvent:(UIKey*)key down:(BOOL)down API_AVAILABLE(ios(13.4));
|
||||
+ (struct KeyEvent) translateKeyEvent:(unichar) inputChar withModifierFlags:(UIKeyModifierFlags)modifierFlags;
|
||||
|
||||
|
||||
@@ -11,7 +11,40 @@
|
||||
|
||||
@implementation KeyboardSupport
|
||||
|
||||
+ (BOOL)sendKeyEvent:(UIKey*)key down:(BOOL)down API_AVAILABLE(ios(13.4)){
|
||||
+ (BOOL)sendKeyEventForPress:(UIPress*)press down:(BOOL)down API_AVAILABLE(ios(13.4)) {
|
||||
if (press.key != nil) {
|
||||
return [KeyboardSupport sendKeyEvent:press.key down:down];
|
||||
}
|
||||
else {
|
||||
short keyCode;
|
||||
|
||||
switch (press.type) {
|
||||
case UIPressTypeUpArrow:
|
||||
keyCode = 0x26;
|
||||
break;
|
||||
case UIPressTypeDownArrow:
|
||||
keyCode = 0x28;
|
||||
break;
|
||||
case UIPressTypeLeftArrow:
|
||||
keyCode = 0x25;
|
||||
break;
|
||||
case UIPressTypeRightArrow:
|
||||
keyCode = 0x27;
|
||||
break;
|
||||
default:
|
||||
// Unhandled press type
|
||||
return NO;
|
||||
}
|
||||
|
||||
LiSendKeyboardEvent(0x8000 | keyCode,
|
||||
down ? KEY_ACTION_DOWN : KEY_ACTION_UP,
|
||||
0);
|
||||
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
+ (BOOL)sendKeyEvent:(UIKey*)key down:(BOOL)down API_AVAILABLE(ios(13.4)) {
|
||||
char modifierFlags = 0;
|
||||
short keyCode = 0;
|
||||
|
||||
@@ -168,7 +201,8 @@
|
||||
keyCode = 0x6B;
|
||||
break;
|
||||
case UIKeyboardHIDUsageKeypadEnter:
|
||||
keyCode = 0x0D; // FIXME: Is this correct?
|
||||
keyCode = 0x0D;
|
||||
modifierFlags |= MODIFIER_EXTENDED;
|
||||
break;
|
||||
case UIKeyboardHIDUsageKeypadPeriod:
|
||||
keyCode = 0x6E;
|
||||
@@ -215,6 +249,9 @@
|
||||
case UIKeyboardHIDUsageKeyboardRightAlt:
|
||||
keyCode = 0xA5;
|
||||
break;
|
||||
case 669: // This value corresponds to the "Globe" or "Language" key on most Apple branded iPad keyboards.
|
||||
keyCode = 0x1B; // This value corresponds to "Escape", which is missing from most Apple branded iPad keyboards.
|
||||
break;
|
||||
default:
|
||||
NSLog(@"Unhandled HID usage: %lu", (unsigned long)key.keyCode);
|
||||
assert(0);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class ControllerSupport;
|
||||
@class StreamConfiguration;
|
||||
|
||||
@interface OnScreenControls : NSObject
|
||||
|
||||
@@ -24,7 +25,7 @@ typedef NS_ENUM(NSInteger, OnScreenControlsLevel) {
|
||||
OnScreenControlsLevelAutoGCExtendedGamepadWithStickButtons
|
||||
};
|
||||
|
||||
- (id) initWithView:(UIView*)view controllerSup:(ControllerSupport*)controllerSupport;
|
||||
- (id) initWithView:(UIView*)view controllerSup:(ControllerSupport*)controllerSupport streamConfig:(StreamConfiguration*)streamConfig;
|
||||
- (BOOL) handleTouchDownEvent:(NSSet*)touches;
|
||||
- (BOOL) handleTouchUpEvent:(NSSet*)touches;
|
||||
- (BOOL) handleTouchMovedEvent:(NSSet*)touches;
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
ControllerSupport *_controllerSupport;
|
||||
Controller *_controller;
|
||||
NSMutableArray* _deadTouches;
|
||||
BOOL _swapABXY;
|
||||
}
|
||||
|
||||
static const float EDGE_WIDTH = .05;
|
||||
@@ -111,12 +112,13 @@ static float L2_Y;
|
||||
static float L3_X;
|
||||
static float L3_Y;
|
||||
|
||||
- (id) initWithView:(UIView*)view controllerSup:(ControllerSupport*)controllerSupport {
|
||||
- (id) initWithView:(UIView*)view controllerSup:(ControllerSupport*)controllerSupport streamConfig:(StreamConfiguration*)streamConfig {
|
||||
self = [self init];
|
||||
_view = view;
|
||||
_controllerSupport = controllerSupport;
|
||||
_controller = [controllerSupport getOscController];
|
||||
_deadTouches = [[NSMutableArray alloc] init];
|
||||
_swapABXY = streamConfig.swapABXYButtons;
|
||||
|
||||
_iPad = ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad);
|
||||
_controlArea = CGRectMake(0, 0, _view.frame.size.width, _view.frame.size.height);
|
||||
@@ -372,27 +374,33 @@ static float L3_Y;
|
||||
}
|
||||
|
||||
- (void) drawButtons {
|
||||
// create A button
|
||||
UIImage* aButtonImage = [UIImage imageNamed:@"AButton"];
|
||||
UIImage* bButtonImage = [UIImage imageNamed:@"BButton"];
|
||||
UIImage* xButtonImage = [UIImage imageNamed:@"XButton"];
|
||||
UIImage* yButtonImage = [UIImage imageNamed:@"YButton"];
|
||||
|
||||
CGRect aButtonFrame = CGRectMake(BUTTON_CENTER_X - aButtonImage.size.width / 2, BUTTON_CENTER_Y + BUTTON_DIST, aButtonImage.size.width, aButtonImage.size.height);
|
||||
CGRect bButtonFrame = CGRectMake(BUTTON_CENTER_X + BUTTON_DIST, BUTTON_CENTER_Y - bButtonImage.size.height / 2, bButtonImage.size.width, bButtonImage.size.height);
|
||||
CGRect xButtonFrame = CGRectMake(BUTTON_CENTER_X - BUTTON_DIST - xButtonImage.size.width, BUTTON_CENTER_Y - xButtonImage.size.height/ 2, xButtonImage.size.width, xButtonImage.size.height);
|
||||
CGRect yButtonFrame = CGRectMake(BUTTON_CENTER_X - yButtonImage.size.width / 2, BUTTON_CENTER_Y - BUTTON_DIST - yButtonImage.size.height, yButtonImage.size.width, yButtonImage.size.height);
|
||||
|
||||
// create A button
|
||||
_aButton.contents = (id) aButtonImage.CGImage;
|
||||
_aButton.frame = CGRectMake(BUTTON_CENTER_X - aButtonImage.size.width / 2, BUTTON_CENTER_Y + BUTTON_DIST, aButtonImage.size.width, aButtonImage.size.height);
|
||||
_aButton.frame = _swapABXY ? bButtonFrame : aButtonFrame;
|
||||
[_view.layer addSublayer:_aButton];
|
||||
|
||||
// create B button
|
||||
UIImage* bButtonImage = [UIImage imageNamed:@"BButton"];
|
||||
_bButton.frame = CGRectMake(BUTTON_CENTER_X + BUTTON_DIST, BUTTON_CENTER_Y - bButtonImage.size.height / 2, bButtonImage.size.width, bButtonImage.size.height);
|
||||
_bButton.frame = _swapABXY ? aButtonFrame : bButtonFrame;
|
||||
_bButton.contents = (id) bButtonImage.CGImage;
|
||||
[_view.layer addSublayer:_bButton];
|
||||
|
||||
// create X Button
|
||||
UIImage* xButtonImage = [UIImage imageNamed:@"XButton"];
|
||||
_xButton.frame = CGRectMake(BUTTON_CENTER_X - BUTTON_DIST - xButtonImage.size.width, BUTTON_CENTER_Y - xButtonImage.size.height/ 2, xButtonImage.size.width, xButtonImage.size.height);
|
||||
_xButton.frame = _swapABXY ? yButtonFrame : xButtonFrame;
|
||||
_xButton.contents = (id) xButtonImage.CGImage;
|
||||
[_view.layer addSublayer:_xButton];
|
||||
|
||||
// create Y Button
|
||||
UIImage* yButtonImage = [UIImage imageNamed:@"YButton"];
|
||||
_yButton.frame = CGRectMake(BUTTON_CENTER_X - yButtonImage.size.width / 2, BUTTON_CENTER_Y - BUTTON_DIST - yButtonImage.size.height, yButtonImage.size.width, yButtonImage.size.height);
|
||||
_yButton.frame = _swapABXY ? xButtonFrame : yButtonFrame;
|
||||
_yButton.contents = (id) yButtonImage.CGImage;
|
||||
[_view.layer addSublayer:_yButton];
|
||||
|
||||
@@ -655,63 +663,63 @@ static float L3_Y;
|
||||
for (UITouch* touch in touches) {
|
||||
CGPoint touchLocation = [touch locationInView:_view];
|
||||
|
||||
if ([_aButton.presentationLayer hitTest:touchLocation]) {
|
||||
if (_aButton.superlayer != nil && [_aButton.presentationLayer hitTest:touchLocation]) {
|
||||
[_controllerSupport setButtonFlag:_controller flags:A_FLAG];
|
||||
_aTouch = touch;
|
||||
updated = true;
|
||||
} else if ([_bButton.presentationLayer hitTest:touchLocation]) {
|
||||
} else if (_bButton.superlayer != nil && [_bButton.presentationLayer hitTest:touchLocation]) {
|
||||
[_controllerSupport setButtonFlag:_controller flags:B_FLAG];
|
||||
_bTouch = touch;
|
||||
updated = true;
|
||||
} else if ([_xButton.presentationLayer hitTest:touchLocation]) {
|
||||
} else if (_xButton.superlayer != nil && [_xButton.presentationLayer hitTest:touchLocation]) {
|
||||
[_controllerSupport setButtonFlag:_controller flags:X_FLAG];
|
||||
_xTouch = touch;
|
||||
updated = true;
|
||||
} else if ([_yButton.presentationLayer hitTest:touchLocation]) {
|
||||
} else if (_yButton.superlayer != nil && [_yButton.presentationLayer hitTest:touchLocation]) {
|
||||
[_controllerSupport setButtonFlag:_controller flags:Y_FLAG];
|
||||
_yTouch = touch;
|
||||
updated = true;
|
||||
} else if ([_upButton.presentationLayer hitTest:touchLocation]) {
|
||||
} else if (_upButton.superlayer != nil && [_upButton.presentationLayer hitTest:touchLocation]) {
|
||||
[_controllerSupport setButtonFlag:_controller flags:UP_FLAG];
|
||||
_dpadTouch = touch;
|
||||
updated = true;
|
||||
} else if ([_downButton.presentationLayer hitTest:touchLocation]) {
|
||||
} else if (_downButton.superlayer != nil && [_downButton.presentationLayer hitTest:touchLocation]) {
|
||||
[_controllerSupport setButtonFlag:_controller flags:DOWN_FLAG];
|
||||
_dpadTouch = touch;
|
||||
updated = true;
|
||||
} else if ([_leftButton.presentationLayer hitTest:touchLocation]) {
|
||||
} else if (_leftButton.superlayer != nil && [_leftButton.presentationLayer hitTest:touchLocation]) {
|
||||
[_controllerSupport setButtonFlag:_controller flags:LEFT_FLAG];
|
||||
_dpadTouch = touch;
|
||||
updated = true;
|
||||
} else if ([_rightButton.presentationLayer hitTest:touchLocation]) {
|
||||
} else if (_rightButton.superlayer != nil && [_rightButton.presentationLayer hitTest:touchLocation]) {
|
||||
[_controllerSupport setButtonFlag:_controller flags:RIGHT_FLAG];
|
||||
_dpadTouch = touch;
|
||||
updated = true;
|
||||
} else if ([_startButton.presentationLayer hitTest:touchLocation]) {
|
||||
} else if (_startButton.superlayer != nil && [_startButton.presentationLayer hitTest:touchLocation]) {
|
||||
[_controllerSupport setButtonFlag:_controller flags:PLAY_FLAG];
|
||||
_startTouch = touch;
|
||||
updated = true;
|
||||
} else if ([_selectButton.presentationLayer hitTest:touchLocation]) {
|
||||
} else if (_selectButton.superlayer != nil && [_selectButton.presentationLayer hitTest:touchLocation]) {
|
||||
[_controllerSupport setButtonFlag:_controller flags:BACK_FLAG];
|
||||
_selectTouch = touch;
|
||||
updated = true;
|
||||
} else if ([_l1Button.presentationLayer hitTest:touchLocation]) {
|
||||
} else if (_l1Button.superlayer != nil && [_l1Button.presentationLayer hitTest:touchLocation]) {
|
||||
[_controllerSupport setButtonFlag:_controller flags:LB_FLAG];
|
||||
_l1Touch = touch;
|
||||
updated = true;
|
||||
} else if ([_r1Button.presentationLayer hitTest:touchLocation]) {
|
||||
} else if (_r1Button.superlayer != nil && [_r1Button.presentationLayer hitTest:touchLocation]) {
|
||||
[_controllerSupport setButtonFlag:_controller flags:RB_FLAG];
|
||||
_r1Touch = touch;
|
||||
updated = true;
|
||||
} else if ([_l2Button.presentationLayer hitTest:touchLocation]) {
|
||||
} else if (_l2Button.superlayer != nil && [_l2Button.presentationLayer hitTest:touchLocation]) {
|
||||
[_controllerSupport updateLeftTrigger:_controller left:0xFF];
|
||||
_l2Touch = touch;
|
||||
updated = true;
|
||||
} else if ([_r2Button.presentationLayer hitTest:touchLocation]) {
|
||||
} else if (_r2Button.superlayer != nil && [_r2Button.presentationLayer hitTest:touchLocation]) {
|
||||
[_controllerSupport updateRightTrigger:_controller right:0xFF];
|
||||
_r2Touch = touch;
|
||||
updated = true;
|
||||
} else if ([_l3Button.presentationLayer hitTest:touchLocation]) {
|
||||
} else if (_l3Button.superlayer != nil && [_l3Button.presentationLayer hitTest:touchLocation]) {
|
||||
if (l3Set) {
|
||||
[_controllerSupport clearButtonFlag:_controller flags:LS_CLK_FLAG];
|
||||
_l3Button.borderWidth = 0.0f;
|
||||
@@ -722,7 +730,7 @@ static float L3_Y;
|
||||
l3Set = !l3Set;
|
||||
_l3Touch = touch;
|
||||
updated = true;
|
||||
} else if ([_r3Button.presentationLayer hitTest:touchLocation]) {
|
||||
} else if (_r3Button.superlayer != nil && [_r3Button.presentationLayer hitTest:touchLocation]) {
|
||||
if (r3Set) {
|
||||
[_controllerSupport clearButtonFlag:_controller flags:RS_CLK_FLAG];
|
||||
_r3Button.borderWidth = 0.0f;
|
||||
@@ -733,7 +741,7 @@ static float L3_Y;
|
||||
r3Set = !r3Set;
|
||||
_r3Touch = touch;
|
||||
updated = true;
|
||||
} else if ([_leftStick.presentationLayer hitTest:touchLocation]) {
|
||||
} else if (_leftStick.superlayer != nil && [_leftStick.presentationLayer hitTest:touchLocation]) {
|
||||
if (l3TouchStart != nil) {
|
||||
// Find elapsed time and convert to milliseconds
|
||||
// Use (-) modifier to conversion since receiver is earlier than now
|
||||
@@ -745,7 +753,7 @@ static float L3_Y;
|
||||
}
|
||||
_lsTouch = touch;
|
||||
stickTouch = true;
|
||||
} else if ([_rightStick.presentationLayer hitTest:touchLocation]) {
|
||||
} else if (_rightStick.superlayer != nil && [_rightStick.presentationLayer hitTest:touchLocation]) {
|
||||
if (r3TouchStart != nil) {
|
||||
// Find elapsed time and convert to milliseconds
|
||||
// Use (-) modifier to conversion since receiver is earlier than now
|
||||
|
||||
@@ -18,6 +18,7 @@ static const int REFERENCE_HEIGHT = 720;
|
||||
BOOL touchMoved;
|
||||
BOOL isDragging;
|
||||
NSTimer* dragTimer;
|
||||
NSUInteger peakTouchCount;
|
||||
|
||||
#if TARGET_OS_TV
|
||||
UIGestureRecognizer* remotePressRecognizer;
|
||||
@@ -59,6 +60,7 @@ static const int REFERENCE_HEIGHT = 720;
|
||||
|
||||
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
|
||||
touchMoved = false;
|
||||
peakTouchCount = [[event allTouches] count];
|
||||
if ([[event allTouches] count] == 1) {
|
||||
UITouch *touch = [[event allTouches] anyObject];
|
||||
originalLocation = touchLocation = [touch locationInView:view];
|
||||
@@ -126,7 +128,7 @@ static const int REFERENCE_HEIGHT = 720;
|
||||
isDragging = false;
|
||||
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT);
|
||||
} else if (!touchMoved) {
|
||||
if ([[event allTouches] count] == 2) {
|
||||
if (peakTouchCount == 2) {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
|
||||
Log(LOG_D, @"Sending right mouse button press");
|
||||
|
||||
@@ -137,7 +139,7 @@ static const int REFERENCE_HEIGHT = 720;
|
||||
|
||||
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_RIGHT);
|
||||
});
|
||||
} else if ([[event allTouches] count] == 1) {
|
||||
} else if (peakTouchCount == 1) {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
|
||||
if (!self->isDragging){
|
||||
Log(LOG_D, @"Sending left mouse button press");
|
||||
@@ -175,6 +177,7 @@ static const int REFERENCE_HEIGHT = 720;
|
||||
isDragging = false;
|
||||
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT);
|
||||
}
|
||||
peakTouchCount = 0;
|
||||
}
|
||||
|
||||
#if TARGET_OS_TV
|
||||
|
||||
@@ -13,14 +13,16 @@
|
||||
#import "KeyboardSupport.h"
|
||||
#import "RelativeTouchHandler.h"
|
||||
#import "AbsoluteTouchHandler.h"
|
||||
#import "KeyboardInputField.h"
|
||||
|
||||
static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
|
||||
|
||||
@implementation StreamView {
|
||||
OnScreenControls* onScreenControls;
|
||||
|
||||
UITextField* keyInputField;
|
||||
KeyboardInputField* keyInputField;
|
||||
BOOL isInputingText;
|
||||
NSMutableSet* keysDown;
|
||||
|
||||
float streamAspectRatio;
|
||||
|
||||
@@ -52,7 +54,8 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
|
||||
|
||||
TemporarySettings* settings = [[[DataManager alloc] init] getSettings];
|
||||
|
||||
keyInputField = [[UITextField alloc] initWithFrame:CGRectZero];
|
||||
keysDown = [[NSMutableSet alloc] init];
|
||||
keyInputField = [[KeyboardInputField alloc] initWithFrame:CGRectZero];
|
||||
[keyInputField setKeyboardType:UIKeyboardTypeDefault];
|
||||
[keyInputField setAutocorrectionType:UITextAutocorrectionTypeNo];
|
||||
[keyInputField setAutocapitalizationType:UITextAutocapitalizationTypeNone];
|
||||
@@ -71,7 +74,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
|
||||
self->touchHandler = [[RelativeTouchHandler alloc] initWithView:self];
|
||||
}
|
||||
|
||||
onScreenControls = [[OnScreenControls alloc] initWithView:self controllerSup:controllerSupport];
|
||||
onScreenControls = [[OnScreenControls alloc] initWithView:self controllerSup:controllerSupport streamConfig:streamConfig];
|
||||
OnScreenControlsLevel level = (OnScreenControlsLevel)[settings.onscreenControls integerValue];
|
||||
if (settings.absoluteTouchMode) {
|
||||
Log(LOG_I, @"On-screen controls disabled in absolute touch mode");
|
||||
@@ -104,6 +107,14 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
|
||||
continuousMouseWheelRecognizer.allowedTouchTypes = @[@(UITouchTypeIndirectPointer)];
|
||||
[self addGestureRecognizer:continuousMouseWheelRecognizer];
|
||||
}
|
||||
|
||||
#if defined(__IPHONE_16_1) || defined(__TVOS_16_1)
|
||||
if (@available(iOS 16.1, *)) {
|
||||
UIHoverGestureRecognizer *stylusHoverRecognizer = [[UIHoverGestureRecognizer alloc] initWithTarget:self action:@selector(sendStylusHoverEvent:)];
|
||||
stylusHoverRecognizer.allowedTouchTypes = @[@(UITouchTypePencil)];
|
||||
[self addGestureRecognizer:stylusHoverRecognizer];
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
x1mouse = [[X1Mouse alloc] init];
|
||||
@@ -165,6 +176,148 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
|
||||
}
|
||||
}
|
||||
|
||||
- (CGSize) getVideoAreaSize {
|
||||
if (self.bounds.size.width > self.bounds.size.height * streamAspectRatio) {
|
||||
return CGSizeMake(self.bounds.size.height * streamAspectRatio, self.bounds.size.height);
|
||||
} else {
|
||||
return CGSizeMake(self.bounds.size.width, self.bounds.size.width / streamAspectRatio);
|
||||
}
|
||||
}
|
||||
|
||||
- (CGPoint) adjustCoordinatesForVideoArea:(CGPoint)point {
|
||||
// These are now relative to the StreamView, however we need to scale them
|
||||
// further to make them relative to the actual video portion.
|
||||
float x = point.x - self.bounds.origin.x;
|
||||
float y = point.y - self.bounds.origin.y;
|
||||
|
||||
// For some reason, we don't seem to always get to the bounds of the window
|
||||
// so we'll subtract 1 pixel if we're to the left/below of the origin and
|
||||
// and add 1 pixel if we're to the right/above. It should be imperceptible
|
||||
// to the user but it will allow activation of gestures that require contact
|
||||
// with the edge of the screen (like Aero Snap).
|
||||
if (x < self.bounds.size.width / 2) {
|
||||
x--;
|
||||
}
|
||||
else {
|
||||
x++;
|
||||
}
|
||||
if (y < self.bounds.size.height / 2) {
|
||||
y--;
|
||||
}
|
||||
else {
|
||||
y++;
|
||||
}
|
||||
|
||||
// This logic mimics what iOS does with AVLayerVideoGravityResizeAspect
|
||||
CGSize videoSize = [self getVideoAreaSize];
|
||||
CGPoint videoOrigin = CGPointMake(self.bounds.size.width / 2 - videoSize.width / 2,
|
||||
self.bounds.size.height / 2 - videoSize.height / 2);
|
||||
|
||||
// Confine the cursor to the video region. We don't just discard events outside
|
||||
// the region because we won't always get one exactly when the mouse leaves the region.
|
||||
return CGPointMake(MIN(MAX(x, videoOrigin.x), videoOrigin.x + videoSize.width) - videoOrigin.x,
|
||||
MIN(MAX(y, videoOrigin.y), videoOrigin.y + videoSize.height) - videoOrigin.y);
|
||||
}
|
||||
|
||||
#if !TARGET_OS_TV
|
||||
|
||||
- (uint16_t)getRotationFromAzimuthAngle:(float)azimuthAngle {
|
||||
// iOS reports azimuth of 0 when the stylus is pointing west, but Moonlight expects
|
||||
// rotation of 0 to mean the stylus is pointing north. Rotate the azimuth angle
|
||||
// clockwise by 90 degrees to convert from iOS to Moonlight rotation conventions.
|
||||
int32_t rotationAngle = (azimuthAngle - M_PI_2) * (180.f / M_PI);
|
||||
if (rotationAngle < 0) {
|
||||
rotationAngle += 360;
|
||||
}
|
||||
return (uint16_t)rotationAngle;
|
||||
}
|
||||
|
||||
- (uint8_t)getTiltFromAltitudeAngle:(float)altitudeAngle {
|
||||
// iOS reports an altitude of 0 when the stylus is parallel to the touch surface,
|
||||
// while Moonlight expects a tilt of 0 when the stylus is perpendicular to the surface.
|
||||
// Subtract the tilt angle from 90 to convert from iOS to Moonlight tilt conventions.
|
||||
uint8_t altitudeDegs = abs((int16_t)(altitudeAngle * (180.f / M_PI)));
|
||||
return 90 - MIN(90, altitudeDegs);
|
||||
}
|
||||
|
||||
- (BOOL)sendStylusEvent:(UITouch*)event {
|
||||
uint8_t type;
|
||||
|
||||
// Don't touch stylus events if the host doesn't support them. We want to pass
|
||||
// them as normal touches for legacy hosts that don't understand pen events.
|
||||
if (!(LiGetHostFeatureFlags() & LI_FF_PEN_TOUCH_EVENTS)) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
switch (event.phase) {
|
||||
case UITouchPhaseBegan:
|
||||
type = LI_TOUCH_EVENT_DOWN;
|
||||
break;
|
||||
case UITouchPhaseMoved:
|
||||
type = LI_TOUCH_EVENT_MOVE;
|
||||
break;
|
||||
case UITouchPhaseEnded:
|
||||
type = LI_TOUCH_EVENT_UP;
|
||||
break;
|
||||
case UITouchPhaseCancelled:
|
||||
type = LI_TOUCH_EVENT_CANCEL;
|
||||
break;
|
||||
default:
|
||||
return YES;
|
||||
}
|
||||
|
||||
CGPoint location = [self adjustCoordinatesForVideoArea:[event locationInView:self]];
|
||||
CGSize videoSize = [self getVideoAreaSize];
|
||||
|
||||
return LiSendPenEvent(type, LI_TOOL_TYPE_PEN, 0, location.x / videoSize.width, location.y / videoSize.height,
|
||||
(event.force / event.maximumPossibleForce) / sin(event.altitudeAngle),
|
||||
0.0f, 0.0f,
|
||||
[self getRotationFromAzimuthAngle:[event azimuthAngleInView:self]],
|
||||
[self getTiltFromAltitudeAngle:event.altitudeAngle]) != LI_ERR_UNSUPPORTED;
|
||||
}
|
||||
|
||||
- (void)sendStylusHoverEvent:(UIHoverGestureRecognizer*)gesture API_AVAILABLE(ios(13.0)) {
|
||||
uint8_t type;
|
||||
|
||||
switch (gesture.state) {
|
||||
case UIGestureRecognizerStateBegan:
|
||||
case UIGestureRecognizerStateChanged:
|
||||
type = LI_TOUCH_EVENT_HOVER;
|
||||
break;
|
||||
|
||||
case UIGestureRecognizerStateEnded:
|
||||
type = LI_TOUCH_EVENT_HOVER_LEAVE;
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
CGPoint location = [self adjustCoordinatesForVideoArea:[gesture locationInView:self]];
|
||||
CGSize videoSize = [self getVideoAreaSize];
|
||||
|
||||
float distance = 0.0f;
|
||||
#if defined(__IPHONE_16_1) || defined(__TVOS_16_1)
|
||||
if (@available(iOS 16.1, *)) {
|
||||
distance = gesture.zOffset;
|
||||
}
|
||||
#endif
|
||||
|
||||
uint16_t rotationAngle = LI_ROT_UNKNOWN;
|
||||
uint8_t tiltAngle = LI_TILT_UNKNOWN;
|
||||
#if defined(__IPHONE_16_4) || defined(__TVOS_16_4)
|
||||
if (@available(iOS 16.4, *)) {
|
||||
rotationAngle = [self getRotationFromAzimuthAngle:[gesture azimuthAngleInView:self]];
|
||||
tiltAngle = [self getTiltFromAltitudeAngle:gesture.altitudeAngle];
|
||||
}
|
||||
#endif
|
||||
|
||||
LiSendPenEvent(type, LI_TOOL_TYPE_PEN, 0, location.x / videoSize.width, location.y / videoSize.height,
|
||||
distance, 0.0f, 0.0f, rotationAngle, tiltAngle);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
|
||||
if ([self handleMouseButtonEvent:BUTTON_ACTION_PRESS
|
||||
forTouches:touches
|
||||
@@ -178,7 +331,26 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
|
||||
// Notify of user interaction and start expiration timer
|
||||
[self startInteractionTimer];
|
||||
|
||||
#if !TARGET_OS_TV
|
||||
if (@available(iOS 13.4, *)) {
|
||||
for (UITouch* touch in touches) {
|
||||
if (touch.type == UITouchTypePencil) {
|
||||
if ([self sendStylusEvent:touch]) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (![onScreenControls handleTouchDownEvent:touches]) {
|
||||
// We still inform the touch handler even if we're going trigger the
|
||||
// keyboard activation gesture. This is important to ensure the touch
|
||||
// handler has a consistent view of touch events to correctly suppress
|
||||
// activation of one or two finger gestures when a three finger gesture
|
||||
// is triggered.
|
||||
[touchHandler touchesBegan:touches withEvent:event];
|
||||
|
||||
if ([[event allTouches] count] == 3) {
|
||||
if (isInputingText) {
|
||||
Log(LOG_D, @"Closing the keyboard");
|
||||
@@ -189,6 +361,23 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
|
||||
// Prepare the textbox used to capture keyboard events.
|
||||
keyInputField.delegate = self;
|
||||
keyInputField.text = @"0";
|
||||
#if !TARGET_OS_TV
|
||||
// Prepare the toolbar above the keyboard for more options
|
||||
UIToolbar *customToolbarView = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, self.bounds.size.width, 44)];
|
||||
|
||||
UIBarButtonItem *doneBarButton = [self createButtonWithImageNamed:@"DoneIcon.png" backgroundColor:[UIColor clearColor] target:self action:@selector(toolbarButtonClicked:) keyCode:0x00 isToggleable:NO];
|
||||
UIBarButtonItem *windowsBarButton = [self createButtonWithImageNamed:@"WindowsIcon.png" backgroundColor:[UIColor blackColor] target:self action:@selector(toolbarButtonClicked:) keyCode:0x5B isToggleable:YES];
|
||||
UIBarButtonItem *tabBarButton = [self createButtonWithImageNamed:@"TabIcon.png" backgroundColor:[UIColor blackColor] target:self action:@selector(toolbarButtonClicked:) keyCode:0x09 isToggleable:NO];
|
||||
UIBarButtonItem *shiftBarButton = [self createButtonWithImageNamed:@"ShiftIcon.png" backgroundColor:[UIColor blackColor] target:self action:@selector(toolbarButtonClicked:) keyCode:0xA0 isToggleable:YES];
|
||||
UIBarButtonItem *escapeBarButton = [self createButtonWithImageNamed:@"EscapeIcon.png" backgroundColor:[UIColor blackColor] target:self action:@selector(toolbarButtonClicked:) keyCode:0x1B isToggleable:NO];
|
||||
UIBarButtonItem *controlBarButton = [self createButtonWithImageNamed:@"ControlIcon.png" backgroundColor:[UIColor blackColor] target:self action:@selector(toolbarButtonClicked:) keyCode:0xA2 isToggleable:YES];
|
||||
UIBarButtonItem *altBarButton = [self createButtonWithImageNamed:@"AltIcon.png" backgroundColor:[UIColor blackColor] target:self action:@selector(toolbarButtonClicked:) keyCode:0xA4 isToggleable:YES];
|
||||
UIBarButtonItem *deleteBarButton = [self createButtonWithImageNamed:@"DeleteIcon.png" backgroundColor:[UIColor blackColor] target:self action:@selector(toolbarButtonClicked:) keyCode:0x2E isToggleable:NO];
|
||||
UIBarButtonItem *flexibleSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
|
||||
|
||||
[customToolbarView setItems:[NSArray arrayWithObjects:doneBarButton, windowsBarButton, escapeBarButton, tabBarButton, shiftBarButton, controlBarButton, altBarButton, deleteBarButton, flexibleSpace, nil]];
|
||||
keyInputField.inputAccessoryView = customToolbarView;
|
||||
#endif
|
||||
[keyInputField becomeFirstResponder];
|
||||
[keyInputField addTarget:self action:@selector(onKeyboardPressed:) forControlEvents:UIControlEventEditingChanged];
|
||||
|
||||
@@ -198,8 +387,62 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
|
||||
isInputingText = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (UIBarButtonItem *)createButtonWithImageNamed:(NSString *)imageName backgroundColor:(UIColor *)backgroundColor target:(id)target action:(SEL)action keyCode:(NSInteger)keyCode isToggleable:(BOOL)isToggleable {
|
||||
UIImage *image = [UIImage imageNamed:imageName];
|
||||
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[button setImage:image forState:UIControlStateNormal];
|
||||
button.frame = CGRectMake(0, 0, 30, 30);
|
||||
button.imageView.contentMode = UIViewContentModeScaleAspectFit;
|
||||
button.imageView.backgroundColor = backgroundColor;
|
||||
button.imageView.layer.cornerRadius = 10.0;
|
||||
button.imageEdgeInsets = UIEdgeInsetsMake(6, 6, 6, 6);
|
||||
[button addTarget:target action:action forControlEvents:UIControlEventTouchUpInside];
|
||||
objc_setAssociatedObject(button, "keyCode", @(keyCode), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
objc_setAssociatedObject(button, "isToggleable", @(isToggleable), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
objc_setAssociatedObject(button, "isOn", @(NO), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
UIBarButtonItem *barButton = [[UIBarButtonItem alloc] initWithCustomView:button];
|
||||
return barButton;
|
||||
}
|
||||
|
||||
- (void)toolbarButtonClicked:(UIButton *)sender {
|
||||
BOOL isToggleable = [objc_getAssociatedObject(sender, "isToggleable") boolValue];
|
||||
BOOL isOn = [objc_getAssociatedObject(sender, "isOn") boolValue];
|
||||
if (isToggleable){
|
||||
isOn = !isOn;
|
||||
// Update the button's appearance based on its new state
|
||||
if (isOn) {
|
||||
sender.imageView.backgroundColor = [UIColor lightGrayColor];
|
||||
} else {
|
||||
sender.imageView.backgroundColor = [UIColor blackColor];
|
||||
}
|
||||
}
|
||||
// Update the new on/off state of the button
|
||||
objc_setAssociatedObject(sender, "isOn", @(isOn), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
// Get the keyCode parameter and convert to short for key press event
|
||||
short keyCode = [objc_getAssociatedObject(sender, "keyCode") shortValue];
|
||||
// Close keyboard if done button clicked
|
||||
if (!keyCode) {
|
||||
[keyInputField resignFirstResponder];
|
||||
isInputingText = false;
|
||||
}
|
||||
else {
|
||||
// Send key press event using keyCode parameter, toggle if necessary
|
||||
if (isToggleable){
|
||||
if (isOn){
|
||||
LiSendKeyboardEvent(keyCode, KEY_ACTION_DOWN, 0);
|
||||
[keysDown addObject:@(keyCode)];
|
||||
} else {
|
||||
LiSendKeyboardEvent(keyCode, KEY_ACTION_UP, 0);
|
||||
[keysDown removeObject:@(keyCode)];
|
||||
}
|
||||
}
|
||||
else {
|
||||
[touchHandler touchesBegan:touches withEvent:event];
|
||||
LiSendKeyboardEvent(keyCode, KEY_ACTION_DOWN, 0);
|
||||
usleep(50 * 1000);
|
||||
LiSendKeyboardEvent(keyCode, KEY_ACTION_UP, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -268,6 +511,14 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
|
||||
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
|
||||
#if !TARGET_OS_TV
|
||||
if (@available(iOS 13.4, *)) {
|
||||
for (UITouch* touch in touches) {
|
||||
if (touch.type == UITouchTypePencil) {
|
||||
if ([self sendStylusEvent:touch]) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UITouch *touch = [touches anyObject];
|
||||
if (touch.type == UITouchTypeIndirectPointer) {
|
||||
if (@available(iOS 14.0, *)) {
|
||||
@@ -303,7 +554,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
|
||||
for (UIPress* press in presses) {
|
||||
// For now, we'll treated it as handled if we handle at least one of the
|
||||
// UIPress events inside the set.
|
||||
if (press.key != nil && [KeyboardSupport sendKeyEvent:press.key down:YES]) {
|
||||
if ([KeyboardSupport sendKeyEventForPress:press down:YES]) {
|
||||
// This will prevent the legacy UITextField from receiving the event
|
||||
handled = YES;
|
||||
}
|
||||
@@ -322,7 +573,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
|
||||
for (UIPress* press in presses) {
|
||||
// For now, we'll treated it as handled if we handle at least one of the
|
||||
// UIPress events inside the set.
|
||||
if (press.key != nil && [KeyboardSupport sendKeyEvent:press.key down:NO]) {
|
||||
if ([KeyboardSupport sendKeyEventForPress:press down:NO]) {
|
||||
// This will prevent the legacy UITextField from receiving the event
|
||||
handled = YES;
|
||||
}
|
||||
@@ -346,6 +597,18 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
|
||||
|
||||
hasUserInteracted = YES;
|
||||
|
||||
#if !TARGET_OS_TV
|
||||
if (@available(iOS 13.4, *)) {
|
||||
for (UITouch* touch in touches) {
|
||||
if (touch.type == UITouchTypePencil) {
|
||||
if ([self sendStylusEvent:touch]) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (![onScreenControls handleTouchUpEvent:touches]) {
|
||||
[touchHandler touchesEnded:touches withEvent:event];
|
||||
}
|
||||
@@ -356,48 +619,21 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
|
||||
[self handleMouseButtonEvent:BUTTON_ACTION_RELEASE
|
||||
forTouches:touches
|
||||
withEvent:event];
|
||||
#if !TARGET_OS_TV
|
||||
if (@available(iOS 13.4, *)) {
|
||||
for (UITouch* touch in touches) {
|
||||
if (touch.type == UITouchTypePencil) {
|
||||
[self sendStylusEvent:touch];
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !TARGET_OS_TV
|
||||
- (void) updateCursorLocation:(CGPoint)location isMouse:(BOOL)isMouse {
|
||||
// These are now relative to the StreamView, however we need to scale them
|
||||
// further to make them relative to the actual video portion.
|
||||
float x = location.x - self.bounds.origin.x;
|
||||
float y = location.y - self.bounds.origin.y;
|
||||
|
||||
// For some reason, we don't seem to always get to the bounds of the window
|
||||
// so we'll subtract 1 pixel if we're to the left/below of the origin and
|
||||
// and add 1 pixel if we're to the right/above. It should be imperceptible
|
||||
// to the user but it will allow activation of gestures that require contact
|
||||
// with the edge of the screen (like Aero Snap).
|
||||
if (x < self.bounds.size.width / 2) {
|
||||
x--;
|
||||
}
|
||||
else {
|
||||
x++;
|
||||
}
|
||||
if (y < self.bounds.size.height / 2) {
|
||||
y--;
|
||||
}
|
||||
else {
|
||||
y++;
|
||||
}
|
||||
|
||||
// This logic mimics what iOS does with AVLayerVideoGravityResizeAspect
|
||||
CGSize videoSize;
|
||||
CGPoint videoOrigin;
|
||||
if (self.bounds.size.width > self.bounds.size.height * streamAspectRatio) {
|
||||
videoSize = CGSizeMake(self.bounds.size.height * streamAspectRatio, self.bounds.size.height);
|
||||
} else {
|
||||
videoSize = CGSizeMake(self.bounds.size.width, self.bounds.size.width / streamAspectRatio);
|
||||
}
|
||||
videoOrigin = CGPointMake(self.bounds.size.width / 2 - videoSize.width / 2,
|
||||
self.bounds.size.height / 2 - videoSize.height / 2);
|
||||
|
||||
// Confine the cursor to the video region. We don't just discard events outside
|
||||
// the region because we won't always get one exactly when the mouse leaves the region.
|
||||
x = MIN(MAX(x, videoOrigin.x), videoOrigin.x + videoSize.width);
|
||||
y = MIN(MAX(y, videoOrigin.y), videoOrigin.y + videoSize.height);
|
||||
CGPoint normalizedLocation = [self adjustCoordinatesForVideoArea:location];
|
||||
CGSize videoSize = [self getVideoAreaSize];
|
||||
|
||||
// Send the mouse position relative to the video region if it has changed
|
||||
// if we're receiving coordinates from a real mouse.
|
||||
@@ -406,15 +642,14 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
|
||||
// send it if the value has changed. We will receive one of these events
|
||||
// any time the user presses a modifier key, which can result in errant
|
||||
// mouse motion when using a Citrix X1 mouse.
|
||||
if (x != lastMouseX || y != lastMouseY || !isMouse) {
|
||||
if (normalizedLocation.x != lastMouseX || normalizedLocation.y != lastMouseY || !isMouse) {
|
||||
if (lastMouseX != 0 || lastMouseY != 0 || !isMouse) {
|
||||
LiSendMousePositionEvent(x - videoOrigin.x, y - videoOrigin.y,
|
||||
videoSize.width, videoSize.height);
|
||||
LiSendMousePositionEvent(normalizedLocation.x, normalizedLocation.y, videoSize.width, videoSize.height);
|
||||
}
|
||||
|
||||
if (isMouse) {
|
||||
lastMouseX = x;
|
||||
lastMouseY = y;
|
||||
lastMouseX = normalizedLocation.x;
|
||||
lastMouseY = normalizedLocation.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -469,10 +704,23 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
|
||||
}
|
||||
|
||||
CGPoint currentScrollTranslation = [gesture translationInView:self];
|
||||
short translationDeltaY = ((currentScrollTranslation.y - lastScrollTranslation.y) / self.bounds.size.height) * 120; // WHEEL_DELTA
|
||||
if (translationDeltaY != 0) {
|
||||
LiSendHighResScrollEvent(translationDeltaY * 20);
|
||||
lastScrollTranslation = currentScrollTranslation;
|
||||
const short translationMultiplier = 120 * 20; // WHEEL_DELTA * 20
|
||||
|
||||
{
|
||||
short translationDeltaY = ((currentScrollTranslation.y - lastScrollTranslation.y) / self.bounds.size.height) * translationMultiplier;
|
||||
if (translationDeltaY != 0) {
|
||||
LiSendHighResScrollEvent(translationDeltaY);
|
||||
lastScrollTranslation = currentScrollTranslation;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
short translationDeltaX = ((currentScrollTranslation.x - lastScrollTranslation.x) / self.bounds.size.width) * translationMultiplier;
|
||||
if (translationDeltaX != 0) {
|
||||
// Direction is reversed from vertical scrolling
|
||||
LiSendHighResHScrollEvent(-translationDeltaX);
|
||||
lastScrollTranslation = currentScrollTranslation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -492,9 +740,20 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
|
||||
// Using velocityInView is 0 for discrete scroll events
|
||||
// when scrolling very slowly, but translationInView does work.
|
||||
CGPoint currentScrollTranslation = [gesture translationInView:self];
|
||||
short translationDeltaY = currentScrollTranslation.y - lastScrollTranslation.y;
|
||||
if (translationDeltaY != 0) {
|
||||
LiSendScrollEvent(translationDeltaY > 0 ? 1 : -1);
|
||||
|
||||
{
|
||||
short translationDeltaY = currentScrollTranslation.y - lastScrollTranslation.y;
|
||||
if (translationDeltaY != 0) {
|
||||
LiSendScrollEvent(translationDeltaY > 0 ? 1 : -1);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
short translationDeltaX = currentScrollTranslation.x - lastScrollTranslation.x;
|
||||
if (translationDeltaX != 0) {
|
||||
// Direction is reversed from vertical scrolling
|
||||
LiSendHScrollEvent(translationDeltaX < 0 ? 1 : -1);
|
||||
}
|
||||
}
|
||||
|
||||
lastScrollTranslation = currentScrollTranslation;
|
||||
@@ -520,6 +779,13 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)textFieldDidEndEditing:(UITextField *)textField {
|
||||
for (NSNumber* keyCode in keysDown) {
|
||||
LiSendKeyboardEvent([keyCode shortValue], KEY_ACTION_UP, 0);
|
||||
}
|
||||
[keysDown removeAllObjects];
|
||||
}
|
||||
|
||||
- (void)onKeyboardPressed:(UITextField *)textField {
|
||||
NSString* inputText = textField.text;
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
|
||||
@@ -579,9 +845,10 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
|
||||
if (event.modifier != 0) {
|
||||
LiSendKeyboardEvent(event.modifierKeycode, KEY_ACTION_DOWN, event.modifier);
|
||||
}
|
||||
LiSendKeyboardEvent(event.keycode, KEY_ACTION_DOWN, event.modifier);
|
||||
// Let the host know these are not (necessarily) normalized to US English scancodes
|
||||
LiSendKeyboardEvent2(event.keycode, KEY_ACTION_DOWN, event.modifier, SS_KBE_FLAG_NON_NORMALIZED);
|
||||
usleep(50 * 1000);
|
||||
LiSendKeyboardEvent(event.keycode, KEY_ACTION_UP, event.modifier);
|
||||
LiSendKeyboardEvent2(event.keycode, KEY_ACTION_UP, event.modifier, SS_KBE_FLAG_NON_NORMALIZED);
|
||||
if (event.modifier != 0) {
|
||||
LiSendKeyboardEvent(event.modifierKeycode, KEY_ACTION_UP, event.modifier);
|
||||
}
|
||||
|
||||
@@ -32,13 +32,11 @@
|
||||
<key>ProfileName</key>
|
||||
<string>ExtendedGamepad</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>ProfileName</key>
|
||||
<string>Gamepad</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>GCSupportsControllerUserInteraction</key>
|
||||
<true/>
|
||||
<key>LSSupportsGameMode</key>
|
||||
<true/>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>_XCCurrentVersionName</key>
|
||||
<string>Moonlight v1.6.xcdatamodel</string>
|
||||
<string>Moonlight v1.10.xcdatamodel</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22225" systemVersion="23A344" minimumToolsVersion="Xcode 7.3" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="">
|
||||
<entity name="App" representedClassName="App" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="hdrSupported" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="hidden" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="id" attributeType="String" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<relationship name="host" maxCount="1" deletionRule="Nullify" destinationEntity="Host" inverseName="appList" inverseEntity="Host" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Host" representedClassName="Host" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="address" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="externalAddress" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="ipv6Address" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="localAddress" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="mac" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<attribute name="pairState" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="serverCert" optional="YES" attributeType="Binary" syncable="YES"/>
|
||||
<attribute name="serverCodecModeSupport" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="uuid" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<relationship name="appList" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="App" inverseName="host" inverseEntity="App" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Settings" representedClassName="Settings" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="absoluteTouchMode" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="audioConfig" attributeType="Integer 32" defaultValueString="2" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="bitrate" attributeType="Integer 32" defaultValueString="10000" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="btMouseSupport" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="deviceGyroMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="enableHdr" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="framerate" attributeType="Integer 32" defaultValueString="60" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="height" attributeType="Integer 32" defaultValueString="720" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="multiController" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="onscreenControls" attributeType="Integer 32" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="optimizeGames" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="playAudioOnPC" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="preferredCodec" attributeType="Integer 32" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="statsOverlay" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="swapABXYButtons" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="uniqueId" attributeType="String" syncable="YES"/>
|
||||
<attribute name="useFramePacing" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="width" attributeType="Integer 32" defaultValueString="1280" usesScalarValueType="NO" syncable="YES"/>
|
||||
</entity>
|
||||
</model>
|
||||
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="19574" systemVersion="20G224" minimumToolsVersion="Xcode 7.3" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="">
|
||||
<entity name="App" representedClassName="App" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="hdrSupported" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="hidden" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="id" attributeType="String" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<relationship name="host" maxCount="1" deletionRule="Nullify" destinationEntity="Host" inverseName="appList" inverseEntity="Host" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Host" representedClassName="Host" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="address" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="externalAddress" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="ipv6Address" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="localAddress" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="mac" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<attribute name="pairState" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="serverCert" optional="YES" attributeType="Binary" syncable="YES"/>
|
||||
<attribute name="serverCodecModeSupport" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="uuid" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<relationship name="appList" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="App" inverseName="host" inverseEntity="App" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Settings" representedClassName="Settings" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="absoluteTouchMode" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="audioConfig" attributeType="Integer 32" defaultValueString="2" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="bitrate" attributeType="Integer 32" defaultValueString="10000" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="btMouseSupport" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="enableHdr" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="framerate" attributeType="Integer 32" defaultValueString="60" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="height" attributeType="Integer 32" defaultValueString="720" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="multiController" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="onscreenControls" attributeType="Integer 32" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="optimizeGames" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="playAudioOnPC" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="statsOverlay" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="uniqueId" attributeType="String" syncable="YES"/>
|
||||
<attribute name="useFramePacing" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="useHevc" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="width" attributeType="Integer 32" defaultValueString="1280" usesScalarValueType="NO" syncable="YES"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="App" positionX="0" positionY="54" width="128" height="118"/>
|
||||
<element name="Host" positionX="0" positionY="0" width="128" height="210"/>
|
||||
<element name="Settings" positionX="0" positionY="0" width="128" height="254"/>
|
||||
</elements>
|
||||
</model>
|
||||
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="20086" systemVersion="21G72" minimumToolsVersion="Xcode 7.3" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="">
|
||||
<entity name="App" representedClassName="App" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="hdrSupported" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="hidden" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="id" attributeType="String" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<relationship name="host" maxCount="1" deletionRule="Nullify" destinationEntity="Host" inverseName="appList" inverseEntity="Host" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Host" representedClassName="Host" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="address" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="externalAddress" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="ipv6Address" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="localAddress" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="mac" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<attribute name="pairState" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="serverCert" optional="YES" attributeType="Binary" syncable="YES"/>
|
||||
<attribute name="serverCodecModeSupport" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="uuid" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<relationship name="appList" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="App" inverseName="host" inverseEntity="App" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Settings" representedClassName="Settings" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="absoluteTouchMode" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="audioConfig" attributeType="Integer 32" defaultValueString="2" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="bitrate" attributeType="Integer 32" defaultValueString="10000" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="btMouseSupport" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="enableHdr" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="framerate" attributeType="Integer 32" defaultValueString="60" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="height" attributeType="Integer 32" defaultValueString="720" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="multiController" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="onscreenControls" attributeType="Integer 32" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="optimizeGames" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="playAudioOnPC" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="statsOverlay" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="swapABXYButtons" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="uniqueId" attributeType="String" syncable="YES"/>
|
||||
<attribute name="useFramePacing" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="useHevc" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="width" attributeType="Integer 32" defaultValueString="1280" usesScalarValueType="NO" syncable="YES"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="App" positionX="0" positionY="54" width="128" height="118"/>
|
||||
<element name="Host" positionX="0" positionY="0" width="128" height="210"/>
|
||||
<element name="Settings" positionX="0" positionY="0" width="128" height="298"/>
|
||||
</elements>
|
||||
</model>
|
||||
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="20086" systemVersion="21G72" minimumToolsVersion="Xcode 7.3" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="">
|
||||
<entity name="App" representedClassName="App" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="hdrSupported" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="hidden" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="id" attributeType="String" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<relationship name="host" maxCount="1" deletionRule="Nullify" destinationEntity="Host" inverseName="appList" inverseEntity="Host" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Host" representedClassName="Host" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="address" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="externalAddress" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="ipv6Address" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="localAddress" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="mac" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<attribute name="pairState" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="serverCert" optional="YES" attributeType="Binary" syncable="YES"/>
|
||||
<attribute name="serverCodecModeSupport" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="uuid" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<relationship name="appList" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="App" inverseName="host" inverseEntity="App" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Settings" representedClassName="Settings" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="absoluteTouchMode" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="audioConfig" attributeType="Integer 32" defaultValueString="2" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="bitrate" attributeType="Integer 32" defaultValueString="10000" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="btMouseSupport" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="enableHdr" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="framerate" attributeType="Integer 32" defaultValueString="60" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="height" attributeType="Integer 32" defaultValueString="720" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="multiController" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="onscreenControls" attributeType="Integer 32" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="optimizeGames" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="playAudioOnPC" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="statsOverlay" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="swapABXYButtons" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="uniqueId" attributeType="String" syncable="YES"/>
|
||||
<attribute name="useFramePacing" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="useHevc2" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="width" attributeType="Integer 32" defaultValueString="1280" usesScalarValueType="NO" syncable="YES"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="App" positionX="0" positionY="54" width="128" height="118"/>
|
||||
<element name="Host" positionX="0" positionY="0" width="128" height="210"/>
|
||||
<element name="Settings" positionX="0" positionY="0" width="128" height="298"/>
|
||||
</elements>
|
||||
</model>
|
||||
@@ -20,7 +20,7 @@ static const int MAX_ATTEMPTS = 5;
|
||||
- (void) main {
|
||||
int attempts = 0;
|
||||
while (![self isCancelled] && attempts++ < MAX_ATTEMPTS) {
|
||||
HttpManager* hMan = [[HttpManager alloc] initWithHost:_host.activeAddress uniqueId:[IdManager getUniqueId] serverCert:_host.serverCert];
|
||||
HttpManager* hMan = [[HttpManager alloc] initWithHost:_host];
|
||||
AppAssetResponse* appAssetResp = [[AppAssetResponse alloc] init];
|
||||
[hMan executeRequestSynchronously:[HttpRequest requestForResponse:appAssetResp withUrlRequest:[hMan newAppAssetRequestWithAppId:self.app.id]]];
|
||||
|
||||
|
||||
@@ -7,14 +7,14 @@
|
||||
//
|
||||
|
||||
#import "AppListResponse.h"
|
||||
#import "TemporaryHost.h"
|
||||
|
||||
#ifndef ConnectionHelper_h
|
||||
#define ConnectionHelper_h
|
||||
|
||||
|
||||
@interface ConnectionHelper : NSObject
|
||||
|
||||
+(AppListResponse*) getAppListForHostWithHostIP:(NSString*) hostIP serverCert:(NSData*) serverCert uniqueID:(NSString*) uniqueId;
|
||||
+(AppListResponse*) getAppListForHost:(TemporaryHost*)host;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
|
||||
@implementation ConnectionHelper
|
||||
|
||||
+(AppListResponse*) getAppListForHostWithHostIP:(NSString*) hostIP serverCert:(NSData*) cert uniqueID:(NSString*) uniqueId {
|
||||
HttpManager* hMan = [[HttpManager alloc] initWithHost:hostIP uniqueId:uniqueId serverCert:cert];
|
||||
+(AppListResponse*) getAppListForHost:(TemporaryHost*)host {
|
||||
HttpManager* hMan = [[HttpManager alloc] initWithHost:host];
|
||||
|
||||
// Try up to 5 times to get the app list
|
||||
AppListResponse* appListResp = nil;
|
||||
|
||||
@@ -120,7 +120,7 @@
|
||||
}
|
||||
|
||||
- (ServerInfoResponse*) getServerInfoResponseForAddress:(NSString*)address {
|
||||
HttpManager* hMan = [[HttpManager alloc] initWithHost:address uniqueId:_uniqueId serverCert:nil];
|
||||
HttpManager* hMan = [[HttpManager alloc] initWithAddress:address httpsPort:0 serverCert:nil];
|
||||
ServerInfoResponse* serverInfoResponse = [[ServerInfoResponse alloc] init];
|
||||
[hMan executeRequestSynchronously:[HttpRequest requestForResponse:serverInfoResponse withUrlRequest:[hMan newServerInfoRequest:false] fallbackError:401 fallbackRequest:[hMan newHttpServerInfoRequest]]];
|
||||
return serverInfoResponse;
|
||||
@@ -202,7 +202,7 @@
|
||||
callback(host, nil);
|
||||
}
|
||||
} else if (!prohibitedAddress) {
|
||||
callback(nil, @"Could not connect to host. Ensure GameStream is enabled in GeForce Experience on your PC.");
|
||||
callback(nil, @"Could not connect to host.\n\nIf you're hosting using GeForce Experience, make sure you've enabled the toggle on the SHIELD tab.\n\nIf you're hosting using Sunshine, ensure it is running properly. If you're using a non-default port, you will need to include that here.");
|
||||
} else {
|
||||
callback(nil, prohibitedAddressMessage);
|
||||
}
|
||||
|
||||
@@ -103,8 +103,8 @@ static const float POLL_RATE = 2.0f; // Poll every 2 seconds
|
||||
ServerInfoResponse* serverInfoResp = [self requestInfoAtAddress:address cert:_host.serverCert];
|
||||
receivedResponse = [self checkResponse:serverInfoResp];
|
||||
if (receivedResponse) {
|
||||
[serverInfoResp populateHost:_host];
|
||||
_host.activeAddress = address;
|
||||
[serverInfoResp populateHost:_host];
|
||||
|
||||
// Update the database using the response
|
||||
DataManager *dataManager = [[DataManager alloc] init];
|
||||
@@ -126,9 +126,7 @@ static const float POLL_RATE = 2.0f; // Poll every 2 seconds
|
||||
}
|
||||
|
||||
- (ServerInfoResponse*) requestInfoAtAddress:(NSString*)address cert:(NSData*)cert {
|
||||
HttpManager* hMan = [[HttpManager alloc] initWithHost:address
|
||||
uniqueId:_uniqueId
|
||||
serverCert:cert];
|
||||
HttpManager* hMan = [[HttpManager alloc] initWithAddress:address httpsPort:0 serverCert:cert];
|
||||
ServerInfoResponse* response = [[ServerInfoResponse alloc] init];
|
||||
[hMan executeRequestSynchronously:[HttpRequest requestForResponse:response
|
||||
withUrlRequest:[hMan newServerInfoRequest:true]
|
||||
|
||||
@@ -9,10 +9,12 @@
|
||||
#import "HttpResponse.h"
|
||||
#import "HttpRequest.h"
|
||||
#import "StreamConfiguration.h"
|
||||
#import "TemporaryHost.h"
|
||||
|
||||
@interface HttpManager : NSObject <NSURLSessionDelegate>
|
||||
|
||||
- (id) initWithHost:(NSString*) host uniqueId:(NSString*) uniqueId serverCert:(NSData*) serverCert;
|
||||
- (id) initWithHost:(TemporaryHost*) host;
|
||||
- (id) initWithAddress:(NSString*) hostAddressPortString httpsPort:(unsigned short) httpsPort serverCert:(NSData*) serverCert;
|
||||
- (void) setServerCert:(NSData*) serverCert;
|
||||
- (NSURLRequest*) newPairRequest:(NSData*)salt clientCert:(NSData*)clientCert;
|
||||
- (NSURLRequest*) newUnpairRequest;
|
||||
@@ -24,8 +26,7 @@
|
||||
- (NSURLRequest*) newServerInfoRequest:(bool)fastFail;
|
||||
- (NSURLRequest*) newHttpServerInfoRequest:(bool)fastFail;
|
||||
- (NSURLRequest*) newHttpServerInfoRequest;
|
||||
- (NSURLRequest*) newLaunchRequest:(StreamConfiguration*)config;
|
||||
- (NSURLRequest*) newResumeRequest:(StreamConfiguration*)config;
|
||||
- (NSURLRequest*) newLaunchOrResumeRequest:(NSString*)verb config:(StreamConfiguration*)config;
|
||||
- (NSURLRequest*) newQuitAppRequest;
|
||||
- (NSURLRequest*) newAppAssetRequestWithAppId:(NSString*)appId;
|
||||
- (void) executeRequestSynchronously:(HttpRequest*)request;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#import "HttpRequest.h"
|
||||
#import "CryptoManager.h"
|
||||
#import "TemporaryApp.h"
|
||||
#import "ServerInfoResponse.h"
|
||||
|
||||
#include <libxml2/libxml/xmlreader.h>
|
||||
#include <string.h>
|
||||
@@ -22,22 +23,16 @@
|
||||
#define EXTRA_LONG_TIMEOUT_SEC 180
|
||||
|
||||
@implementation HttpManager {
|
||||
NSURLSession* _urlSession;
|
||||
NSString* _urlSafeHostName;
|
||||
NSString* _baseHTTPURL;
|
||||
NSString* _baseHTTPSURL;
|
||||
NSString* _uniqueId;
|
||||
NSString* _deviceName;
|
||||
NSData* _serverCert;
|
||||
NSMutableData* _respData;
|
||||
NSData* _requestResp;
|
||||
dispatch_semaphore_t _requestLock;
|
||||
|
||||
NSError* _error;
|
||||
TemporaryHost *_host; // May be nil
|
||||
NSString* _baseHTTPSURL;
|
||||
}
|
||||
|
||||
static const NSString* HTTP_PORT = @"47989";
|
||||
static const NSString* HTTPS_PORT = @"47984";
|
||||
|
||||
+ (NSData*) fixXmlVersion:(NSData*) xmlData {
|
||||
NSString* dataString = [[NSString alloc] initWithData:xmlData encoding:NSUTF8StringEncoding];
|
||||
NSString* xmlString = [dataString stringByReplacingOccurrencesOfString:@"UTF-16" withString:@"UTF-8" options:NSCaseInsensitiveSearch range:NSMakeRange(0, [dataString length])];
|
||||
@@ -49,63 +44,111 @@ static const NSString* HTTPS_PORT = @"47984";
|
||||
_serverCert = serverCert;
|
||||
}
|
||||
|
||||
- (id) initWithHost:(NSString*) host uniqueId:(NSString*) uniqueId serverCert:(NSData*) serverCert {
|
||||
- (id) initWithHost:(TemporaryHost*) host {
|
||||
self = [self initWithAddress:host.activeAddress httpsPort:host.httpsPort serverCert:host.serverCert];
|
||||
_host = host;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id) initWithAddress:(NSString*) hostAddressPortString httpsPort:(unsigned short)httpsPort serverCert:(NSData*) serverCert {
|
||||
self = [super init];
|
||||
// Use the same UID for all Moonlight clients to allow them
|
||||
// quit games started on another Moonlight client.
|
||||
_uniqueId = @"0123456789ABCDEF";
|
||||
_deviceName = deviceName;
|
||||
_serverCert = serverCert;
|
||||
_requestLock = dispatch_semaphore_create(0);
|
||||
_respData = [[NSMutableData alloc] init];
|
||||
NSURLSessionConfiguration* config = [NSURLSessionConfiguration ephemeralSessionConfiguration];
|
||||
_urlSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
|
||||
|
||||
NSString* address = [Utils addressPortStringToAddress:hostAddressPortString];
|
||||
unsigned short port = [Utils addressPortStringToPort:hostAddressPortString];
|
||||
|
||||
// If this is an IPv6 literal, we must properly enclose it in brackets
|
||||
NSString* urlSafeHost;
|
||||
if ([host containsString:@":"]) {
|
||||
urlSafeHost = [NSString stringWithFormat:@"[%@]", host];
|
||||
if ([address containsString:@":"]) {
|
||||
_urlSafeHostName = [NSString stringWithFormat:@"[%@]", address];
|
||||
} else {
|
||||
urlSafeHost = host;
|
||||
_urlSafeHostName = address;
|
||||
}
|
||||
|
||||
_baseHTTPURL = [NSString stringWithFormat:@"http://%@:%@", urlSafeHost, HTTP_PORT];
|
||||
_baseHTTPSURL = [NSString stringWithFormat:@"https://%@:%@", urlSafeHost, HTTPS_PORT];
|
||||
_baseHTTPURL = [NSString stringWithFormat:@"http://%@:%u", _urlSafeHostName, port];
|
||||
|
||||
if (httpsPort) {
|
||||
_baseHTTPSURL = [NSString stringWithFormat:@"https://%@:%u", _urlSafeHostName, httpsPort];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL) ensureHttpsUrlPopulated:(bool)fastFail {
|
||||
if (!_baseHTTPSURL) {
|
||||
// Use the caller's provided port if one was specified
|
||||
if (_host && _host.httpsPort != 0) {
|
||||
_baseHTTPSURL = [NSString stringWithFormat:@"https://%@:%u", _urlSafeHostName, _host.httpsPort];
|
||||
}
|
||||
else {
|
||||
// Query the host to retrieve the HTTPS port
|
||||
ServerInfoResponse* serverInfoResponse = [[ServerInfoResponse alloc] init];
|
||||
[self executeRequestSynchronously:[HttpRequest requestForResponse:serverInfoResponse withUrlRequest:[self newHttpServerInfoRequest:false]]];
|
||||
TemporaryHost* dummyHost = [[TemporaryHost alloc] init];
|
||||
if (![serverInfoResponse isStatusOk]) {
|
||||
return NO;
|
||||
}
|
||||
[serverInfoResponse populateHost:dummyHost];
|
||||
|
||||
// Pass the port back if the caller provided storage for it
|
||||
if (_host) {
|
||||
_host.httpsPort = dummyHost.httpsPort;
|
||||
}
|
||||
|
||||
_baseHTTPSURL = [NSString stringWithFormat:@"https://%@:%u", _urlSafeHostName, dummyHost.httpsPort];
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void) executeRequestSynchronously:(HttpRequest*)request {
|
||||
[_respData setLength:0];
|
||||
_error = nil;
|
||||
// This is a special case to handle failure of HTTPS port fetching
|
||||
if (!request.request) {
|
||||
if (request.response) {
|
||||
request.response.statusCode = EHOSTDOWN;
|
||||
request.response.statusMessage = @"Host is unreachable";
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
__block NSData* requestResp = nil;
|
||||
__block NSError* respError = nil;
|
||||
__block dispatch_semaphore_t requestLock = dispatch_semaphore_create(0);
|
||||
|
||||
Log(LOG_D, @"Making Request: %@", request);
|
||||
[[_urlSession dataTaskWithRequest:request.request completionHandler:^(NSData * __nullable data, NSURLResponse * __nullable response, NSError * __nullable error) {
|
||||
NSURLSession* urlSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration] delegate:self delegateQueue:nil];
|
||||
[[urlSession dataTaskWithRequest:request.request completionHandler:^(NSData * __nullable data, NSURLResponse * __nullable response, NSError * __nullable error) {
|
||||
|
||||
if (error != NULL) {
|
||||
Log(LOG_D, @"Connection error: %@", error);
|
||||
self->_error = error;
|
||||
respError = error;
|
||||
}
|
||||
else {
|
||||
Log(LOG_D, @"Received response: %@", response);
|
||||
|
||||
if (data != NULL) {
|
||||
Log(LOG_D, @"\n\nReceived data: %@\n\n", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
|
||||
[self->_respData appendData:data];
|
||||
if ([[NSString alloc] initWithData:self->_respData encoding:NSUTF8StringEncoding] != nil) {
|
||||
self->_requestResp = [HttpManager fixXmlVersion:self->_respData];
|
||||
if ([[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] != nil) {
|
||||
requestResp = [HttpManager fixXmlVersion:data];
|
||||
} else {
|
||||
self->_requestResp = self->_respData;
|
||||
requestResp = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dispatch_semaphore_signal(self->_requestLock);
|
||||
dispatch_semaphore_signal(requestLock);
|
||||
}] resume];
|
||||
dispatch_semaphore_wait(_requestLock, DISPATCH_TIME_FOREVER);
|
||||
|
||||
if (!_error && request.response) {
|
||||
[request.response populateWithData:_requestResp];
|
||||
dispatch_semaphore_wait(requestLock, DISPATCH_TIME_FOREVER);
|
||||
[urlSession invalidateAndCancel];
|
||||
|
||||
if (!respError && request.response) {
|
||||
[request.response populateWithData:requestResp];
|
||||
|
||||
// If the fallback error code was detected, issue the fallback request
|
||||
if (request.response.statusCode == request.fallbackError && request.fallbackRequest != NULL) {
|
||||
@@ -116,7 +159,7 @@ static const NSString* HTTPS_PORT = @"47984";
|
||||
[self executeRequestSynchronously:request];
|
||||
}
|
||||
}
|
||||
else if (_error && [_error code] == NSURLErrorServerCertificateUntrusted) {
|
||||
else if (respError && [respError code] == NSURLErrorServerCertificateUntrusted) {
|
||||
// We must have a pinned cert for HTTPS. If we fail, it must be due to
|
||||
// a non-matching cert, not because we had no cert at all.
|
||||
assert(_serverCert != nil);
|
||||
@@ -131,9 +174,9 @@ static const NSString* HTTPS_PORT = @"47984";
|
||||
[self executeRequestSynchronously:request];
|
||||
}
|
||||
}
|
||||
else if (_error && request.response) {
|
||||
request.response.statusCode = [_error code];
|
||||
request.response.statusMessage = [_error localizedDescription];
|
||||
else if (respError && request.response) {
|
||||
request.response.statusCode = [respError code];
|
||||
request.response.statusMessage = [respError localizedDescription];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,11 +217,19 @@ static const NSString* HTTPS_PORT = @"47984";
|
||||
}
|
||||
|
||||
- (NSURLRequest*) newPairChallenge {
|
||||
if (![self ensureHttpsUrlPopulated:NO]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString* urlString = [NSString stringWithFormat:@"%@/pair?uniqueid=%@&devicename=%@&updateState=1&phrase=pairchallenge", _baseHTTPSURL, _uniqueId, _deviceName];
|
||||
return [self createRequestFromString:urlString timeout:NORMAL_TIMEOUT_SEC];
|
||||
}
|
||||
|
||||
- (NSURLRequest *)newAppListRequest {
|
||||
if (![self ensureHttpsUrlPopulated:NO]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString* urlString = [NSString stringWithFormat:@"%@/applist?uniqueid=%@", _baseHTTPSURL, _uniqueId];
|
||||
return [self createRequestFromString:urlString timeout:NORMAL_TIMEOUT_SEC];
|
||||
}
|
||||
@@ -189,6 +240,10 @@ static const NSString* HTTPS_PORT = @"47984";
|
||||
return [self newHttpServerInfoRequest:fastFail];
|
||||
}
|
||||
|
||||
if (![self ensureHttpsUrlPopulated:fastFail]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString* urlString = [NSString stringWithFormat:@"%@/serverinfo?uniqueid=%@", _baseHTTPSURL, _uniqueId];
|
||||
return [self createRequestFromString:urlString timeout:(fastFail ? SHORT_TIMEOUT_SEC : NORMAL_TIMEOUT_SEC)];
|
||||
}
|
||||
@@ -202,44 +257,49 @@ static const NSString* HTTPS_PORT = @"47984";
|
||||
return [self newHttpServerInfoRequest:false];
|
||||
}
|
||||
|
||||
- (NSURLRequest*) newLaunchRequest:(StreamConfiguration*)config {
|
||||
- (NSURLRequest*) newLaunchOrResumeRequest:(NSString*)verb config:(StreamConfiguration*)config {
|
||||
if (![self ensureHttpsUrlPopulated:NO]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Using an FPS value over 60 causes SOPS to default to 720p60,
|
||||
// so force it to 0 to ensure the correct resolution is set. We
|
||||
// used to use 60 here but that locked the frame rate to 60 FPS
|
||||
// on GFE 3.20.3.
|
||||
int fps = config.frameRate > 60 ? 0 : config.frameRate;
|
||||
// on GFE 3.20.3. We do not do this hack for Sunshine (which is
|
||||
// indicated by a negative version in the last field.
|
||||
int fps = (config.frameRate > 60 && ![config.appVersion containsString:@".-"]) ? 0 : config.frameRate;
|
||||
|
||||
NSString* urlString = [NSString stringWithFormat:@"%@/launch?uniqueid=%@&appid=%@&mode=%dx%dx%d&additionalStates=1&sops=%d&rikey=%@&rikeyid=%d%@&localAudioPlayMode=%d&surroundAudioInfo=%d&remoteControllersBitmap=%d&gcmap=%d",
|
||||
_baseHTTPSURL, _uniqueId,
|
||||
NSString* urlString = [NSString stringWithFormat:@"%@/%@?uniqueid=%@&appid=%@&mode=%dx%dx%d&additionalStates=1&sops=%d&rikey=%@&rikeyid=%d%@&localAudioPlayMode=%d&surroundAudioInfo=%d&remoteControllersBitmap=%d&gcmap=%d&gcpersist=%d%s",
|
||||
_baseHTTPSURL, verb, _uniqueId,
|
||||
config.appID,
|
||||
config.width, config.height, fps,
|
||||
config.optimizeGameSettings ? 1 : 0,
|
||||
[Utils bytesToHex:config.riKey], config.riKeyId,
|
||||
config.enableHdr ? @"&hdrMode=1&clientHdrCapVersion=0&clientHdrCapSupportedFlagsInUint32=0&clientHdrCapMetaDataId=NV_STATIC_METADATA_TYPE_1&clientHdrCapDisplayData=0x0x0x0x0x0x0x0x0x0x0": @"",
|
||||
(config.supportedVideoFormats & VIDEO_FORMAT_MASK_10BIT) ? @"&hdrMode=1&clientHdrCapVersion=0&clientHdrCapSupportedFlagsInUint32=0&clientHdrCapMetaDataId=NV_STATIC_METADATA_TYPE_1&clientHdrCapDisplayData=0x0x0x0x0x0x0x0x0x0x0": @"",
|
||||
config.playAudioOnPC ? 1 : 0,
|
||||
SURROUNDAUDIOINFO_FROM_AUDIO_CONFIGURATION(config.audioConfiguration),
|
||||
config.gamepadMask, config.gamepadMask];
|
||||
config.gamepadMask, config.gamepadMask,
|
||||
!config.multiController ? 1 : 0,
|
||||
LiGetLaunchUrlQueryParameters()];
|
||||
Log(LOG_I, @"Requesting: %@", urlString);
|
||||
// This blocks while the app is launching
|
||||
return [self createRequestFromString:urlString timeout:LONG_TIMEOUT_SEC];
|
||||
}
|
||||
|
||||
- (NSURLRequest*) newResumeRequest:(StreamConfiguration*)config {
|
||||
NSString* urlString = [NSString stringWithFormat:@"%@/resume?uniqueid=%@&rikey=%@&rikeyid=%d&surroundAudioInfo=%d",
|
||||
_baseHTTPSURL, _uniqueId,
|
||||
[Utils bytesToHex:config.riKey], config.riKeyId,
|
||||
SURROUNDAUDIOINFO_FROM_AUDIO_CONFIGURATION(config.audioConfiguration)];
|
||||
Log(LOG_I, @"Requesting: %@", urlString);
|
||||
// This blocks while the app is resuming
|
||||
return [self createRequestFromString:urlString timeout:LONG_TIMEOUT_SEC];
|
||||
}
|
||||
|
||||
- (NSURLRequest*) newQuitAppRequest {
|
||||
if (![self ensureHttpsUrlPopulated:NO]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString* urlString = [NSString stringWithFormat:@"%@/cancel?uniqueid=%@", _baseHTTPSURL, _uniqueId];
|
||||
return [self createRequestFromString:urlString timeout:LONG_TIMEOUT_SEC];
|
||||
}
|
||||
|
||||
- (NSURLRequest*) newAppAssetRequestWithAppId:(NSString *)appId {
|
||||
if (![self ensureHttpsUrlPopulated:NO]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString* urlString = [NSString stringWithFormat:@"%@/appasset?uniqueid=%@&appid=%@&AssetType=2&AssetIdx=0", _baseHTTPSURL, _uniqueId, appId];
|
||||
return [self createRequestFromString:urlString timeout:NORMAL_TIMEOUT_SEC];
|
||||
}
|
||||
@@ -259,7 +319,7 @@ static const NSString* HTTPS_PORT = @"47984";
|
||||
|
||||
SecIdentityCopyCertificate(identity, &certificate);
|
||||
|
||||
return [[NSArray alloc] initWithObjects:(__bridge id)certificate, nil];
|
||||
return [[NSArray alloc] initWithObjects:(__bridge_transfer id)certificate, nil];
|
||||
}
|
||||
|
||||
// Returns the identity
|
||||
@@ -271,13 +331,14 @@ static const NSString* HTTPS_PORT = @"47984";
|
||||
const void *keys[] = { kSecImportExportPassphrase };
|
||||
const void *values[] = { password };
|
||||
CFDictionaryRef options = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
|
||||
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
|
||||
CFArrayRef items = nil;
|
||||
OSStatus securityError = SecPKCS12Import(p12Data, options, &items);
|
||||
|
||||
if (securityError == errSecSuccess) {
|
||||
//Log(LOG_D, @"Success opening p12 certificate. Items: %ld", CFArrayGetCount(items));
|
||||
CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
|
||||
identityApp = (SecIdentityRef)CFDictionaryGetValue(identityDict, kSecImportItemIdentity);
|
||||
identityApp = (SecIdentityRef)CFRetain(CFDictionaryGetValue(identityDict, kSecImportItemIdentity));
|
||||
CFRelease(items);
|
||||
} else {
|
||||
Log(LOG_E, @"Error opening Certificate.");
|
||||
}
|
||||
@@ -331,6 +392,7 @@ static const NSString* HTTPS_PORT = @"47984";
|
||||
SecIdentityRef identity = [self getClientCertificate];
|
||||
NSArray* certArray = [self getCertificate:identity];
|
||||
NSURLCredential* newCredential = [NSURLCredential credentialWithIdentity:identity certificates:certArray persistence:NSURLCredentialPersistencePermanent];
|
||||
CFRelease(identity);
|
||||
completionHandler(NSURLSessionAuthChallengeUseCredential, newCredential);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -72,16 +72,21 @@ static NSString* NV_SERVICE_TYPE = @"_nvstream._tcp";
|
||||
struct sockaddr* addr = (struct sockaddr*)[addrData bytes];
|
||||
if (addr->sa_family == AF_INET) {
|
||||
inet_ntop(addr->sa_family, &((struct sockaddr_in*)addr)->sin_addr, addrStr, sizeof(addrStr));
|
||||
unsigned short port = ntohs(((struct sockaddr_in*)addr)->sin_port);
|
||||
return [NSString stringWithFormat: @"%s:%u", addrStr, port];
|
||||
}
|
||||
else {
|
||||
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)addr;
|
||||
inet_ntop(addr->sa_family, &sin6->sin6_addr, addrStr, sizeof(addrStr));
|
||||
unsigned short port = ntohs(((struct sockaddr_in6*)addr)->sin6_port);
|
||||
if (sin6->sin6_scope_id != 0) {
|
||||
// Link-local addresses with scope IDs are special
|
||||
return [NSString stringWithFormat: @"%s%%%u", addrStr, sin6->sin6_scope_id];
|
||||
return [NSString stringWithFormat: @"[%s%%%u]:%u", addrStr, sin6->sin6_scope_id, port];
|
||||
}
|
||||
else {
|
||||
return [NSString stringWithFormat: @"[%s]:%u", addrStr, port];
|
||||
}
|
||||
}
|
||||
return [NSString stringWithFormat: @"%s", addrStr];
|
||||
}
|
||||
|
||||
+ (BOOL)isAddress:(uint8_t*)address inSubnet:(uint8_t*)subnet netmask:(int)bits {
|
||||
@@ -235,7 +240,7 @@ static NSString* NV_SERVICE_TYPE = @"_nvstream._tcp";
|
||||
[NSTimer scheduledTimerWithTimeInterval:2.0
|
||||
target:self
|
||||
selector:@selector(retryResolveTimerCallback:)
|
||||
userInfo:nil
|
||||
userInfo:sender
|
||||
repeats:NO];
|
||||
}
|
||||
|
||||
@@ -289,12 +294,12 @@ static NSString* NV_SERVICE_TYPE = @"_nvstream._tcp";
|
||||
return;
|
||||
}
|
||||
|
||||
Log(LOG_I, @"Retrying mDNS resolution");
|
||||
for (NSNetService* service in services) {
|
||||
if (service.hostName == nil) {
|
||||
[service setDelegate:self];
|
||||
[service resolveWithTimeout:5];
|
||||
}
|
||||
NSNetService* service = timer.userInfo;
|
||||
Log(LOG_I, @"Retrying mDNS resolution for %@", service);
|
||||
|
||||
if (service.hostName == nil) {
|
||||
[service setDelegate:self];
|
||||
[service resolveWithTimeout:5];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,15 +38,13 @@
|
||||
[_httpManager executeRequestSynchronously:[HttpRequest requestForResponse:serverInfoResp withUrlRequest:[_httpManager newServerInfoRequest:false]
|
||||
fallbackError:401 fallbackRequest:[_httpManager newHttpServerInfoRequest]]];
|
||||
if ([serverInfoResp isStatusOk]) {
|
||||
if ([[serverInfoResp getStringTag:@"state"] hasSuffix:@"_SERVER_BUSY"]) {
|
||||
[_callback pairFailed:@"You cannot pair while a previous session is still running on the host PC. Quit any running games or reboot the host PC, then try pairing again."];
|
||||
} else if (![[serverInfoResp getStringTag:@"PairStatus"] isEqual:@"1"]) {
|
||||
if (![[serverInfoResp getStringTag:@"PairStatus"] isEqual:@"1"]) {
|
||||
NSString* appversion = [serverInfoResp getStringTag:@"appversion"];
|
||||
if (appversion == nil) {
|
||||
[_callback pairFailed:@"Missing XML element"];
|
||||
return;
|
||||
}
|
||||
[self initiatePairWithPin:PIN forServerMajorVersion:[[appversion substringToIndex:1] intValue]];
|
||||
[self initiatePairWithPin:PIN forServerMajorVersion:[[appversion substringToIndex:1] intValue] withState:[serverInfoResp getStringTag:@"state"]];
|
||||
} else {
|
||||
[_callback alreadyPaired];
|
||||
}
|
||||
@@ -82,8 +80,8 @@
|
||||
}
|
||||
|
||||
// All codepaths must call finishPairing exactly once before returning!
|
||||
- (void) initiatePairWithPin:(NSString*)PIN forServerMajorVersion:(int)serverMajorVersion {
|
||||
Log(LOG_I, @"Pairing with generation %d server", serverMajorVersion);
|
||||
- (void) initiatePairWithPin:(NSString*)PIN forServerMajorVersion:(int)serverMajorVersion withState:(NSString*)state {
|
||||
Log(LOG_I, @"Pairing with generation %d server in state %@", serverMajorVersion, state);
|
||||
|
||||
// Start a background task to help prevent the app from being killed
|
||||
// while pairing is in progress.
|
||||
@@ -99,7 +97,13 @@
|
||||
HttpResponse* pairResp = [[HttpResponse alloc] init];
|
||||
[_httpManager executeRequestSynchronously:[HttpRequest requestForResponse:pairResp withUrlRequest:[_httpManager newPairRequest:salt clientCert:_clientCert]]];
|
||||
if (![self verifyResponseStatus:pairResp]) {
|
||||
[self finishPairing:bgId forResponse:pairResp withFallbackError:@"Pairing was declined by the target."];
|
||||
// GFE does not allow pairing while a server is busy, but Sunshine does. We give it a try and display the busy error if it fails.
|
||||
if ([state hasSuffix:@"_SERVER_BUSY"]) {
|
||||
[self finishPairing:bgId forResponse:pairResp withFallbackError:@"You cannot pair while a previous session is still running on the host PC. Quit any running games or reboot the host PC, then try pairing again."];
|
||||
}
|
||||
else {
|
||||
[self finishPairing:bgId forResponse:pairResp withFallbackError:@"Pairing was declined by the target."];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -168,7 +172,7 @@
|
||||
|
||||
NSData* serverSecretResp = [Utils hexToBytes:[secretResp getStringTag:@"pairingsecret"]];
|
||||
NSData* serverSecret = [serverSecretResp subdataWithRange:NSMakeRange(0, 16)];
|
||||
NSData* serverSignature = [serverSecretResp subdataWithRange:NSMakeRange(16, 256)];
|
||||
NSData* serverSignature = [serverSecretResp subdataWithRange:NSMakeRange(16, serverSecretResp.length - 16)];
|
||||
|
||||
if (![cryptoMan verifySignature:serverSecret withSignature:serverSignature andCert:[Utils hexToBytes:plainCert]]) {
|
||||
[self finishPairing:bgId forResponse:secretResp withFallbackError:@"Server certificate invalid"];
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#define TAG_HOSTNAME @"hostname"
|
||||
#define TAG_EXTERNAL_IP @"ExternalIP"
|
||||
#define TAG_HTTPS_PORT @"HttpsPort"
|
||||
#define TAG_LOCAL_IP @"LocalIP"
|
||||
#define TAG_UNIQUE_ID @"uniqueid"
|
||||
#define TAG_MAC_ADDRESS @"mac"
|
||||
@@ -18,6 +19,9 @@
|
||||
#define TAG_STATE @"state"
|
||||
#define TAG_CURRENT_GAME @"currentgame"
|
||||
|
||||
// Sunshine extension
|
||||
#define TAG_EXTERNAL_PORT @"ExternalPort"
|
||||
|
||||
@interface ServerInfoResponse : HttpResponse <Response>
|
||||
|
||||
- (void) populateWithData:(NSData *)data;
|
||||
|
||||
@@ -23,17 +23,58 @@
|
||||
host.mac = [[self getStringTag:TAG_MAC_ADDRESS] trim];
|
||||
host.currentGame = [[self getStringTag:TAG_CURRENT_GAME] trim];
|
||||
|
||||
NSInteger httpsPort;
|
||||
if ([self getIntTag:TAG_HTTPS_PORT value:&httpsPort]) {
|
||||
host.httpsPort = (unsigned short)httpsPort;
|
||||
}
|
||||
else {
|
||||
// Use the default if it's not specified
|
||||
host.httpsPort = 47984;
|
||||
}
|
||||
|
||||
// We might get an IPv4 loopback address if we're using GS IPv6 Forwarder
|
||||
NSString *lanAddr = [[self getStringTag:TAG_LOCAL_IP] trim];
|
||||
if (![lanAddr hasPrefix:@"127."]) {
|
||||
host.localAddress = lanAddr;
|
||||
unsigned short localPort;
|
||||
|
||||
// If we reached this host through this port, store our port there
|
||||
if (host.activeAddress && [lanAddr isEqualToString:[Utils addressPortStringToAddress:host.activeAddress]]) {
|
||||
localPort = [Utils addressPortStringToPort:host.activeAddress];
|
||||
}
|
||||
else if (host.localAddress) {
|
||||
// If there's an existing local address, use the port from that
|
||||
localPort = [Utils addressPortStringToPort:host.localAddress];
|
||||
}
|
||||
else {
|
||||
// If all else fails, use 47989
|
||||
localPort = 47989;
|
||||
}
|
||||
|
||||
host.localAddress = [Utils addressAndPortToAddressPortString:lanAddr port:localPort];
|
||||
}
|
||||
|
||||
// This is a Sunshine extension for WAN port remapping
|
||||
NSInteger externalHttpPort;
|
||||
if (![self getIntTag:TAG_EXTERNAL_PORT value:&externalHttpPort]) {
|
||||
// Use our active port if it's not specified
|
||||
if (host.activeAddress) {
|
||||
externalHttpPort = [Utils addressPortStringToPort:host.activeAddress];
|
||||
}
|
||||
else {
|
||||
// Otherwise use the default
|
||||
externalHttpPort = 47989;
|
||||
}
|
||||
}
|
||||
|
||||
// Modern GFE versions don't actually give us a WAN address anymore
|
||||
// so we leave the one that we populated from mDNS discovery via STUN.
|
||||
NSString *wanAddr = [[self getStringTag:TAG_EXTERNAL_IP] trim];
|
||||
if (wanAddr) {
|
||||
host.externalAddress = wanAddr;
|
||||
host.externalAddress = [Utils addressAndPortToAddressPortString:wanAddr port:externalHttpPort];
|
||||
}
|
||||
else if (host.externalAddress) {
|
||||
// If we have an external address (via STUN) already, we still need to populate the port
|
||||
host.externalAddress = [Utils addressAndPortToAddressPortString:[Utils addressPortStringToAddress:host.externalAddress] port:externalHttpPort];
|
||||
}
|
||||
|
||||
NSString *state = [[self getStringTag:TAG_STATE] trim];
|
||||
@@ -44,6 +85,9 @@
|
||||
host.currentGame = @"0";
|
||||
}
|
||||
|
||||
// GFE uses the Mjolnir codename in their state enum values
|
||||
host.isNvidiaServerSoftware = [state containsString:@"MJOLNIR"];
|
||||
|
||||
NSInteger pairStatus;
|
||||
if ([self getIntTag:TAG_PAIR_STATUS value:&pairStatus]) {
|
||||
host.pairState = pairStatus ? PairStatePaired : PairStateUnpaired;
|
||||
|
||||
@@ -16,12 +16,15 @@
|
||||
|
||||
@implementation WakeOnLanManager
|
||||
|
||||
static const int numPorts = 7;
|
||||
static const int ports[numPorts] = {
|
||||
static const int numStaticPorts = 2;
|
||||
static const int staticPorts[numStaticPorts] = {
|
||||
9, // Standard WOL port (privileged port)
|
||||
47998, 47999, 48000, 48002, 48010, // Ports opened by GFE
|
||||
47009, // Port opened by Moonlight Internet Hosting Tool for WoL (non-privileged port)
|
||||
};
|
||||
static const int numDynamicPorts = 5;
|
||||
static const int dynamicPorts[numDynamicPorts] = {
|
||||
47998, 47999, 48000, 48002, 48010, // Ports opened by GFE/Sunshine
|
||||
};
|
||||
|
||||
+ (void) populateAddress:(struct sockaddr_storage*)addr withPort:(unsigned short)port {
|
||||
if (addr->ss_family == AF_INET) {
|
||||
@@ -38,29 +41,33 @@ static const int ports[numPorts] = {
|
||||
NSData* wolPayload = [WakeOnLanManager createPayload:host];
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
const char* address;
|
||||
NSString* address;
|
||||
struct addrinfo hints, *res, *curr;
|
||||
|
||||
// try all ip addresses
|
||||
if (i == 0 && host.localAddress != nil) {
|
||||
address = [host.localAddress UTF8String];
|
||||
address = host.localAddress;
|
||||
} else if (i == 1 && host.externalAddress != nil) {
|
||||
address = [host.externalAddress UTF8String];
|
||||
address = host.externalAddress;
|
||||
} else if (i == 2 && host.address != nil) {
|
||||
address = [host.address UTF8String];
|
||||
address = host.address;
|
||||
} else if (i == 3 && host.ipv6Address != nil) {
|
||||
address = [host.ipv6Address UTF8String];
|
||||
address = host.ipv6Address;
|
||||
} else if (i == 4) {
|
||||
address = "255.255.255.255";
|
||||
address = @"255.255.255.255";
|
||||
} else {
|
||||
// Requested address wasn't present
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the raw address and base port from the address+port string
|
||||
NSString* rawAddress = [Utils addressPortStringToAddress:address];
|
||||
unsigned short basePort = [Utils addressPortStringToPort:address];
|
||||
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_flags = AI_ADDRCONFIG;
|
||||
if (getaddrinfo(address, NULL, &hints, &res) != 0 || res == NULL) {
|
||||
if (getaddrinfo([rawAddress UTF8String], NULL, &hints, &res) != 0 || res == NULL) {
|
||||
// Failed to resolve address
|
||||
Log(LOG_E, @"Failed to resolve WOL address");
|
||||
continue;
|
||||
@@ -86,15 +93,29 @@ static const int ports[numPorts] = {
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
memcpy(&addr, curr->ai_addr, curr->ai_addrlen);
|
||||
|
||||
for (int j = 0; j < numPorts; j++) {
|
||||
[WakeOnLanManager populateAddress:&addr withPort:ports[j]];
|
||||
for (int j = 0; j < numStaticPorts; j++) {
|
||||
[WakeOnLanManager populateAddress:&addr withPort:staticPorts[j]];
|
||||
long err = sendto(wolSocket,
|
||||
[wolPayload bytes],
|
||||
[wolPayload length],
|
||||
0,
|
||||
(struct sockaddr*)&addr,
|
||||
curr->ai_addrlen);
|
||||
Log(LOG_I, @"Sending WOL packet returned: %ld", err);
|
||||
Log(LOG_I, @"Sending WOL packet to port %u returned: %ld", staticPorts[j], err);
|
||||
}
|
||||
|
||||
for (int j = 0; j < numDynamicPorts; j++) {
|
||||
// Offset the WoL dynamic ports by the base port
|
||||
unsigned short port = ((int)dynamicPorts[j] - 47989) + basePort;
|
||||
|
||||
[WakeOnLanManager populateAddress:&addr withPort:port];
|
||||
long err = sendto(wolSocket,
|
||||
[wolPayload bytes],
|
||||
[wolPayload length],
|
||||
0,
|
||||
(struct sockaddr*)&addr,
|
||||
curr->ai_addrlen);
|
||||
Log(LOG_I, @"Sending WOL packet to port %u returned: %ld", port, err);
|
||||
}
|
||||
|
||||
close(wolSocket);
|
||||
|
||||
@@ -17,6 +17,10 @@ typedef struct {
|
||||
int totalFrames;
|
||||
int receivedFrames;
|
||||
int networkDroppedFrames;
|
||||
int totalHostProcessingLatency;
|
||||
int framesWithHostProcessingLatency;
|
||||
int maxHostProcessingLatency;
|
||||
int minHostProcessingLatency;
|
||||
} video_stats_t;
|
||||
|
||||
@interface Connection : NSOperation <NSStreamDelegate>
|
||||
|
||||
@@ -47,7 +47,7 @@ static VideoDecoderRenderer* renderer;
|
||||
|
||||
int DrDecoderSetup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags)
|
||||
{
|
||||
[renderer setupWithVideoFormat:videoFormat refreshRate:redrawRate];
|
||||
[renderer setupWithVideoFormat:videoFormat width:width height:height frameRate:redrawRate];
|
||||
lastFrameNumber = 0;
|
||||
activeVideoFormat = videoFormat;
|
||||
memset(¤tVideoStats, 0, sizeof(currentVideoStats));
|
||||
@@ -55,9 +55,14 @@ int DrDecoderSetup(int videoFormat, int width, int height, int redrawRate, void*
|
||||
return 0;
|
||||
}
|
||||
|
||||
void DrCleanup(void)
|
||||
void DrStart(void)
|
||||
{
|
||||
[renderer cleanup];
|
||||
[renderer start];
|
||||
}
|
||||
|
||||
void DrStop(void)
|
||||
{
|
||||
[renderer stop];
|
||||
}
|
||||
|
||||
-(BOOL) getVideoStats:(video_stats_t*)stats
|
||||
@@ -81,10 +86,44 @@ void DrCleanup(void)
|
||||
{
|
||||
case VIDEO_FORMAT_H264:
|
||||
return @"H.264";
|
||||
case VIDEO_FORMAT_H264_HIGH8_444:
|
||||
return @"H.264 (4:4:4)";
|
||||
case VIDEO_FORMAT_H265:
|
||||
return @"HEVC";
|
||||
case VIDEO_FORMAT_H265_REXT8_444:
|
||||
return @"HEVC (4:4:4)";
|
||||
case VIDEO_FORMAT_H265_MAIN10:
|
||||
return @"HEVC Main 10";
|
||||
if (LiGetCurrentHostDisplayHdrMode()) {
|
||||
return @"HEVC Main 10 HDR";
|
||||
}
|
||||
else {
|
||||
return @"HEVC Main 10 SDR";
|
||||
}
|
||||
case VIDEO_FORMAT_H265_REXT10_444:
|
||||
if (LiGetCurrentHostDisplayHdrMode()) {
|
||||
return @"HEVC Main 10 HDR (4:4:4)";
|
||||
}
|
||||
else {
|
||||
return @"HEVC Main 10 SDR (4:4:4)";
|
||||
}
|
||||
case VIDEO_FORMAT_AV1_MAIN8:
|
||||
return @"AV1";
|
||||
case VIDEO_FORMAT_AV1_HIGH8_444:
|
||||
return @"AV1 (4:4:4)";
|
||||
case VIDEO_FORMAT_AV1_MAIN10:
|
||||
if (LiGetCurrentHostDisplayHdrMode()) {
|
||||
return @"AV1 10-bit HDR";
|
||||
}
|
||||
else {
|
||||
return @"AV1 10-bit SDR";
|
||||
}
|
||||
case VIDEO_FORMAT_AV1_HIGH10_444:
|
||||
if (LiGetCurrentHostDisplayHdrMode()) {
|
||||
return @"AV1 10-bit HDR (4:4:4)";
|
||||
}
|
||||
else {
|
||||
return @"AV1 10-bit SDR (4:4:4)";
|
||||
}
|
||||
default:
|
||||
return @"UNKNOWN";
|
||||
}
|
||||
@@ -124,6 +163,19 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit)
|
||||
lastFrameNumber = decodeUnit->frameNumber;
|
||||
}
|
||||
|
||||
if (decodeUnit->frameHostProcessingLatency != 0) {
|
||||
if (currentVideoStats.minHostProcessingLatency == 0 || decodeUnit->frameHostProcessingLatency < currentVideoStats.minHostProcessingLatency) {
|
||||
currentVideoStats.minHostProcessingLatency = decodeUnit->frameHostProcessingLatency;
|
||||
}
|
||||
|
||||
if (decodeUnit->frameHostProcessingLatency > currentVideoStats.maxHostProcessingLatency) {
|
||||
currentVideoStats.maxHostProcessingLatency = decodeUnit->frameHostProcessingLatency;
|
||||
}
|
||||
|
||||
currentVideoStats.framesWithHostProcessingLatency++;
|
||||
currentVideoStats.totalHostProcessingLatency += decodeUnit->frameHostProcessingLatency;
|
||||
}
|
||||
|
||||
currentVideoStats.receivedFrames++;
|
||||
currentVideoStats.totalFrames++;
|
||||
|
||||
@@ -134,8 +186,7 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit)
|
||||
ret = [renderer submitDecodeBuffer:(unsigned char*)entry->data
|
||||
length:entry->length
|
||||
bufferType:entry->bufferType
|
||||
frameType:decodeUnit->frameType
|
||||
pts:decodeUnit->presentationTimeMs];
|
||||
decodeUnit:decodeUnit];
|
||||
if (ret != DR_OK) {
|
||||
free(data);
|
||||
return ret;
|
||||
@@ -153,8 +204,7 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit)
|
||||
return [renderer submitDecodeBuffer:data
|
||||
length:offset
|
||||
bufferType:BUFFER_TYPE_PICDATA
|
||||
frameType:decodeUnit->frameType
|
||||
pts:decodeUnit->presentationTimeMs];
|
||||
decodeUnit:decodeUnit];
|
||||
}
|
||||
|
||||
int ArInit(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig, void* context, int flags)
|
||||
@@ -171,23 +221,23 @@ int ArInit(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig, v
|
||||
want.freq = opusConfig->sampleRate;
|
||||
want.format = AUDIO_S16;
|
||||
want.channels = opusConfig->channelCount;
|
||||
|
||||
// This is supposed to be a power of 2, but our
|
||||
// frames contain a non-power of 2 number of samples,
|
||||
// so the slop would require buffering another full frame.
|
||||
// Specifying non-Po2 seems to work for our supported platforms.
|
||||
want.samples = opusConfig->samplesPerFrame;
|
||||
|
||||
audioDevice = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);
|
||||
if (audioDevice == 0) {
|
||||
Log(LOG_E, @"Failed to open audio device: %s\n", SDL_GetError());
|
||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||
ArCleanup();
|
||||
return -1;
|
||||
}
|
||||
|
||||
audioConfig = *opusConfig;
|
||||
audioFrameSize = opusConfig->samplesPerFrame * sizeof(short) * opusConfig->channelCount;
|
||||
audioBuffer = SDL_malloc(audioFrameSize);
|
||||
if (audioBuffer == NULL) {
|
||||
Log(LOG_E, @"Failed to allocate audio frame buffer");
|
||||
ArCleanup();
|
||||
return -1;
|
||||
}
|
||||
|
||||
opusDecoder = opus_multistream_decoder_create(opusConfig->sampleRate,
|
||||
opusConfig->channelCount,
|
||||
@@ -195,10 +245,18 @@ int ArInit(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig, v
|
||||
opusConfig->coupledStreams,
|
||||
opusConfig->mapping,
|
||||
&err);
|
||||
if (opusDecoder == NULL) {
|
||||
Log(LOG_E, @"Failed to create Opus decoder");
|
||||
ArCleanup();
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Start playback
|
||||
SDL_PauseAudioDevice(audioDevice, 0);
|
||||
|
||||
// Disable lowering volume of other audio streams (SDL sets AVAudioSessionCategoryOptionDuckOthers by default)
|
||||
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:nil];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -292,6 +350,27 @@ void ClConnectionStatusUpdate(int status)
|
||||
[_callbacks connectionStatusUpdate:status];
|
||||
}
|
||||
|
||||
void ClSetHdrMode(bool enabled)
|
||||
{
|
||||
[renderer setHdrMode:enabled];
|
||||
[_callbacks setHdrMode:enabled];
|
||||
}
|
||||
|
||||
void ClRumbleTriggers(uint16_t controllerNumber, uint16_t leftTriggerMotor, uint16_t rightTriggerMotor)
|
||||
{
|
||||
[_callbacks rumbleTriggers:controllerNumber leftTrigger:leftTriggerMotor rightTrigger:rightTriggerMotor];
|
||||
}
|
||||
|
||||
void ClSetMotionEventState(uint16_t controllerNumber, uint8_t motionType, uint16_t reportRateHz)
|
||||
{
|
||||
[_callbacks setMotionEventState:controllerNumber motionType:motionType reportRateHz:reportRateHz];
|
||||
}
|
||||
|
||||
void ClSetControllerLED(uint16_t controllerNumber, uint8_t r, uint8_t g, uint8_t b)
|
||||
{
|
||||
[_callbacks setControllerLed:controllerNumber r:r g:g b:b];
|
||||
}
|
||||
|
||||
-(void) terminate
|
||||
{
|
||||
// Interrupt any action blocking LiStartConnection(). This is
|
||||
@@ -324,21 +403,22 @@ void ClConnectionStatusUpdate(int status)
|
||||
videoStatsLock = [[NSLock alloc] init];
|
||||
}
|
||||
|
||||
NSString *rawAddress = [Utils addressPortStringToAddress:config.host];
|
||||
strncpy(_hostString,
|
||||
[config.host cStringUsingEncoding:NSUTF8StringEncoding],
|
||||
sizeof(_hostString));
|
||||
[rawAddress cStringUsingEncoding:NSUTF8StringEncoding],
|
||||
sizeof(_hostString) - 1);
|
||||
strncpy(_appVersionString,
|
||||
[config.appVersion cStringUsingEncoding:NSUTF8StringEncoding],
|
||||
sizeof(_appVersionString));
|
||||
sizeof(_appVersionString) - 1);
|
||||
if (config.gfeVersion != nil) {
|
||||
strncpy(_gfeVersionString,
|
||||
[config.gfeVersion cStringUsingEncoding:NSUTF8StringEncoding],
|
||||
sizeof(_gfeVersionString));
|
||||
sizeof(_gfeVersionString) - 1);
|
||||
}
|
||||
if (config.rtspSessionUrl != nil) {
|
||||
strncpy(_rtspSessionUrl,
|
||||
[config.rtspSessionUrl cStringUsingEncoding:NSUTF8StringEncoding],
|
||||
sizeof(_rtspSessionUrl));
|
||||
sizeof(_rtspSessionUrl) - 1);
|
||||
}
|
||||
|
||||
LiInitializeServerInformation(&_serverInfo);
|
||||
@@ -350,6 +430,7 @@ void ClConnectionStatusUpdate(int status)
|
||||
if (config.rtspSessionUrl != nil) {
|
||||
_serverInfo.rtspSessionUrl = _rtspSessionUrl;
|
||||
}
|
||||
_serverInfo.serverCodecModeSupport = config.serverCodecModeSupport;
|
||||
|
||||
renderer = myRenderer;
|
||||
_callbacks = callbacks;
|
||||
@@ -359,17 +440,13 @@ void ClConnectionStatusUpdate(int status)
|
||||
_streamConfig.height = config.height;
|
||||
_streamConfig.fps = config.frameRate;
|
||||
_streamConfig.bitrate = config.bitRate;
|
||||
_streamConfig.enableHdr = config.enableHdr;
|
||||
_streamConfig.supportedVideoFormats = config.supportedVideoFormats;
|
||||
_streamConfig.audioConfiguration = config.audioConfiguration;
|
||||
|
||||
// TODO: If/when video encryption is added, we'll probably want to
|
||||
// limit that to devices that support the ARMv8 AES instructions.
|
||||
_streamConfig.encryptionFlags = ENCFLG_AUDIO;
|
||||
|
||||
// Use some of the HEVC encoding efficiency improvements to
|
||||
// reduce bandwidth usage while still gaining some image
|
||||
// quality improvement.
|
||||
_streamConfig.hevcBitratePercentageMultiplier = 75;
|
||||
// Since we require iOS 12 or above, we're guaranteed to be running
|
||||
// on a 64-bit device with ARMv8 crypto instructions, so we don't
|
||||
// need to check for that here.
|
||||
_streamConfig.encryptionFlags = ENCFLG_ALL;
|
||||
|
||||
if ([Utils isActiveNetworkVPN]) {
|
||||
// Force remote streaming mode when a VPN is connected
|
||||
@@ -381,32 +458,6 @@ void ClConnectionStatusUpdate(int status)
|
||||
_streamConfig.streamingRemotely = STREAM_CFG_AUTO;
|
||||
_streamConfig.packetSize = 1392;
|
||||
}
|
||||
|
||||
// HDR implies HEVC allowed
|
||||
if (config.enableHdr) {
|
||||
config.allowHevc = YES;
|
||||
}
|
||||
|
||||
// Streaming at resolutions above 4K requires HEVC
|
||||
if (config.width > 4096 || config.height > 4096) {
|
||||
config.allowHevc = YES;
|
||||
}
|
||||
|
||||
// On iOS 11, we can use HEVC if the server supports encoding it
|
||||
// and this device has hardware decode for it (A9 and later).
|
||||
// Additionally, iPhone X had a bug which would cause video
|
||||
// to freeze after a few minutes with HEVC prior to iOS 11.3.
|
||||
// As a result, we will only use HEVC on iOS 11.3 or later.
|
||||
if (@available(iOS 11.3, tvOS 11.3, *)) {
|
||||
_streamConfig.supportsHevc =
|
||||
#if !TARGET_OS_TV
|
||||
config.allowHevc &&
|
||||
#endif
|
||||
VTIsHardwareDecodeSupported(kCMVideoCodecType_HEVC);
|
||||
}
|
||||
|
||||
// HEVC must be supported when HDR is enabled
|
||||
assert(!_streamConfig.enableHdr || _streamConfig.supportsHevc);
|
||||
|
||||
memcpy(_streamConfig.remoteInputAesKey, [config.riKey bytes], [config.riKey length]);
|
||||
memset(_streamConfig.remoteInputAesIv, 0, 16);
|
||||
@@ -415,18 +466,11 @@ void ClConnectionStatusUpdate(int status)
|
||||
|
||||
LiInitializeVideoCallbacks(&_drCallbacks);
|
||||
_drCallbacks.setup = DrDecoderSetup;
|
||||
_drCallbacks.cleanup = DrCleanup;
|
||||
_drCallbacks.submitDecodeUnit = DrSubmitDecodeUnit;
|
||||
|
||||
// RFI doesn't work properly with HEVC on iOS 11 with an iPhone SE (at least)
|
||||
// It doesnt work on macOS either, tested with Network Link Conditioner.
|
||||
// RFI seems to be broken at all resolutions on the Apple TV 4K (1st gen)
|
||||
// on tvOS 14.5.
|
||||
_drCallbacks.capabilities =
|
||||
#if !TARGET_OS_TV
|
||||
CAPABILITY_REFERENCE_FRAME_INVALIDATION_AVC |
|
||||
#endif
|
||||
CAPABILITY_DIRECT_SUBMIT;
|
||||
_drCallbacks.start = DrStart;
|
||||
_drCallbacks.stop = DrStop;
|
||||
_drCallbacks.capabilities = CAPABILITY_PULL_RENDERER |
|
||||
CAPABILITY_REFERENCE_FRAME_INVALIDATION_HEVC |
|
||||
CAPABILITY_REFERENCE_FRAME_INVALIDATION_AV1;
|
||||
|
||||
LiInitializeAudioCallbacks(&_arCallbacks);
|
||||
_arCallbacks.init = ArInit;
|
||||
@@ -443,6 +487,10 @@ void ClConnectionStatusUpdate(int status)
|
||||
_clCallbacks.logMessage = ClLogMessage;
|
||||
_clCallbacks.rumble = ClRumble;
|
||||
_clCallbacks.connectionStatusUpdate = ClConnectionStatusUpdate;
|
||||
_clCallbacks.setHdrMode = ClSetHdrMode;
|
||||
_clCallbacks.rumbleTriggers = ClRumbleTriggers;
|
||||
_clCallbacks.setMotionEventState = ClSetMotionEventState;
|
||||
_clCallbacks.setControllerLED = ClSetControllerLED;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
- (void) launchFailed:(NSString*)message;
|
||||
- (void) rumble:(unsigned short)controllerNumber lowFreqMotor:(unsigned short)lowFreqMotor highFreqMotor:(unsigned short)highFreqMotor;
|
||||
- (void) connectionStatusUpdate:(int)status;
|
||||
- (void) setHdrMode:(bool)enabled;
|
||||
- (void) rumbleTriggers:(uint16_t)controllerNumber leftTrigger:(uint16_t)leftTrigger rightTrigger:(uint16_t)rightTrigger;
|
||||
- (void) setMotionEventState:(uint16_t)controllerNumber motionType:(uint8_t)motionType reportRateHz:(uint16_t)reportRateHz;
|
||||
- (void) setControllerLed:(uint16_t)controllerNumber r:(uint8_t)r g:(uint8_t)g b:(uint8_t)b;
|
||||
- (void) videoContentShown;
|
||||
|
||||
@end
|
||||
|
||||
@@ -9,11 +9,13 @@
|
||||
@interface StreamConfiguration : NSObject
|
||||
|
||||
@property NSString* host;
|
||||
@property unsigned short httpsPort;
|
||||
@property NSString* appVersion;
|
||||
@property NSString* gfeVersion;
|
||||
@property NSString* appID;
|
||||
@property NSString* appName;
|
||||
@property NSString* rtspSessionUrl;
|
||||
@property int serverCodecModeSupport;
|
||||
@property int width;
|
||||
@property int height;
|
||||
@property int frameRate;
|
||||
@@ -23,10 +25,11 @@
|
||||
@property int gamepadMask;
|
||||
@property BOOL optimizeGameSettings;
|
||||
@property BOOL playAudioOnPC;
|
||||
@property BOOL swapABXYButtons;
|
||||
@property int audioConfiguration;
|
||||
@property BOOL enableHdr;
|
||||
@property int supportedVideoFormats;
|
||||
@property BOOL multiController;
|
||||
@property BOOL allowHevc;
|
||||
@property BOOL useFramePacing;
|
||||
@property NSData* serverCert;
|
||||
|
||||
@end
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
#import "StreamConfiguration.h"
|
||||
|
||||
@implementation StreamConfiguration
|
||||
@synthesize host, appID, width, height, frameRate, bitRate, riKeyId, riKey, gamepadMask, appName, optimizeGameSettings, playAudioOnPC, audioConfiguration, enableHdr, multiController, allowHevc, serverCert, rtspSessionUrl;
|
||||
@synthesize host, httpsPort, appID, width, height, frameRate, bitRate, riKeyId, riKey, gamepadMask, appName, optimizeGameSettings, playAudioOnPC, swapABXYButtons, audioConfiguration, supportedVideoFormats, multiController, serverCert, rtspSessionUrl, serverCodecModeSupport;
|
||||
@end
|
||||
|
||||
@@ -39,10 +39,8 @@
|
||||
|
||||
- (void)main {
|
||||
[CryptoManager generateKeyPairUsingSSL];
|
||||
NSString* uniqueId = [IdManager getUniqueId];
|
||||
|
||||
HttpManager* hMan = [[HttpManager alloc] initWithHost:_config.host
|
||||
uniqueId:uniqueId
|
||||
HttpManager* hMan = [[HttpManager alloc] initWithAddress:_config.host httpsPort:_config.httpsPort
|
||||
serverCert:_config.serverCert];
|
||||
|
||||
ServerInfoResponse* serverInfoResp = [[ServerInfoResponse alloc] init];
|
||||
@@ -67,7 +65,8 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (_config.width > 4096 || _config.height > 4096) {
|
||||
// Only perform this check on GFE (as indicated by MJOLNIR in state value)
|
||||
if ((_config.width > 4096 || _config.height > 4096) && [serverState containsString:@"MJOLNIR"]) {
|
||||
// Pascal added support for 8K HEVC encoding support. Maxwell 2 could encode HEVC but only up to 4K.
|
||||
// We can't directly identify Pascal, but we can look for HEVC Main10 which was added in the same generation.
|
||||
NSString* codecSupport = [serverInfoResp getStringTag:@"ServerCodecModeSupport"];
|
||||
@@ -77,6 +76,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Populate the config's version fields from serverinfo
|
||||
_config.appVersion = appversion;
|
||||
_config.gfeVersion = gfeVersion;
|
||||
|
||||
// resumeApp and launchApp handle calling launchFailed
|
||||
NSString* sessionUrl;
|
||||
if ([serverState hasSuffix:@"_SERVER_BUSY"]) {
|
||||
@@ -94,13 +97,9 @@
|
||||
// Populate RTSP session URL from launch/resume response
|
||||
_config.rtspSessionUrl = sessionUrl;
|
||||
|
||||
// Populate the config's version fields from serverinfo
|
||||
_config.appVersion = appversion;
|
||||
_config.gfeVersion = gfeVersion;
|
||||
|
||||
// Initializing the renderer must be done on the main thread
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
VideoDecoderRenderer* renderer = [[VideoDecoderRenderer alloc] initWithView:self->_renderView callbacks:self->_callbacks];
|
||||
VideoDecoderRenderer* renderer = [[VideoDecoderRenderer alloc] initWithView:self->_renderView callbacks:self->_callbacks streamAspectRatio:(float)self->_config.width / (float)self->_config.height useFramePacing:self->_config.useFramePacing];
|
||||
self->_connection = [[Connection alloc] initWithConfig:self->_config renderer:renderer connectionCallbacks:self->_callbacks];
|
||||
NSOperationQueue* opQueue = [[NSOperationQueue alloc] init];
|
||||
[opQueue addOperation:self->_connection];
|
||||
@@ -114,7 +113,7 @@
|
||||
|
||||
- (BOOL) launchApp:(HttpManager*)hMan receiveSessionUrl:(NSString**)sessionUrl {
|
||||
HttpResponse* launchResp = [[HttpResponse alloc] init];
|
||||
[hMan executeRequestSynchronously:[HttpRequest requestForResponse:launchResp withUrlRequest:[hMan newLaunchRequest:_config]]];
|
||||
[hMan executeRequestSynchronously:[HttpRequest requestForResponse:launchResp withUrlRequest:[hMan newLaunchOrResumeRequest:@"launch" config:_config]]];
|
||||
NSString *gameSession = [launchResp getStringTag:@"gamesession"];
|
||||
if (![launchResp isStatusOk]) {
|
||||
[_callbacks launchFailed:launchResp.statusMessage];
|
||||
@@ -132,7 +131,7 @@
|
||||
|
||||
- (BOOL) resumeApp:(HttpManager*)hMan receiveSessionUrl:(NSString**)sessionUrl {
|
||||
HttpResponse* resumeResp = [[HttpResponse alloc] init];
|
||||
[hMan executeRequestSynchronously:[HttpRequest requestForResponse:resumeResp withUrlRequest:[hMan newResumeRequest:_config]]];
|
||||
[hMan executeRequestSynchronously:[HttpRequest requestForResponse:resumeResp withUrlRequest:[hMan newLaunchOrResumeRequest:@"resume" config:_config]]];
|
||||
NSString* resume = [resumeResp getStringTag:@"resume"];
|
||||
if (![resumeResp isStatusOk]) {
|
||||
[_callbacks launchFailed:resumeResp.statusMessage];
|
||||
@@ -168,14 +167,26 @@
|
||||
latencyString = @"N/A";
|
||||
}
|
||||
|
||||
NSString* hostProcessingString;
|
||||
if (stats.framesWithHostProcessingLatency != 0) {
|
||||
hostProcessingString = [NSString stringWithFormat:@"\nHost processing latency min/max/avg: %.1f/%.1f/%.1f ms",
|
||||
stats.minHostProcessingLatency / 10.f,
|
||||
stats.maxHostProcessingLatency / 10.f,
|
||||
(float)stats.totalHostProcessingLatency / stats.framesWithHostProcessingLatency / 10.f];
|
||||
}
|
||||
else {
|
||||
hostProcessingString = @"";
|
||||
}
|
||||
|
||||
float interval = stats.endTime - stats.startTime;
|
||||
return [NSString stringWithFormat:@"Video stream: %dx%d %.2f FPS (Codec: %@)\nFrames dropped by your network connection: %.2f%%\nAverage network latency: %@",
|
||||
return [NSString stringWithFormat:@"Video stream: %dx%d %.2f FPS (Codec: %@)\nFrames dropped by your network connection: %.2f%%\nAverage network latency: %@%@",
|
||||
_config.width,
|
||||
_config.height,
|
||||
stats.totalFrames / interval,
|
||||
[_connection getActiveCodecName],
|
||||
stats.networkDroppedFrames / interval,
|
||||
latencyString];
|
||||
latencyString,
|
||||
hostProcessingString];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -10,16 +10,17 @@
|
||||
|
||||
#import "ConnectionCallbacks.h"
|
||||
|
||||
#include "Limelight.h"
|
||||
|
||||
@interface VideoDecoderRenderer : NSObject
|
||||
|
||||
- (id)initWithView:(UIView*)view callbacks:(id<ConnectionCallbacks>)callbacks;
|
||||
- (id)initWithView:(UIView*)view callbacks:(id<ConnectionCallbacks>)callbacks streamAspectRatio:(float)aspectRatio useFramePacing:(BOOL)useFramePacing;
|
||||
|
||||
- (void)setupWithVideoFormat:(int)videoFormat refreshRate:(int)refreshRate;
|
||||
- (void)setupWithVideoFormat:(int)videoFormat width:(int)videoWidth height:(int)videoHeight frameRate:(int)frameRate;
|
||||
- (void)start;
|
||||
- (void)stop;
|
||||
- (void)setHdrMode:(BOOL)enabled;
|
||||
|
||||
- (void)cleanup;
|
||||
|
||||
- (void)updateBufferForRange:(CMBlockBufferRef)existingBuffer data:(unsigned char *)data offset:(int)offset length:(int)nalLength;
|
||||
|
||||
- (int)submitDecodeBuffer:(unsigned char *)data length:(int)length bufferType:(int)bufferType frameType:(int)frameType pts:(unsigned int)pts;
|
||||
- (int)submitDecodeBuffer:(unsigned char *)data length:(int)length bufferType:(int)bufferType decodeUnit:(PDECODE_UNIT)du;
|
||||
|
||||
@end
|
||||
|
||||
@@ -9,20 +9,32 @@
|
||||
#import "VideoDecoderRenderer.h"
|
||||
#import "StreamView.h"
|
||||
|
||||
#include "Limelight.h"
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavcodec/cbs.h>
|
||||
#include <libavcodec/cbs_av1.h>
|
||||
#include <libavformat/avio.h>
|
||||
#include <libavutil/mem.h>
|
||||
|
||||
// Private libavformat API for writing the AV1 Codec Configuration Box
|
||||
extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size,
|
||||
int write_seq_header);
|
||||
|
||||
@implementation VideoDecoderRenderer {
|
||||
StreamView* _view;
|
||||
id<ConnectionCallbacks> _callbacks;
|
||||
float _streamAspectRatio;
|
||||
|
||||
AVSampleBufferDisplayLayer* displayLayer;
|
||||
Boolean waitingForSps, waitingForPps, waitingForVps;
|
||||
int videoFormat;
|
||||
int frameRate;
|
||||
|
||||
NSData *spsData, *ppsData, *vpsData;
|
||||
NSMutableArray *parameterSetBuffers;
|
||||
NSData *masteringDisplayColorVolume;
|
||||
NSData *contentLightLevelInfo;
|
||||
CMVideoFormatDescriptionRef formatDesc;
|
||||
|
||||
CADisplayLink* _displayLink;
|
||||
BOOL framePacing;
|
||||
}
|
||||
|
||||
- (void)reinitializeDisplayLayer
|
||||
@@ -30,12 +42,23 @@
|
||||
CALayer *oldLayer = displayLayer;
|
||||
|
||||
displayLayer = [[AVSampleBufferDisplayLayer alloc] init];
|
||||
displayLayer.bounds = _view.bounds;
|
||||
displayLayer.backgroundColor = [UIColor blackColor].CGColor;
|
||||
|
||||
// Ensure the AVSampleBufferDisplayLayer is sized to preserve the aspect ratio
|
||||
// of the video stream. We used to use AVLayerVideoGravityResizeAspect, but that
|
||||
// respects the PAR encoded in the SPS which causes our computed video-relative
|
||||
// touch location to be wrong in StreamView if the aspect ratio of the host
|
||||
// desktop doesn't match the aspect ratio of the stream.
|
||||
CGSize videoSize;
|
||||
if (_view.bounds.size.width > _view.bounds.size.height * _streamAspectRatio) {
|
||||
videoSize = CGSizeMake(_view.bounds.size.height * _streamAspectRatio, _view.bounds.size.height);
|
||||
} else {
|
||||
videoSize = CGSizeMake(_view.bounds.size.width, _view.bounds.size.width / _streamAspectRatio);
|
||||
}
|
||||
displayLayer.position = CGPointMake(CGRectGetMidX(_view.bounds), CGRectGetMidY(_view.bounds));
|
||||
displayLayer.videoGravity = AVLayerVideoGravityResizeAspect;
|
||||
|
||||
displayLayer.bounds = CGRectMake(0, 0, videoSize.width, videoSize.height);
|
||||
displayLayer.videoGravity = AVLayerVideoGravityResize;
|
||||
|
||||
// Hide the layer until we get an IDR frame. This ensures we
|
||||
// can see the loading progress label as the stream is starting.
|
||||
displayLayer.hidden = YES;
|
||||
@@ -48,215 +71,442 @@
|
||||
[_view.layer addSublayer:displayLayer];
|
||||
}
|
||||
|
||||
// We need some parameter sets before we can properly start decoding frames
|
||||
waitingForSps = true;
|
||||
spsData = nil;
|
||||
waitingForPps = true;
|
||||
ppsData = nil;
|
||||
waitingForVps = true;
|
||||
vpsData = nil;
|
||||
|
||||
if (formatDesc != nil) {
|
||||
CFRelease(formatDesc);
|
||||
formatDesc = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (id)initWithView:(StreamView*)view callbacks:(id<ConnectionCallbacks>)callbacks
|
||||
- (id)initWithView:(StreamView*)view callbacks:(id<ConnectionCallbacks>)callbacks streamAspectRatio:(float)aspectRatio useFramePacing:(BOOL)useFramePacing
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
_view = view;
|
||||
_callbacks = callbacks;
|
||||
_streamAspectRatio = aspectRatio;
|
||||
framePacing = useFramePacing;
|
||||
|
||||
parameterSetBuffers = [[NSMutableArray alloc] init];
|
||||
|
||||
[self reinitializeDisplayLayer];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setupWithVideoFormat:(int)videoFormat refreshRate:(int)refreshRate
|
||||
- (void)setupWithVideoFormat:(int)videoFormat width:(int)videoWidth height:(int)videoHeight frameRate:(int)frameRate
|
||||
{
|
||||
self->videoFormat = videoFormat;
|
||||
|
||||
if (refreshRate > 60) {
|
||||
// HACK: We seem to just get 60 Hz screen updates even with a 120 FPS stream if
|
||||
// we don't set preferredFramesPerSecond somewhere. Since we're a UIKit view, we
|
||||
// have to use CADisplayLink for that. See https://github.com/moonlight-stream/moonlight-ios/issues/372
|
||||
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkCallback:)];
|
||||
if (@available(iOS 10.0, tvOS 10.0, *)) {
|
||||
_displayLink.preferredFramesPerSecond = refreshRate;
|
||||
}
|
||||
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)displayLinkCallback:(CADisplayLink *)sender
|
||||
{
|
||||
// No-op - rendering done in submitDecodeBuffer
|
||||
self->frameRate = frameRate;
|
||||
}
|
||||
|
||||
- (void)cleanup
|
||||
- (void)start
|
||||
{
|
||||
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkCallback:)];
|
||||
if (@available(iOS 15.0, tvOS 15.0, *)) {
|
||||
_displayLink.preferredFrameRateRange = CAFrameRateRangeMake(self->frameRate, self->frameRate, self->frameRate);
|
||||
}
|
||||
else {
|
||||
_displayLink.preferredFramesPerSecond = self->frameRate;
|
||||
}
|
||||
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
|
||||
}
|
||||
|
||||
// TODO: Refactor this
|
||||
int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit);
|
||||
|
||||
- (void)displayLinkCallback:(CADisplayLink *)sender
|
||||
{
|
||||
VIDEO_FRAME_HANDLE handle;
|
||||
PDECODE_UNIT du;
|
||||
|
||||
while (LiPollNextVideoFrame(&handle, &du)) {
|
||||
LiCompleteVideoFrame(handle, DrSubmitDecodeUnit(du));
|
||||
|
||||
if (framePacing) {
|
||||
// Calculate the actual display refresh rate
|
||||
double displayRefreshRate = 1 / (_displayLink.targetTimestamp - _displayLink.timestamp);
|
||||
|
||||
// Only pace frames if the display refresh rate is >= 90% of our stream frame rate.
|
||||
// Battery saver, accessibility settings, or device thermals can cause the actual
|
||||
// refresh rate of the display to drop below the physical maximum.
|
||||
if (displayRefreshRate >= frameRate * 0.9f) {
|
||||
// Keep one pending frame to smooth out gaps due to
|
||||
// network jitter at the cost of 1 frame of latency
|
||||
if (LiGetPendingVideoFrames() == 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)stop
|
||||
{
|
||||
[_displayLink invalidate];
|
||||
}
|
||||
|
||||
#define FRAME_START_PREFIX_SIZE 4
|
||||
#define NALU_START_PREFIX_SIZE 3
|
||||
#define NAL_LENGTH_PREFIX_SIZE 4
|
||||
|
||||
- (Boolean)readyForPictureData
|
||||
- (void)updateAnnexBBufferForRange:(CMBlockBufferRef)frameBuffer dataBlock:(CMBlockBufferRef)dataBuffer offset:(int)offset length:(int)nalLength
|
||||
{
|
||||
if (videoFormat & VIDEO_FORMAT_MASK_H264) {
|
||||
return !waitingForSps && !waitingForPps;
|
||||
OSStatus status;
|
||||
size_t oldOffset = CMBlockBufferGetDataLength(frameBuffer);
|
||||
|
||||
// Append a 4 byte buffer to the frame block for the length prefix
|
||||
status = CMBlockBufferAppendMemoryBlock(frameBuffer, NULL,
|
||||
NAL_LENGTH_PREFIX_SIZE,
|
||||
kCFAllocatorDefault, NULL, 0,
|
||||
NAL_LENGTH_PREFIX_SIZE, 0);
|
||||
if (status != noErr) {
|
||||
Log(LOG_E, @"CMBlockBufferAppendMemoryBlock failed: %d", (int)status);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
// H.265 requires VPS in addition to SPS and PPS
|
||||
return !waitingForVps && !waitingForSps && !waitingForPps;
|
||||
|
||||
// Write the length prefix to the new buffer
|
||||
const int dataLength = nalLength - NALU_START_PREFIX_SIZE;
|
||||
const uint8_t lengthBytes[] = {(uint8_t)(dataLength >> 24), (uint8_t)(dataLength >> 16),
|
||||
(uint8_t)(dataLength >> 8), (uint8_t)dataLength};
|
||||
status = CMBlockBufferReplaceDataBytes(lengthBytes, frameBuffer,
|
||||
oldOffset, NAL_LENGTH_PREFIX_SIZE);
|
||||
if (status != noErr) {
|
||||
Log(LOG_E, @"CMBlockBufferReplaceDataBytes failed: %d", (int)status);
|
||||
return;
|
||||
}
|
||||
|
||||
// Attach the data buffer to the frame buffer by reference
|
||||
status = CMBlockBufferAppendBufferReference(frameBuffer, dataBuffer, offset + NALU_START_PREFIX_SIZE, dataLength, 0);
|
||||
if (status != noErr) {
|
||||
Log(LOG_E, @"CMBlockBufferAppendBufferReference failed: %d", (int)status);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateBufferForRange:(CMBlockBufferRef)existingBuffer data:(unsigned char *)data offset:(int)offset length:(int)nalLength
|
||||
{
|
||||
OSStatus status;
|
||||
size_t oldOffset = CMBlockBufferGetDataLength(existingBuffer);
|
||||
- (NSData*)getAv1CodecConfigurationBox:(NSData*)frameData {
|
||||
AVIOContext* ioctx = NULL;
|
||||
int err;
|
||||
|
||||
// If we're at index 1 (first NALU in frame), enqueue this buffer to the memory block
|
||||
// so it can handle freeing it when the block buffer is destroyed
|
||||
if (offset == 1) {
|
||||
int dataLength = nalLength - NALU_START_PREFIX_SIZE;
|
||||
|
||||
// Pass the real buffer pointer directly (no offset)
|
||||
// This will give it to the block buffer to free when it's released.
|
||||
// All further calls to CMBlockBufferAppendMemoryBlock will do so
|
||||
// at an offset and will not be asking the buffer to be freed.
|
||||
status = CMBlockBufferAppendMemoryBlock(existingBuffer, data,
|
||||
nalLength + 1, // Add 1 for the offset we decremented
|
||||
kCFAllocatorDefault,
|
||||
NULL, 0, nalLength + 1, 0);
|
||||
if (status != noErr) {
|
||||
Log(LOG_E, @"CMBlockBufferReplaceDataBytes failed: %d", (int)status);
|
||||
return;
|
||||
}
|
||||
|
||||
// Write the length prefix to existing buffer
|
||||
const uint8_t lengthBytes[] = {(uint8_t)(dataLength >> 24), (uint8_t)(dataLength >> 16),
|
||||
(uint8_t)(dataLength >> 8), (uint8_t)dataLength};
|
||||
status = CMBlockBufferReplaceDataBytes(lengthBytes, existingBuffer,
|
||||
oldOffset, NAL_LENGTH_PREFIX_SIZE);
|
||||
if (status != noErr) {
|
||||
Log(LOG_E, @"CMBlockBufferReplaceDataBytes failed: %d", (int)status);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Append a 4 byte buffer to this block for the length prefix
|
||||
status = CMBlockBufferAppendMemoryBlock(existingBuffer, NULL,
|
||||
NAL_LENGTH_PREFIX_SIZE,
|
||||
kCFAllocatorDefault, NULL, 0,
|
||||
NAL_LENGTH_PREFIX_SIZE, 0);
|
||||
if (status != noErr) {
|
||||
Log(LOG_E, @"CMBlockBufferAppendMemoryBlock failed: %d", (int)status);
|
||||
return;
|
||||
}
|
||||
|
||||
// Write the length prefix to the new buffer
|
||||
int dataLength = nalLength - NALU_START_PREFIX_SIZE;
|
||||
const uint8_t lengthBytes[] = {(uint8_t)(dataLength >> 24), (uint8_t)(dataLength >> 16),
|
||||
(uint8_t)(dataLength >> 8), (uint8_t)dataLength};
|
||||
status = CMBlockBufferReplaceDataBytes(lengthBytes, existingBuffer,
|
||||
oldOffset, NAL_LENGTH_PREFIX_SIZE);
|
||||
if (status != noErr) {
|
||||
Log(LOG_E, @"CMBlockBufferReplaceDataBytes failed: %d", (int)status);
|
||||
return;
|
||||
}
|
||||
|
||||
// Attach the buffer by reference to the block buffer
|
||||
status = CMBlockBufferAppendMemoryBlock(existingBuffer, &data[offset+NALU_START_PREFIX_SIZE],
|
||||
dataLength,
|
||||
kCFAllocatorNull, // Don't deallocate data on free
|
||||
NULL, 0, dataLength, 0);
|
||||
if (status != noErr) {
|
||||
Log(LOG_E, @"CMBlockBufferReplaceDataBytes failed: %d", (int)status);
|
||||
return;
|
||||
}
|
||||
err = avio_open_dyn_buf(&ioctx);
|
||||
if (err < 0) {
|
||||
Log(LOG_E, @"avio_open_dyn_buf() failed: %d", err);
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Submit the IDR frame to write the av1C blob
|
||||
err = ff_isom_write_av1c(ioctx, (uint8_t*)frameData.bytes, (int)frameData.length, 1);
|
||||
if (err < 0) {
|
||||
Log(LOG_E, @"ff_isom_write_av1c() failed: %d", err);
|
||||
// Fall-through to close and free buffer
|
||||
}
|
||||
|
||||
// Close the dynbuf and get the underlying buffer back (which we must free)
|
||||
uint8_t* av1cBuf = NULL;
|
||||
int av1cBufLen = avio_close_dyn_buf(ioctx, &av1cBuf);
|
||||
|
||||
Log(LOG_I, @"av1C block is %d bytes", av1cBufLen);
|
||||
|
||||
// Only return data if ff_isom_write_av1c() was successful
|
||||
NSData* data = nil;
|
||||
if (err >= 0 && av1cBufLen > 0) {
|
||||
data = [NSData dataWithBytes:av1cBuf length:av1cBufLen];
|
||||
}
|
||||
else {
|
||||
data = nil;
|
||||
}
|
||||
|
||||
av_free(av1cBuf);
|
||||
return data;
|
||||
}
|
||||
|
||||
// Much of this logic comes from Chrome
|
||||
- (CMVideoFormatDescriptionRef)createAV1FormatDescriptionForIDRFrame:(NSData*)frameData {
|
||||
NSMutableDictionary* extensions = [[NSMutableDictionary alloc] init];
|
||||
|
||||
CodedBitstreamContext* cbsCtx = NULL;
|
||||
int err = ff_cbs_init(&cbsCtx, AV_CODEC_ID_AV1, NULL);
|
||||
if (err < 0) {
|
||||
Log(LOG_E, @"ff_cbs_init() failed: %d", err);
|
||||
return nil;
|
||||
}
|
||||
|
||||
AVPacket avPacket = {};
|
||||
avPacket.data = (uint8_t*)frameData.bytes;
|
||||
avPacket.size = (int)frameData.length;
|
||||
|
||||
// Read the sequence header OBU
|
||||
CodedBitstreamFragment cbsFrag = {};
|
||||
err = ff_cbs_read_packet(cbsCtx, &cbsFrag, &avPacket);
|
||||
if (err < 0) {
|
||||
Log(LOG_E, @"ff_cbs_read_packet() failed: %d", err);
|
||||
ff_cbs_close(&cbsCtx);
|
||||
return nil;
|
||||
}
|
||||
|
||||
#define SET_CFSTR_EXTENSION(key, value) extensions[(__bridge NSString*)key] = (__bridge NSString*)(value)
|
||||
#define SET_EXTENSION(key, value) extensions[(__bridge NSString*)key] = (value)
|
||||
|
||||
SET_EXTENSION(kCMFormatDescriptionExtension_FormatName, @"av01");
|
||||
|
||||
// We use the value for YUV without alpha, same as Chrome
|
||||
// https://developer.apple.com/library/archive/qa/qa1183/_index.html
|
||||
SET_EXTENSION(kCMFormatDescriptionExtension_Depth, @24);
|
||||
|
||||
CodedBitstreamAV1Context* bitstreamCtx = (CodedBitstreamAV1Context*)cbsCtx->priv_data;
|
||||
AV1RawSequenceHeader* seqHeader = bitstreamCtx->sequence_header;
|
||||
if (seqHeader == NULL) {
|
||||
Log(LOG_E, @"AV1 sequence header not found in IDR frame!");
|
||||
ff_cbs_fragment_free(&cbsFrag);
|
||||
ff_cbs_close(&cbsCtx);
|
||||
return nil;
|
||||
}
|
||||
|
||||
switch (seqHeader->color_config.color_primaries) {
|
||||
case 1: // CP_BT_709
|
||||
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_ColorPrimaries,
|
||||
kCMFormatDescriptionColorPrimaries_ITU_R_709_2);
|
||||
break;
|
||||
|
||||
case 6: // CP_BT_601
|
||||
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_ColorPrimaries,
|
||||
kCMFormatDescriptionColorPrimaries_SMPTE_C);
|
||||
break;
|
||||
|
||||
case 9: // CP_BT_2020
|
||||
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_ColorPrimaries,
|
||||
kCMFormatDescriptionColorPrimaries_ITU_R_2020);
|
||||
break;
|
||||
|
||||
default:
|
||||
Log(LOG_W, @"Unsupported color_primaries value: %d", seqHeader->color_config.color_primaries);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (seqHeader->color_config.transfer_characteristics) {
|
||||
case 1: // TC_BT_709
|
||||
case 6: // TC_BT_601
|
||||
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_TransferFunction,
|
||||
kCMFormatDescriptionTransferFunction_ITU_R_709_2);
|
||||
break;
|
||||
|
||||
case 7: // TC_SMPTE_240
|
||||
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_TransferFunction,
|
||||
kCMFormatDescriptionTransferFunction_SMPTE_240M_1995);
|
||||
break;
|
||||
|
||||
case 8: // TC_LINEAR
|
||||
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_TransferFunction,
|
||||
kCMFormatDescriptionTransferFunction_Linear);
|
||||
break;
|
||||
|
||||
case 14: // TC_BT_2020_10_BIT
|
||||
case 15: // TC_BT_2020_12_BIT
|
||||
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_TransferFunction,
|
||||
kCMFormatDescriptionTransferFunction_ITU_R_2020);
|
||||
break;
|
||||
|
||||
case 16: // TC_SMPTE_2084
|
||||
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_TransferFunction,
|
||||
kCMFormatDescriptionTransferFunction_SMPTE_ST_2084_PQ);
|
||||
break;
|
||||
|
||||
case 17: // TC_HLG
|
||||
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_TransferFunction,
|
||||
kCMFormatDescriptionTransferFunction_ITU_R_2100_HLG);
|
||||
break;
|
||||
|
||||
default:
|
||||
Log(LOG_W, @"Unsupported transfer_characteristics value: %d", seqHeader->color_config.transfer_characteristics);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (seqHeader->color_config.matrix_coefficients) {
|
||||
case 1: // MC_BT_709
|
||||
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_YCbCrMatrix,
|
||||
kCMFormatDescriptionYCbCrMatrix_ITU_R_709_2);
|
||||
break;
|
||||
|
||||
case 6: // MC_BT_601
|
||||
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_YCbCrMatrix,
|
||||
kCMFormatDescriptionYCbCrMatrix_ITU_R_601_4);
|
||||
break;
|
||||
|
||||
case 7: // MC_SMPTE_240
|
||||
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_YCbCrMatrix,
|
||||
kCMFormatDescriptionYCbCrMatrix_SMPTE_240M_1995);
|
||||
break;
|
||||
|
||||
case 9: // MC_BT_2020_NCL
|
||||
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_YCbCrMatrix,
|
||||
kCMFormatDescriptionYCbCrMatrix_ITU_R_2020);
|
||||
break;
|
||||
|
||||
default:
|
||||
Log(LOG_W, @"Unsupported matrix_coefficients value: %d", seqHeader->color_config.matrix_coefficients);
|
||||
break;
|
||||
}
|
||||
|
||||
SET_EXTENSION(kCMFormatDescriptionExtension_FullRangeVideo, @(seqHeader->color_config.color_range == 1));
|
||||
|
||||
// Progressive content
|
||||
SET_EXTENSION(kCMFormatDescriptionExtension_FieldCount, @(1));
|
||||
|
||||
switch (seqHeader->color_config.chroma_sample_position) {
|
||||
case 1: // CSP_VERTICAL
|
||||
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_ChromaLocationTopField,
|
||||
kCMFormatDescriptionChromaLocation_Left);
|
||||
break;
|
||||
|
||||
case 2: // CSP_COLOCATED
|
||||
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_ChromaLocationTopField,
|
||||
kCMFormatDescriptionChromaLocation_TopLeft);
|
||||
break;
|
||||
|
||||
default:
|
||||
Log(LOG_W, @"Unsupported chroma_sample_position value: %d", seqHeader->color_config.chroma_sample_position);
|
||||
break;
|
||||
}
|
||||
|
||||
if (contentLightLevelInfo) {
|
||||
SET_EXTENSION(kCMFormatDescriptionExtension_ContentLightLevelInfo, contentLightLevelInfo);
|
||||
}
|
||||
|
||||
if (masteringDisplayColorVolume) {
|
||||
SET_EXTENSION(kCMFormatDescriptionExtension_MasteringDisplayColorVolume, masteringDisplayColorVolume);
|
||||
}
|
||||
|
||||
// Referenced the VP9 code in Chrome that performs a similar function
|
||||
// https://source.chromium.org/chromium/chromium/src/+/main:media/gpu/mac/vt_config_util.mm;drc=977dc02c431b4979e34c7792bc3d646f649dacb4;l=155
|
||||
extensions[(__bridge NSString*)kCMFormatDescriptionExtension_SampleDescriptionExtensionAtoms] =
|
||||
@{
|
||||
@"av1C" : [self getAv1CodecConfigurationBox:frameData],
|
||||
};
|
||||
extensions[@"BitsPerComponent"] = @(bitstreamCtx->bit_depth);
|
||||
|
||||
#undef SET_EXTENSION
|
||||
#undef SET_CFSTR_EXTENSION
|
||||
|
||||
// AV1 doesn't have a special format description function like H.264 and HEVC have, so we just use the generic one
|
||||
CMVideoFormatDescriptionRef formatDesc = NULL;
|
||||
OSStatus status = CMVideoFormatDescriptionCreate(kCFAllocatorDefault, kCMVideoCodecType_AV1,
|
||||
bitstreamCtx->frame_width, bitstreamCtx->frame_height,
|
||||
(__bridge CFDictionaryRef)extensions,
|
||||
&formatDesc);
|
||||
if (status != noErr) {
|
||||
Log(LOG_E, @"Failed to create AV1 format description: %d", (int)status);
|
||||
formatDesc = NULL;
|
||||
}
|
||||
|
||||
ff_cbs_fragment_free(&cbsFrag);
|
||||
ff_cbs_close(&cbsCtx);
|
||||
return formatDesc;
|
||||
}
|
||||
|
||||
// This function must free data for bufferType == BUFFER_TYPE_PICDATA
|
||||
- (int)submitDecodeBuffer:(unsigned char *)data length:(int)length bufferType:(int)bufferType frameType:(int)frameType pts:(unsigned int)pts
|
||||
- (int)submitDecodeBuffer:(unsigned char *)data length:(int)length bufferType:(int)bufferType decodeUnit:(PDECODE_UNIT)du
|
||||
{
|
||||
OSStatus status;
|
||||
|
||||
if (bufferType != BUFFER_TYPE_PICDATA) {
|
||||
if (bufferType == BUFFER_TYPE_VPS) {
|
||||
Log(LOG_I, @"Got VPS");
|
||||
vpsData = [NSData dataWithBytes:&data[FRAME_START_PREFIX_SIZE] length:length - FRAME_START_PREFIX_SIZE];
|
||||
waitingForVps = false;
|
||||
|
||||
// We got a new VPS so wait for a new SPS to match it
|
||||
waitingForSps = true;
|
||||
}
|
||||
else if (bufferType == BUFFER_TYPE_SPS) {
|
||||
Log(LOG_I, @"Got SPS");
|
||||
spsData = [NSData dataWithBytes:&data[FRAME_START_PREFIX_SIZE] length:length - FRAME_START_PREFIX_SIZE];
|
||||
waitingForSps = false;
|
||||
|
||||
// We got a new SPS so wait for a new PPS to match it
|
||||
waitingForPps = true;
|
||||
} else if (bufferType == BUFFER_TYPE_PPS) {
|
||||
Log(LOG_I, @"Got PPS");
|
||||
ppsData = [NSData dataWithBytes:&data[FRAME_START_PREFIX_SIZE] length:length - FRAME_START_PREFIX_SIZE];
|
||||
waitingForPps = false;
|
||||
}
|
||||
|
||||
// See if we've got all the parameter sets we need for our video format
|
||||
if ([self readyForPictureData]) {
|
||||
if (videoFormat & VIDEO_FORMAT_MASK_H264) {
|
||||
const uint8_t* const parameterSetPointers[] = { [spsData bytes], [ppsData bytes] };
|
||||
const size_t parameterSetSizes[] = { [spsData length], [ppsData length] };
|
||||
|
||||
Log(LOG_I, @"Constructing new H264 format description");
|
||||
status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault,
|
||||
2, /* count of parameter sets */
|
||||
parameterSetPointers,
|
||||
parameterSetSizes,
|
||||
NAL_LENGTH_PREFIX_SIZE,
|
||||
&formatDesc);
|
||||
if (status != noErr) {
|
||||
Log(LOG_E, @"Failed to create H264 format description: %d", (int)status);
|
||||
formatDesc = NULL;
|
||||
}
|
||||
}
|
||||
else {
|
||||
const uint8_t* const parameterSetPointers[] = { [vpsData bytes], [spsData bytes], [ppsData bytes] };
|
||||
const size_t parameterSetSizes[] = { [vpsData length], [spsData length], [ppsData length] };
|
||||
|
||||
Log(LOG_I, @"Constructing new HEVC format description");
|
||||
|
||||
if (@available(iOS 11.0, *)) {
|
||||
status = CMVideoFormatDescriptionCreateFromHEVCParameterSets(kCFAllocatorDefault,
|
||||
3, /* count of parameter sets */
|
||||
parameterSetPointers,
|
||||
parameterSetSizes,
|
||||
NAL_LENGTH_PREFIX_SIZE,
|
||||
nil,
|
||||
&formatDesc);
|
||||
} else {
|
||||
// This means Moonlight-common-c decided to give us an HEVC stream
|
||||
// even though we said we couldn't support it. All we can do is abort().
|
||||
abort();
|
||||
}
|
||||
|
||||
if (status != noErr) {
|
||||
Log(LOG_E, @"Failed to create HEVC format description: %d", (int)status);
|
||||
formatDesc = NULL;
|
||||
}
|
||||
// Construct a new format description object each time we receive an IDR frame
|
||||
if (du->frameType == FRAME_TYPE_IDR) {
|
||||
if (bufferType != BUFFER_TYPE_PICDATA) {
|
||||
if (bufferType == BUFFER_TYPE_VPS || bufferType == BUFFER_TYPE_SPS || bufferType == BUFFER_TYPE_PPS) {
|
||||
// Add new parameter set into the parameter set array
|
||||
int startLen = data[2] == 0x01 ? 3 : 4;
|
||||
[parameterSetBuffers addObject:[NSData dataWithBytes:&data[startLen] length:length - startLen]];
|
||||
}
|
||||
|
||||
// Data is NOT to be freed here. It's a direct usage of the caller's buffer.
|
||||
|
||||
// No frame data to submit for these NALUs
|
||||
return DR_OK;
|
||||
}
|
||||
|
||||
// Data is NOT to be freed here. It's a direct usage of the caller's buffer.
|
||||
// Create the new format description when we get the first picture data buffer of an IDR frame.
|
||||
// This is the only way we know that there is no more CSD for this frame.
|
||||
//
|
||||
// NB: This logic depends on the fact that we submit all picture data in one buffer!
|
||||
|
||||
// No frame data to submit for these NALUs
|
||||
return DR_OK;
|
||||
// Free the old format description
|
||||
if (formatDesc != NULL) {
|
||||
CFRelease(formatDesc);
|
||||
formatDesc = NULL;
|
||||
}
|
||||
|
||||
if (videoFormat & VIDEO_FORMAT_MASK_H264) {
|
||||
// Construct parameter set arrays for the format description
|
||||
size_t parameterSetCount = [parameterSetBuffers count];
|
||||
const uint8_t* parameterSetPointers[parameterSetCount];
|
||||
size_t parameterSetSizes[parameterSetCount];
|
||||
for (int i = 0; i < parameterSetCount; i++) {
|
||||
NSData* parameterSet = parameterSetBuffers[i];
|
||||
parameterSetPointers[i] = parameterSet.bytes;
|
||||
parameterSetSizes[i] = parameterSet.length;
|
||||
}
|
||||
|
||||
Log(LOG_I, @"Constructing new H264 format description");
|
||||
status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault,
|
||||
parameterSetCount,
|
||||
parameterSetPointers,
|
||||
parameterSetSizes,
|
||||
NAL_LENGTH_PREFIX_SIZE,
|
||||
&formatDesc);
|
||||
if (status != noErr) {
|
||||
Log(LOG_E, @"Failed to create H264 format description: %d", (int)status);
|
||||
formatDesc = NULL;
|
||||
}
|
||||
|
||||
// Free parameter set buffers after submission
|
||||
[parameterSetBuffers removeAllObjects];
|
||||
}
|
||||
else if (videoFormat & VIDEO_FORMAT_MASK_H265) {
|
||||
// Construct parameter set arrays for the format description
|
||||
size_t parameterSetCount = [parameterSetBuffers count];
|
||||
const uint8_t* parameterSetPointers[parameterSetCount];
|
||||
size_t parameterSetSizes[parameterSetCount];
|
||||
for (int i = 0; i < parameterSetCount; i++) {
|
||||
NSData* parameterSet = parameterSetBuffers[i];
|
||||
parameterSetPointers[i] = parameterSet.bytes;
|
||||
parameterSetSizes[i] = parameterSet.length;
|
||||
}
|
||||
|
||||
Log(LOG_I, @"Constructing new HEVC format description");
|
||||
|
||||
NSMutableDictionary* videoFormatParams = [[NSMutableDictionary alloc] init];
|
||||
|
||||
if (contentLightLevelInfo) {
|
||||
[videoFormatParams setObject:contentLightLevelInfo forKey:(__bridge NSString*)kCMFormatDescriptionExtension_ContentLightLevelInfo];
|
||||
}
|
||||
|
||||
if (masteringDisplayColorVolume) {
|
||||
[videoFormatParams setObject:masteringDisplayColorVolume forKey:(__bridge NSString*)kCMFormatDescriptionExtension_MasteringDisplayColorVolume];
|
||||
}
|
||||
|
||||
status = CMVideoFormatDescriptionCreateFromHEVCParameterSets(kCFAllocatorDefault,
|
||||
parameterSetCount,
|
||||
parameterSetPointers,
|
||||
parameterSetSizes,
|
||||
NAL_LENGTH_PREFIX_SIZE,
|
||||
(__bridge CFDictionaryRef)videoFormatParams,
|
||||
&formatDesc);
|
||||
|
||||
if (status != noErr) {
|
||||
Log(LOG_E, @"Failed to create HEVC format description: %d", (int)status);
|
||||
formatDesc = NULL;
|
||||
}
|
||||
|
||||
// Free parameter set buffers after submission
|
||||
[parameterSetBuffers removeAllObjects];
|
||||
}
|
||||
else if (videoFormat & VIDEO_FORMAT_MASK_AV1) {
|
||||
NSData* fullFrameData = [NSData dataWithBytesNoCopy:data length:length freeWhenDone:NO];
|
||||
|
||||
Log(LOG_I, @"Constructing new AV1 format description");
|
||||
formatDesc = [self createAV1FormatDescriptionForIDRFrame:fullFrameData];
|
||||
}
|
||||
else {
|
||||
// Unsupported codec!
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
if (formatDesc == NULL) {
|
||||
@@ -269,13 +519,9 @@
|
||||
if (displayLayer.status == AVQueuedSampleBufferRenderingStatusFailed) {
|
||||
Log(LOG_E, @"Display layer rendering failed: %@", displayLayer.error);
|
||||
|
||||
// Recreate the display layer on the main thread.
|
||||
// We need to use dispatch_sync() or we may miss
|
||||
// some parameter sets while the layer is being
|
||||
// recreated.
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
[self reinitializeDisplayLayer];
|
||||
});
|
||||
// Recreate the display layer. We are already on the main thread,
|
||||
// so this is safe to do right here.
|
||||
[self reinitializeDisplayLayer];
|
||||
|
||||
// Request an IDR frame to initialize the new decoder
|
||||
free(data);
|
||||
@@ -283,85 +529,156 @@
|
||||
}
|
||||
|
||||
// Now we're decoding actual frame data here
|
||||
CMBlockBufferRef blockBuffer;
|
||||
CMBlockBufferRef frameBlockBuffer;
|
||||
CMBlockBufferRef dataBlockBuffer;
|
||||
|
||||
status = CMBlockBufferCreateEmpty(NULL, 0, 0, &blockBuffer);
|
||||
status = CMBlockBufferCreateWithMemoryBlock(NULL, data, length, kCFAllocatorDefault, NULL, 0, length, 0, &dataBlockBuffer);
|
||||
if (status != noErr) {
|
||||
Log(LOG_E, @"CMBlockBufferCreateEmpty failed: %d", (int)status);
|
||||
Log(LOG_E, @"CMBlockBufferCreateWithMemoryBlock failed: %d", (int)status);
|
||||
free(data);
|
||||
return DR_NEED_IDR;
|
||||
}
|
||||
|
||||
int lastOffset = -1;
|
||||
for (int i = 0; i < length - FRAME_START_PREFIX_SIZE; i++) {
|
||||
// Search for a NALU
|
||||
if (data[i] == 0 && data[i+1] == 0 && data[i+2] == 1) {
|
||||
// It's the start of a new NALU
|
||||
if (lastOffset != -1) {
|
||||
// We've seen a start before this so enqueue that NALU
|
||||
[self updateBufferForRange:blockBuffer data:data offset:lastOffset length:i - lastOffset];
|
||||
}
|
||||
|
||||
lastOffset = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (lastOffset != -1) {
|
||||
// Enqueue the remaining data
|
||||
[self updateBufferForRange:blockBuffer data:data offset:lastOffset length:length - lastOffset];
|
||||
}
|
||||
|
||||
// From now on, CMBlockBuffer owns the data pointer and will free it when it's dereferenced
|
||||
|
||||
CMSampleBufferRef sampleBuffer;
|
||||
|
||||
status = CMSampleBufferCreate(kCFAllocatorDefault,
|
||||
blockBuffer,
|
||||
true, NULL,
|
||||
NULL, formatDesc, 1, 0,
|
||||
NULL, 0, NULL,
|
||||
&sampleBuffer);
|
||||
status = CMBlockBufferCreateEmpty(NULL, 0, 0, &frameBlockBuffer);
|
||||
if (status != noErr) {
|
||||
Log(LOG_E, @"CMSampleBufferCreate failed: %d", (int)status);
|
||||
CFRelease(blockBuffer);
|
||||
Log(LOG_E, @"CMBlockBufferCreateEmpty failed: %d", (int)status);
|
||||
CFRelease(dataBlockBuffer);
|
||||
return DR_NEED_IDR;
|
||||
}
|
||||
|
||||
CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, YES);
|
||||
CFMutableDictionaryRef dict = (CFMutableDictionaryRef)CFArrayGetValueAtIndex(attachments, 0);
|
||||
|
||||
CFDictionarySetValue(dict, kCMSampleAttachmentKey_DisplayImmediately, kCFBooleanTrue);
|
||||
CFDictionarySetValue(dict, kCMSampleAttachmentKey_IsDependedOnByOthers, kCFBooleanTrue);
|
||||
|
||||
if (frameType == FRAME_TYPE_PFRAME) {
|
||||
// P-frame
|
||||
CFDictionarySetValue(dict, kCMSampleAttachmentKey_NotSync, kCFBooleanTrue);
|
||||
CFDictionarySetValue(dict, kCMSampleAttachmentKey_DependsOnOthers, kCFBooleanTrue);
|
||||
} else {
|
||||
// I-frame
|
||||
CFDictionarySetValue(dict, kCMSampleAttachmentKey_NotSync, kCFBooleanFalse);
|
||||
CFDictionarySetValue(dict, kCMSampleAttachmentKey_DependsOnOthers, kCFBooleanFalse);
|
||||
}
|
||||
|
||||
// Enqueue video samples on the main thread
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
// Enqueue the next frame
|
||||
[self->displayLayer enqueueSampleBuffer:sampleBuffer];
|
||||
|
||||
if (frameType == FRAME_TYPE_IDR) {
|
||||
// Ensure the layer is visible now
|
||||
self->displayLayer.hidden = NO;
|
||||
|
||||
// Tell our parent VC to hide the progress indicator
|
||||
[self->_callbacks videoContentShown];
|
||||
// H.264 and HEVC formats require NAL prefix fixups from Annex B to length-delimited
|
||||
if (videoFormat & (VIDEO_FORMAT_MASK_H264 | VIDEO_FORMAT_MASK_H265)) {
|
||||
int lastOffset = -1;
|
||||
for (int i = 0; i < length - NALU_START_PREFIX_SIZE; i++) {
|
||||
// Search for a NALU
|
||||
if (data[i] == 0 && data[i+1] == 0 && data[i+2] == 1) {
|
||||
// It's the start of a new NALU
|
||||
if (lastOffset != -1) {
|
||||
// We've seen a start before this so enqueue that NALU
|
||||
[self updateAnnexBBufferForRange:frameBlockBuffer dataBlock:dataBlockBuffer offset:lastOffset length:i - lastOffset];
|
||||
}
|
||||
|
||||
lastOffset = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Dereference the buffers
|
||||
CFRelease(blockBuffer);
|
||||
CFRelease(sampleBuffer);
|
||||
});
|
||||
if (lastOffset != -1) {
|
||||
// Enqueue the remaining data
|
||||
[self updateAnnexBBufferForRange:frameBlockBuffer dataBlock:dataBlockBuffer offset:lastOffset length:length - lastOffset];
|
||||
}
|
||||
}
|
||||
else {
|
||||
// For formats that require no length-changing fixups, just append a reference to the raw data block
|
||||
status = CMBlockBufferAppendBufferReference(frameBlockBuffer, dataBlockBuffer, 0, length, 0);
|
||||
if (status != noErr) {
|
||||
Log(LOG_E, @"CMBlockBufferAppendBufferReference failed: %d", (int)status);
|
||||
return DR_NEED_IDR;
|
||||
}
|
||||
}
|
||||
|
||||
CMSampleBufferRef sampleBuffer;
|
||||
|
||||
CMSampleTimingInfo sampleTiming = {kCMTimeInvalid, CMTimeMake(du->presentationTimeUs, 1000000), kCMTimeInvalid};
|
||||
|
||||
status = CMSampleBufferCreateReady(kCFAllocatorDefault,
|
||||
frameBlockBuffer,
|
||||
formatDesc, 1, 1,
|
||||
&sampleTiming, 0, NULL,
|
||||
&sampleBuffer);
|
||||
if (status != noErr) {
|
||||
Log(LOG_E, @"CMSampleBufferCreate failed: %d", (int)status);
|
||||
CFRelease(dataBlockBuffer);
|
||||
CFRelease(frameBlockBuffer);
|
||||
return DR_NEED_IDR;
|
||||
}
|
||||
|
||||
// Enqueue the next frame
|
||||
[self->displayLayer enqueueSampleBuffer:sampleBuffer];
|
||||
|
||||
if (du->frameType == FRAME_TYPE_IDR) {
|
||||
// Ensure the layer is visible now
|
||||
self->displayLayer.hidden = NO;
|
||||
|
||||
// Tell our parent VC to hide the progress indicator
|
||||
[self->_callbacks videoContentShown];
|
||||
}
|
||||
|
||||
// Dereference the buffers
|
||||
CFRelease(dataBlockBuffer);
|
||||
CFRelease(frameBlockBuffer);
|
||||
CFRelease(sampleBuffer);
|
||||
|
||||
return DR_OK;
|
||||
}
|
||||
|
||||
- (void)setHdrMode:(BOOL)enabled {
|
||||
SS_HDR_METADATA hdrMetadata;
|
||||
|
||||
BOOL hasMetadata = enabled && LiGetHdrMetadata(&hdrMetadata);
|
||||
BOOL metadataChanged = NO;
|
||||
|
||||
if (hasMetadata && hdrMetadata.displayPrimaries[0].x != 0 && hdrMetadata.maxDisplayLuminance != 0) {
|
||||
// This data is all in big-endian
|
||||
struct {
|
||||
vector_ushort2 primaries[3];
|
||||
vector_ushort2 white_point;
|
||||
uint32_t luminance_max;
|
||||
uint32_t luminance_min;
|
||||
} __attribute__((packed, aligned(4))) mdcv;
|
||||
|
||||
// mdcv is in GBR order while SS_HDR_METADATA is in RGB order
|
||||
mdcv.primaries[0].x = __builtin_bswap16(hdrMetadata.displayPrimaries[1].x);
|
||||
mdcv.primaries[0].y = __builtin_bswap16(hdrMetadata.displayPrimaries[1].y);
|
||||
mdcv.primaries[1].x = __builtin_bswap16(hdrMetadata.displayPrimaries[2].x);
|
||||
mdcv.primaries[1].y = __builtin_bswap16(hdrMetadata.displayPrimaries[2].y);
|
||||
mdcv.primaries[2].x = __builtin_bswap16(hdrMetadata.displayPrimaries[0].x);
|
||||
mdcv.primaries[2].y = __builtin_bswap16(hdrMetadata.displayPrimaries[0].y);
|
||||
|
||||
mdcv.white_point.x = __builtin_bswap16(hdrMetadata.whitePoint.x);
|
||||
mdcv.white_point.y = __builtin_bswap16(hdrMetadata.whitePoint.y);
|
||||
|
||||
// These luminance values are in 10000ths of a nit
|
||||
mdcv.luminance_max = __builtin_bswap32((uint32_t)hdrMetadata.maxDisplayLuminance * 10000);
|
||||
mdcv.luminance_min = __builtin_bswap32(hdrMetadata.minDisplayLuminance);
|
||||
|
||||
NSData* newMdcv = [NSData dataWithBytes:&mdcv length:sizeof(mdcv)];
|
||||
if (masteringDisplayColorVolume == nil || ![newMdcv isEqualToData:masteringDisplayColorVolume]) {
|
||||
masteringDisplayColorVolume = newMdcv;
|
||||
metadataChanged = YES;
|
||||
}
|
||||
}
|
||||
else if (masteringDisplayColorVolume != nil) {
|
||||
masteringDisplayColorVolume = nil;
|
||||
metadataChanged = YES;
|
||||
}
|
||||
|
||||
if (hasMetadata && hdrMetadata.maxContentLightLevel != 0 && hdrMetadata.maxFrameAverageLightLevel != 0) {
|
||||
// This data is all in big-endian
|
||||
struct {
|
||||
uint16_t max_content_light_level;
|
||||
uint16_t max_frame_average_light_level;
|
||||
} __attribute__((packed, aligned(2))) cll;
|
||||
|
||||
cll.max_content_light_level = __builtin_bswap16(hdrMetadata.maxContentLightLevel);
|
||||
cll.max_frame_average_light_level = __builtin_bswap16(hdrMetadata.maxFrameAverageLightLevel);
|
||||
|
||||
NSData* newCll = [NSData dataWithBytes:&cll length:sizeof(cll)];
|
||||
if (contentLightLevelInfo == nil || ![newCll isEqualToData:contentLightLevelInfo]) {
|
||||
contentLightLevelInfo = newCll;
|
||||
metadataChanged = YES;
|
||||
}
|
||||
}
|
||||
else if (contentLightLevelInfo != nil) {
|
||||
contentLightLevelInfo = nil;
|
||||
metadataChanged = YES;
|
||||
}
|
||||
|
||||
// If the metadata changed, request an IDR frame to re-create the CMVideoFormatDescription
|
||||
if (metadataChanged) {
|
||||
LiRequestIdrFrame();
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -27,6 +27,14 @@ FOUNDATION_EXPORT NSString *const deviceName;
|
||||
+ (NSData*) hexToBytes:(NSString*) hex;
|
||||
+ (void) addHelpOptionToDialog:(UIAlertController*)dialog;
|
||||
+ (BOOL) isActiveNetworkVPN;
|
||||
+ (BOOL) parseAddressPortString:(NSString*)addressPort address:(NSRange*)address port:(NSRange*)port;
|
||||
+ (NSString*) addressPortStringToAddress:(NSString*)addressPort;
|
||||
+ (unsigned short) addressPortStringToPort:(NSString*)addressPort;
|
||||
+ (NSString*) addressAndPortToAddressPortString:(NSString*)address port:(unsigned short)port;
|
||||
|
||||
#if !TARGET_OS_TV
|
||||
+ (void) launchUrl:(NSString*)urlString;
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -64,15 +64,88 @@ NSString *const deviceName = @"roth";
|
||||
return NO;
|
||||
}
|
||||
|
||||
#if !TARGET_OS_TV
|
||||
+ (void) launchUrl:(NSString*)urlString {
|
||||
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlString] options:@{} completionHandler:nil];
|
||||
}
|
||||
#endif
|
||||
|
||||
+ (void) addHelpOptionToDialog:(UIAlertController*)dialog {
|
||||
#if !TARGET_OS_TV
|
||||
// tvOS doesn't have a browser
|
||||
[dialog addAction:[UIAlertAction actionWithTitle:@"Help" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action){
|
||||
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"https://github.com/moonlight-stream/moonlight-docs/wiki/Troubleshooting"]];
|
||||
[Utils launchUrl:@"https://github.com/moonlight-stream/moonlight-docs/wiki/Troubleshooting"];
|
||||
}]];
|
||||
#endif
|
||||
}
|
||||
|
||||
+ (BOOL) parseAddressPortString:(NSString*)addressPort address:(NSRange*)address port:(NSRange*)port {
|
||||
if (![addressPort containsString:@":"]) {
|
||||
// If there's no port or IPv6 separator, the whole thing is an address
|
||||
*address = NSMakeRange(0, [addressPort length]);
|
||||
*port = NSMakeRange(NSNotFound, 0);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
NSInteger locationOfOpeningBracket = [addressPort rangeOfString:@"["].location;
|
||||
NSInteger locationOfClosingBracket = [addressPort rangeOfString:@"]"].location;
|
||||
if (locationOfOpeningBracket != NSNotFound || locationOfClosingBracket != NSNotFound) {
|
||||
// If we have brackets, it's an IPv6 address
|
||||
if (locationOfOpeningBracket == NSNotFound || locationOfClosingBracket == NSNotFound ||
|
||||
locationOfClosingBracket < locationOfOpeningBracket) {
|
||||
// Invalid address format
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Cut at the brackets
|
||||
*address = NSMakeRange(locationOfOpeningBracket + 1, locationOfClosingBracket - locationOfOpeningBracket - 1);
|
||||
}
|
||||
else {
|
||||
// It's an IPv4 address, so just cut at the port separator
|
||||
*address = NSMakeRange(0, [addressPort rangeOfString:@":"].location);
|
||||
}
|
||||
|
||||
NSUInteger remainingStringLocation = address->location + address->length;
|
||||
NSRange remainingStringRange = NSMakeRange(remainingStringLocation, [addressPort length] - remainingStringLocation);
|
||||
NSInteger locationOfPortSeparator = [addressPort rangeOfString:@":" options:0 range:remainingStringRange].location;
|
||||
if (locationOfPortSeparator != NSNotFound) {
|
||||
*port = NSMakeRange(locationOfPortSeparator + 1, [addressPort length] - locationOfPortSeparator - 1);
|
||||
}
|
||||
else {
|
||||
*port = NSMakeRange(NSNotFound, 0);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
+ (NSString*) addressPortStringToAddress:(NSString*)addressPort {
|
||||
NSRange addressRange, portRange;
|
||||
if (![self parseAddressPortString:addressPort address:&addressRange port:&portRange]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [addressPort substringWithRange:addressRange];
|
||||
}
|
||||
|
||||
+ (unsigned short) addressPortStringToPort:(NSString*)addressPort {
|
||||
NSRange addressRange, portRange;
|
||||
if (![self parseAddressPortString:addressPort address:&addressRange port:&portRange] || portRange.location == NSNotFound) {
|
||||
return 47989;
|
||||
}
|
||||
|
||||
return [[addressPort substringWithRange:portRange] integerValue];
|
||||
}
|
||||
|
||||
+ (NSString*) addressAndPortToAddressPortString:(NSString*)address port:(unsigned short)port {
|
||||
if ([address containsString:@":"]) {
|
||||
// IPv6 addresses require escaping
|
||||
return [NSString stringWithFormat:@"[%@]:%u", address, port];
|
||||
}
|
||||
else {
|
||||
return [NSString stringWithFormat:@"%@:%u", address, port];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSString (NSStringWithTrim)
|
||||
|
||||
@@ -64,7 +64,7 @@ static NSMutableSet* hostList;
|
||||
// failure callback could be invoked.
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
self->_pairAlert = [UIAlertController alertControllerWithTitle:@"Pairing"
|
||||
message:[NSString stringWithFormat:@"Enter the following PIN on the host machine: %@", PIN]
|
||||
message:[NSString stringWithFormat:@"Enter the following PIN on the host machine: %@\n\nIf your host PC is running Sunshine, navigate to the Sunshine web UI to enter the PIN.", PIN]
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[self->_pairAlert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleDestructive handler:^(UIAlertAction* action) {
|
||||
self->_pairAlert = nil;
|
||||
@@ -168,7 +168,7 @@ static NSMutableSet* hostList;
|
||||
// Exempt this host from discovery while handling the applist query
|
||||
[self->_discMan pauseDiscoveryForHost:host];
|
||||
|
||||
AppListResponse* appListResp = [ConnectionHelper getAppListForHostWithHostIP:host.activeAddress serverCert:host.serverCert uniqueID:self->_uniqueId];
|
||||
AppListResponse* appListResp = [ConnectionHelper getAppListForHost:host];
|
||||
|
||||
[self->_discMan resumeDiscoveryForHost:host];
|
||||
|
||||
@@ -373,7 +373,7 @@ static NSMutableSet* hostList;
|
||||
return;
|
||||
}
|
||||
|
||||
HttpManager* hMan = [[HttpManager alloc] initWithHost:host.activeAddress uniqueId:self->_uniqueId serverCert:host.serverCert];
|
||||
HttpManager* hMan = [[HttpManager alloc] initWithHost:host];
|
||||
ServerInfoResponse* serverInfoResp = [[ServerInfoResponse alloc] init];
|
||||
|
||||
// Exempt this host from discovery while handling the serverinfo request
|
||||
@@ -492,6 +492,14 @@ static NSMutableSet* hostList;
|
||||
self->_showHiddenApps = YES;
|
||||
[self hostClicked:host view:view];
|
||||
}]];
|
||||
|
||||
#if !TARGET_OS_TV
|
||||
if (host.isNvidiaServerSoftware) {
|
||||
[longClickAlert addAction:[UIAlertAction actionWithTitle:@"NVIDIA GameStream End-of-Service" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action){
|
||||
[Utils launchUrl:@"https://github.com/moonlight-stream/moonlight-docs/wiki/NVIDIA-GameStream-End-Of-Service-Announcement-FAQ"];
|
||||
}]];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
[longClickAlert addAction:[UIAlertAction actionWithTitle:@"Test Network" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action) {
|
||||
[self showLoadingFrame:^{
|
||||
@@ -524,8 +532,11 @@ static NSMutableSet* hostList;
|
||||
}]];
|
||||
#if !TARGET_OS_TV
|
||||
if (host.state != StateOnline) {
|
||||
[longClickAlert addAction:[UIAlertAction actionWithTitle:@"NVIDIA GameStream End-of-Service" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action){
|
||||
[Utils launchUrl:@"https://github.com/moonlight-stream/moonlight-docs/wiki/NVIDIA-GameStream-End-Of-Service-Announcement-FAQ"];
|
||||
}]];
|
||||
[longClickAlert addAction:[UIAlertAction actionWithTitle:@"Connection Help" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action){
|
||||
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"https://github.com/moonlight-stream/moonlight-docs/wiki/Troubleshooting"]];
|
||||
[Utils launchUrl:@"https://github.com/moonlight-stream/moonlight-docs/wiki/Troubleshooting"];
|
||||
}]];
|
||||
}
|
||||
#endif
|
||||
@@ -593,6 +604,7 @@ static NSMutableSet* hostList;
|
||||
- (void) prepareToStreamApp:(TemporaryApp *)app {
|
||||
_streamConfig = [[StreamConfiguration alloc] init];
|
||||
_streamConfig.host = app.host.activeAddress;
|
||||
_streamConfig.httpsPort = app.host.httpsPort;
|
||||
_streamConfig.appID = app.id;
|
||||
_streamConfig.appName = app.name;
|
||||
_streamConfig.serverCert = app.host.serverCert;
|
||||
@@ -625,39 +637,69 @@ static NSMutableSet* hostList;
|
||||
_streamConfig.bitRate = [streamSettings.bitrate intValue];
|
||||
_streamConfig.optimizeGameSettings = streamSettings.optimizeGames;
|
||||
_streamConfig.playAudioOnPC = streamSettings.playAudioOnPC;
|
||||
_streamConfig.allowHevc = streamSettings.useHevc;
|
||||
_streamConfig.useFramePacing = streamSettings.useFramePacing;
|
||||
_streamConfig.swapABXYButtons = streamSettings.swapABXYButtons;
|
||||
|
||||
// multiController must be set before calling getConnectedGamepadMask
|
||||
_streamConfig.multiController = streamSettings.multiController;
|
||||
_streamConfig.gamepadMask = [ControllerSupport getConnectedGamepadMask:_streamConfig];
|
||||
|
||||
|
||||
// Probe for supported channel configurations
|
||||
long outputChannels = [AVAudioSession sharedInstance].maximumOutputNumberOfChannels;
|
||||
Log(LOG_I, @"Audio device supports %d channels", outputChannels);
|
||||
if (outputChannels >= 8) {
|
||||
int physicalOutputChannels = (int)[AVAudioSession sharedInstance].maximumOutputNumberOfChannels;
|
||||
Log(LOG_I, @"Audio device supports %d channels", physicalOutputChannels);
|
||||
|
||||
int numberOfChannels = MIN([streamSettings.audioConfig intValue], physicalOutputChannels);
|
||||
Log(LOG_I, @"Selected number of audio channels %d", numberOfChannels);
|
||||
if (numberOfChannels >= 8) {
|
||||
_streamConfig.audioConfiguration = AUDIO_CONFIGURATION_71_SURROUND;
|
||||
}
|
||||
else if (outputChannels >= 6) {
|
||||
else if (numberOfChannels >= 6) {
|
||||
_streamConfig.audioConfiguration = AUDIO_CONFIGURATION_51_SURROUND;
|
||||
}
|
||||
else {
|
||||
_streamConfig.audioConfiguration = AUDIO_CONFIGURATION_STEREO;
|
||||
}
|
||||
|
||||
// HDR requires HDR10 game, HDR10 display, and HEVC Main10 decoder on the client.
|
||||
// It additionally requires an HEVC Main10 encoder on the server (GTX 1000+).
|
||||
//
|
||||
// It should also be a user preference, since some games may require higher peak
|
||||
// brightness than the iOS device can support to look correct in HDR mode.
|
||||
if (@available(iOS 11.3, *)) {
|
||||
_streamConfig.enableHdr =
|
||||
app.hdrSupported && // App supported
|
||||
(app.host.serverCodecModeSupport & 0x200) != 0 && // HEVC Main10 encoding on host PC GPU
|
||||
VTIsHardwareDecodeSupported(kCMVideoCodecType_HEVC) && // Decoder supported
|
||||
(AVPlayer.availableHDRModes & AVPlayerHDRModeHDR10) != 0 && // Display supported
|
||||
streamSettings.enableHdr; // User wants it enabled
|
||||
_streamConfig.serverCodecModeSupport = app.host.serverCodecModeSupport;
|
||||
|
||||
switch (streamSettings.preferredCodec) {
|
||||
case CODEC_PREF_AV1:
|
||||
#if defined(__IPHONE_16_0) || defined(__TVOS_16_0)
|
||||
if (VTIsHardwareDecodeSupported(kCMVideoCodecType_AV1)) {
|
||||
_streamConfig.supportedVideoFormats |= VIDEO_FORMAT_AV1_MAIN8;
|
||||
}
|
||||
#endif
|
||||
// Fall-through
|
||||
|
||||
case CODEC_PREF_AUTO:
|
||||
case CODEC_PREF_HEVC:
|
||||
if (VTIsHardwareDecodeSupported(kCMVideoCodecType_HEVC)) {
|
||||
_streamConfig.supportedVideoFormats |= VIDEO_FORMAT_H265;
|
||||
}
|
||||
// Fall-through
|
||||
|
||||
case CODEC_PREF_H264:
|
||||
_streamConfig.supportedVideoFormats |= VIDEO_FORMAT_H264;
|
||||
break;
|
||||
}
|
||||
|
||||
// HEVC is supported if the user wants it (or it's required by the chosen resolution) and the SoC supports it
|
||||
if ((_streamConfig.width > 4096 || _streamConfig.height > 4096 || streamSettings.enableHdr) && VTIsHardwareDecodeSupported(kCMVideoCodecType_HEVC)) {
|
||||
_streamConfig.supportedVideoFormats |= VIDEO_FORMAT_H265;
|
||||
|
||||
// HEVC Main10 is supported if the user wants it and the display supports it
|
||||
if (streamSettings.enableHdr && (AVPlayer.availableHDRModes & AVPlayerHDRModeHDR10) != 0) {
|
||||
_streamConfig.supportedVideoFormats |= VIDEO_FORMAT_H265_MAIN10;
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(__IPHONE_16_0) || defined(__TVOS_16_0)
|
||||
// Add the AV1 Main10 format if AV1 and HDR are both enabled and supported
|
||||
if ((_streamConfig.supportedVideoFormats & VIDEO_FORMAT_MASK_AV1) && streamSettings.enableHdr &&
|
||||
VTIsHardwareDecodeSupported(kCMVideoCodecType_AV1) && (AVPlayer.availableHDRModes & AVPlayerHDRModeHDR10) != 0) {
|
||||
_streamConfig.supportedVideoFormats |= VIDEO_FORMAT_AV1_MAIN10;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)appLongClicked:(TemporaryApp *)app view:(UIView *)view {
|
||||
@@ -715,7 +757,7 @@ static NSMutableSet* hostList;
|
||||
Log(LOG_I, @"Quitting application: %@", currentApp.name);
|
||||
[self showLoadingFrame: ^{
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
HttpManager* hMan = [[HttpManager alloc] initWithHost:app.host.activeAddress uniqueId:self->_uniqueId serverCert:app.host.serverCert];
|
||||
HttpManager* hMan = [[HttpManager alloc] initWithHost:app.host];
|
||||
HttpResponse* quitResponse = [[HttpResponse alloc] init];
|
||||
HttpRequest* quitRequest = [HttpRequest requestForResponse: quitResponse withUrlRequest:[hMan newQuitAppRequest]];
|
||||
|
||||
|
||||
@@ -626,10 +626,10 @@ const int FrontViewPositionNone = 0xff;
|
||||
_frontViewPosition = FrontViewPositionLeft;
|
||||
_rearViewPosition = FrontViewPositionLeft;
|
||||
_rightViewPosition = FrontViewPositionLeft;
|
||||
_rearViewRevealWidth = 260.0f;
|
||||
_rearViewRevealWidth = 510.0f;
|
||||
_rearViewRevealOverdraw = 60.0f;
|
||||
_rearViewRevealDisplacement = 40.0f;
|
||||
_rightViewRevealWidth = 260.0f;
|
||||
_rightViewRevealWidth = 510.0f;
|
||||
_rightViewRevealOverdraw = 60.0f;
|
||||
_rightViewRevealDisplacement = 40.0f;
|
||||
_bounceBackOnOverdraw = YES;
|
||||
|
||||
@@ -14,12 +14,16 @@
|
||||
@property (strong, nonatomic) IBOutlet UISlider *bitrateSlider;
|
||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *framerateSelector;
|
||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *resolutionSelector;
|
||||
@property (strong, nonatomic) IBOutlet UIView *resolutionDisplayView;
|
||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *touchModeSelector;
|
||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *onscreenControlSelector;
|
||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *optimizeSettingsSelector;
|
||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *multiControllerSelector;
|
||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *swapABXYButtonsSelector;
|
||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *audioOnPCSelector;
|
||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *hevcSelector;
|
||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *codecSelector;
|
||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *hdrSelector;
|
||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *framePacingSelector;
|
||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *btMouseSelector;
|
||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *statsOverlaySelector;
|
||||
@property (strong, nonatomic) IBOutlet UIScrollView *scrollView;
|
||||
|
||||
@@ -11,9 +11,11 @@
|
||||
#import "DataManager.h"
|
||||
|
||||
#import <VideoToolbox/VideoToolbox.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
@implementation SettingsViewController {
|
||||
NSInteger _bitrate;
|
||||
NSInteger _lastSelectedResolutionIndex;
|
||||
}
|
||||
|
||||
@dynamic overrideUserInterfaceStyle;
|
||||
@@ -48,6 +50,10 @@ static const int bitrateTable[] = {
|
||||
150000,
|
||||
};
|
||||
|
||||
const int RESOLUTION_TABLE_SIZE = 7;
|
||||
const int RESOLUTION_TABLE_CUSTOM_INDEX = RESOLUTION_TABLE_SIZE - 1;
|
||||
CGSize resolutionTable[RESOLUTION_TABLE_SIZE];
|
||||
|
||||
-(int)getSliderValueForBitrate:(NSInteger)bitrate {
|
||||
int i;
|
||||
|
||||
@@ -61,35 +67,33 @@ static const int bitrateTable[] = {
|
||||
return i - 1;
|
||||
}
|
||||
|
||||
// This view is rooted at a ScrollView. To make it scrollable,
|
||||
// we'll update content size here.
|
||||
-(void)viewDidLayoutSubviews {
|
||||
// On iPhone layouts, this view is rooted at a ScrollView. To make it
|
||||
// scrollable, we'll update content size here.
|
||||
if (self.scrollView != nil) {
|
||||
CGFloat highestViewY = 0;
|
||||
|
||||
// Enumerate the scroll view's subviews looking for the
|
||||
// highest view Y value to set our scroll view's content
|
||||
// size.
|
||||
for (UIView* view in self.scrollView.subviews) {
|
||||
// UIScrollViews have 2 default child views
|
||||
// which represent the horizontal and vertical scrolling
|
||||
// indicators. Ignore any views we don't recognize.
|
||||
if (![view isKindOfClass:[UILabel class]] &&
|
||||
![view isKindOfClass:[UISegmentedControl class]] &&
|
||||
![view isKindOfClass:[UISlider class]]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CGFloat currentViewY = view.frame.origin.y + view.frame.size.height;
|
||||
if (currentViewY > highestViewY) {
|
||||
highestViewY = currentViewY;
|
||||
}
|
||||
CGFloat highestViewY = 0;
|
||||
|
||||
// Enumerate the scroll view's subviews looking for the
|
||||
// highest view Y value to set our scroll view's content
|
||||
// size.
|
||||
for (UIView* view in self.scrollView.subviews) {
|
||||
// UIScrollViews have 2 default child views
|
||||
// which represent the horizontal and vertical scrolling
|
||||
// indicators. Ignore any views we don't recognize.
|
||||
if (![view isKindOfClass:[UILabel class]] &&
|
||||
![view isKindOfClass:[UISegmentedControl class]] &&
|
||||
![view isKindOfClass:[UISlider class]]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add a bit of padding so the view doesn't end right at the button of the display
|
||||
self.scrollView.contentSize = CGSizeMake(self.scrollView.contentSize.width,
|
||||
highestViewY + 20);
|
||||
CGFloat currentViewY = view.frame.origin.y + view.frame.size.height;
|
||||
if (currentViewY > highestViewY) {
|
||||
highestViewY = currentViewY;
|
||||
}
|
||||
}
|
||||
|
||||
// Add a bit of padding so the view doesn't end right at the button of the display
|
||||
self.scrollView.contentSize = CGSizeMake(self.scrollView.contentSize.width,
|
||||
highestViewY + 20);
|
||||
}
|
||||
|
||||
// Adjust the subviews for the safe area on the iPhone X.
|
||||
@@ -108,6 +112,20 @@ static const int bitrateTable[] = {
|
||||
}
|
||||
}
|
||||
|
||||
BOOL isCustomResolution(CGSize res) {
|
||||
if (res.width == 0 && res.height == 0) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
for (int i = 0; i < RESOLUTION_TABLE_CUSTOM_INDEX; i++) {
|
||||
if (res.width == resolutionTable[i].width && res.height == resolutionTable[i].height) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
@@ -121,6 +139,31 @@ static const int bitrateTable[] = {
|
||||
|
||||
// Ensure we pick a bitrate that falls exactly onto a slider notch
|
||||
_bitrate = bitrateTable[[self getSliderValueForBitrate:[currentSettings.bitrate intValue]]];
|
||||
|
||||
// Get the size of the screen with and without safe area insets
|
||||
UIWindow *window = UIApplication.sharedApplication.windows.firstObject;
|
||||
CGFloat screenScale = window.screen.scale;
|
||||
CGFloat safeAreaWidth = (window.frame.size.width - window.safeAreaInsets.left - window.safeAreaInsets.right) * screenScale;
|
||||
CGFloat fullScreenWidth = window.frame.size.width * screenScale;
|
||||
CGFloat fullScreenHeight = window.frame.size.height * screenScale;
|
||||
|
||||
self.resolutionDisplayView.layer.cornerRadius = 10;
|
||||
self.resolutionDisplayView.clipsToBounds = YES;
|
||||
UITapGestureRecognizer *resolutionDisplayViewTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(resolutionDisplayViewTapped:)];
|
||||
[self.resolutionDisplayView addGestureRecognizer:resolutionDisplayViewTap];
|
||||
|
||||
resolutionTable[0] = CGSizeMake(640, 360);
|
||||
resolutionTable[1] = CGSizeMake(1280, 720);
|
||||
resolutionTable[2] = CGSizeMake(1920, 1080);
|
||||
resolutionTable[3] = CGSizeMake(3840, 2160);
|
||||
resolutionTable[4] = CGSizeMake(safeAreaWidth, fullScreenHeight);
|
||||
resolutionTable[5] = CGSizeMake(fullScreenWidth, fullScreenHeight);
|
||||
resolutionTable[6] = CGSizeMake([currentSettings.width integerValue], [currentSettings.height integerValue]); // custom initial value
|
||||
|
||||
// Don't populate the custom entry unless we have a custom resolution
|
||||
if (!isCustomResolution(resolutionTable[6])) {
|
||||
resolutionTable[6] = CGSizeMake(0, 0);
|
||||
}
|
||||
|
||||
NSInteger framerate;
|
||||
switch ([currentSettings.framerate integerValue]) {
|
||||
@@ -135,23 +178,16 @@ static const int bitrateTable[] = {
|
||||
framerate = 2;
|
||||
break;
|
||||
}
|
||||
NSInteger resolution;
|
||||
switch ([currentSettings.height integerValue]) {
|
||||
case 360:
|
||||
resolution = 0;
|
||||
break;
|
||||
default:
|
||||
case 720:
|
||||
resolution = 1;
|
||||
break;
|
||||
case 1080:
|
||||
resolution = 2;
|
||||
break;
|
||||
case 2160:
|
||||
resolution = 3;
|
||||
|
||||
NSInteger resolution = 1;
|
||||
for (int i = 0; i < RESOLUTION_TABLE_SIZE; i++) {
|
||||
if ((int) resolutionTable[i].height == [currentSettings.height intValue]
|
||||
&& (int) resolutionTable[i].width == [currentSettings.width intValue]) {
|
||||
resolution = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Only show the 120 FPS option if we have a > 60-ish Hz display
|
||||
bool enable120Fps = false;
|
||||
if (@available(iOS 10.3, tvOS 10.3, *)) {
|
||||
@@ -162,35 +198,46 @@ static const int bitrateTable[] = {
|
||||
if (!enable120Fps) {
|
||||
[self.framerateSelector removeSegmentAtIndex:2 animated:NO];
|
||||
}
|
||||
|
||||
// Only show the 4K option for "recent" devices. We'll judge that by whether
|
||||
// they support HEVC decoding (A9 or later).
|
||||
if (@available(iOS 11.0, tvOS 11.0, *)) {
|
||||
if (!VTIsHardwareDecodeSupported(kCMVideoCodecType_HEVC)) {
|
||||
[self.resolutionSelector removeSegmentAtIndex:3 animated:NO];
|
||||
}
|
||||
|
||||
// Disable codec selector segments for unsupported codecs
|
||||
#if defined(__IPHONE_16_0) || defined(__TVOS_16_0)
|
||||
if (!VTIsHardwareDecodeSupported(kCMVideoCodecType_AV1))
|
||||
#endif
|
||||
{
|
||||
[self.codecSelector removeSegmentAtIndex:2 animated:NO];
|
||||
}
|
||||
else {
|
||||
[self.resolutionSelector removeSegmentAtIndex:3 animated:NO];
|
||||
if (!VTIsHardwareDecodeSupported(kCMVideoCodecType_HEVC)) {
|
||||
[self.codecSelector removeSegmentAtIndex:1 animated:NO];
|
||||
|
||||
// Only enable the 4K option for "recent" devices. We'll judge that by whether
|
||||
// they support HEVC decoding (A9 or later).
|
||||
[self.resolutionSelector setEnabled:NO forSegmentAtIndex:3];
|
||||
}
|
||||
switch (currentSettings.preferredCodec) {
|
||||
case CODEC_PREF_AUTO:
|
||||
[self.codecSelector setSelectedSegmentIndex:self.codecSelector.numberOfSegments - 1];
|
||||
break;
|
||||
|
||||
case CODEC_PREF_AV1:
|
||||
[self.codecSelector setSelectedSegmentIndex:2];
|
||||
break;
|
||||
|
||||
case CODEC_PREF_HEVC:
|
||||
[self.codecSelector setSelectedSegmentIndex:1];
|
||||
break;
|
||||
|
||||
case CODEC_PREF_H264:
|
||||
[self.codecSelector setSelectedSegmentIndex:0];
|
||||
break;
|
||||
}
|
||||
|
||||
// Disable the HEVC selector if HEVC is not supported by the hardware
|
||||
// or the version of iOS. See comment in Connection.m for reasoning behind
|
||||
// the iOS 11.3 check.
|
||||
if (@available(iOS 11.3, tvOS 11.3, *)) {
|
||||
if (VTIsHardwareDecodeSupported(kCMVideoCodecType_HEVC)) {
|
||||
[self.hevcSelector setSelectedSegmentIndex:currentSettings.useHevc ? 1 : 0];
|
||||
}
|
||||
else {
|
||||
[self.hevcSelector removeAllSegments];
|
||||
[self.hevcSelector insertSegmentWithTitle:@"Unsupported on this device" atIndex:0 animated:NO];
|
||||
[self.hevcSelector setEnabled:NO];
|
||||
}
|
||||
if (!VTIsHardwareDecodeSupported(kCMVideoCodecType_HEVC) || !(AVPlayer.availableHDRModes & AVPlayerHDRModeHDR10)) {
|
||||
[self.hdrSelector removeAllSegments];
|
||||
[self.hdrSelector insertSegmentWithTitle:@"Unsupported on this device" atIndex:0 animated:NO];
|
||||
[self.hdrSelector setEnabled:NO];
|
||||
}
|
||||
else {
|
||||
[self.hevcSelector removeAllSegments];
|
||||
[self.hevcSelector insertSegmentWithTitle:@"Requires iOS 11.3 or later" atIndex:0 animated:NO];
|
||||
[self.hevcSelector setEnabled:NO];
|
||||
[self.hdrSelector setSelectedSegmentIndex:currentSettings.enableHdr ? 1 : 0];
|
||||
}
|
||||
|
||||
[self.touchModeSelector setSelectedSegmentIndex:currentSettings.absoluteTouchMode ? 1 : 0];
|
||||
@@ -198,13 +245,16 @@ static const int bitrateTable[] = {
|
||||
[self.statsOverlaySelector setSelectedSegmentIndex:currentSettings.statsOverlay ? 1 : 0];
|
||||
[self.btMouseSelector setSelectedSegmentIndex:currentSettings.btMouseSupport ? 1 : 0];
|
||||
[self.optimizeSettingsSelector setSelectedSegmentIndex:currentSettings.optimizeGames ? 1 : 0];
|
||||
[self.framePacingSelector setSelectedSegmentIndex:currentSettings.useFramePacing ? 1 : 0];
|
||||
[self.multiControllerSelector setSelectedSegmentIndex:currentSettings.multiController ? 1 : 0];
|
||||
[self.swapABXYButtonsSelector setSelectedSegmentIndex:currentSettings.swapABXYButtons ? 1 : 0];
|
||||
[self.audioOnPCSelector setSelectedSegmentIndex:currentSettings.playAudioOnPC ? 1 : 0];
|
||||
NSInteger onscreenControls = [currentSettings.onscreenControls integerValue];
|
||||
_lastSelectedResolutionIndex = resolution;
|
||||
[self.resolutionSelector setSelectedSegmentIndex:resolution];
|
||||
[self.resolutionSelector addTarget:self action:@selector(newResolutionFpsChosen) forControlEvents:UIControlEventValueChanged];
|
||||
[self.resolutionSelector addTarget:self action:@selector(newResolutionChosen) forControlEvents:UIControlEventValueChanged];
|
||||
[self.framerateSelector setSelectedSegmentIndex:framerate];
|
||||
[self.framerateSelector addTarget:self action:@selector(newResolutionFpsChosen) forControlEvents:UIControlEventValueChanged];
|
||||
[self.framerateSelector addTarget:self action:@selector(updateBitrate) forControlEvents:UIControlEventValueChanged];
|
||||
[self.onscreenControlSelector setSelectedSegmentIndex:onscreenControls];
|
||||
[self.onscreenControlSelector setEnabled:!currentSettings.absoluteTouchMode];
|
||||
[self.bitrateSlider setMinimumValue:0];
|
||||
@@ -212,6 +262,7 @@ static const int bitrateTable[] = {
|
||||
[self.bitrateSlider setValue:[self getSliderValueForBitrate:_bitrate] animated:YES];
|
||||
[self.bitrateSlider addTarget:self action:@selector(bitrateSliderMoved) forControlEvents:UIControlEventValueChanged];
|
||||
[self updateBitrateText];
|
||||
[self updateResolutionDisplayViewText];
|
||||
}
|
||||
|
||||
- (void) touchModeChanged {
|
||||
@@ -219,45 +270,192 @@ static const int bitrateTable[] = {
|
||||
[self.onscreenControlSelector setEnabled:[self.touchModeSelector selectedSegmentIndex] == 0];
|
||||
}
|
||||
|
||||
- (void) newResolutionFpsChosen {
|
||||
- (void) updateBitrate {
|
||||
NSInteger fps = [self getChosenFrameRate];
|
||||
NSInteger width = [self getChosenStreamWidth];
|
||||
NSInteger height = [self getChosenStreamHeight];
|
||||
NSInteger defaultBitrate;
|
||||
|
||||
// This table prefers 16:10 resolutions because they are
|
||||
// only slightly more pixels than the 16:9 equivalents, so
|
||||
// we don't want to bump those 16:10 resolutions up to the
|
||||
// next 16:9 slot.
|
||||
//
|
||||
// This logic is shamelessly stolen from Moonlight Qt:
|
||||
// https://github.com/moonlight-stream/moonlight-qt/blob/master/app/settings/streamingpreferences.cpp
|
||||
|
||||
if (width * height <= 640 * 360) {
|
||||
defaultBitrate = 1000 * (fps / 30.0);
|
||||
// Don't scale bitrate linearly beyond 60 FPS. It's definitely not a linear
|
||||
// bitrate increase for frame rate once we get to values that high.
|
||||
float frameRateFactor = (fps <= 60 ? fps : (sqrtf(fps / 60.f) * 60.f)) / 30.f;
|
||||
|
||||
// TODO: Collect some empirical data to see if these defaults make sense.
|
||||
// We're just using the values that the Shield used, as we have for years.
|
||||
struct {
|
||||
NSInteger pixels;
|
||||
int factor;
|
||||
} resTable[] = {
|
||||
{ 640 * 360, 1 },
|
||||
{ 854 * 480, 2 },
|
||||
{ 1280 * 720, 5 },
|
||||
{ 1920 * 1080, 10 },
|
||||
{ 2560 * 1440, 20 },
|
||||
{ 3840 * 2160, 40 },
|
||||
{ -1, -1 }
|
||||
};
|
||||
|
||||
// Calculate the resolution factor by linear interpolation of the resolution table
|
||||
float resolutionFactor;
|
||||
NSInteger pixels = width * height;
|
||||
for (int i = 0;; i++) {
|
||||
if (pixels == resTable[i].pixels) {
|
||||
// We can bail immediately for exact matches
|
||||
resolutionFactor = resTable[i].factor;
|
||||
break;
|
||||
}
|
||||
else if (pixels < resTable[i].pixels) {
|
||||
if (i == 0) {
|
||||
// Never go below the lowest resolution entry
|
||||
resolutionFactor = resTable[i].factor;
|
||||
}
|
||||
else {
|
||||
// Interpolate between the entry greater than the chosen resolution (i) and the entry less than the chosen resolution (i-1)
|
||||
resolutionFactor = ((float)(pixels - resTable[i-1].pixels) / (resTable[i].pixels - resTable[i-1].pixels)) * (resTable[i].factor - resTable[i-1].factor) + resTable[i-1].factor;
|
||||
}
|
||||
break;
|
||||
}
|
||||
else if (resTable[i].pixels == -1) {
|
||||
// Never go above the highest resolution entry
|
||||
resolutionFactor = resTable[i-1].factor;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// This covers 1280x720 and 1280x800 too
|
||||
else if (width * height <= 1366 * 768) {
|
||||
defaultBitrate = 5000 * (fps / 30.0);
|
||||
}
|
||||
else if (width * height <= 1920 * 1200) {
|
||||
defaultBitrate = 10000 * (fps / 30.0);
|
||||
}
|
||||
else if (width * height <= 2560 * 1600) {
|
||||
defaultBitrate = 20000 * (fps / 30.0);
|
||||
}
|
||||
else /* if (width * height <= 3840 * 2160) */ {
|
||||
defaultBitrate = 40000 * (fps / 30.0);
|
||||
}
|
||||
|
||||
// We should always be exactly on a slider position with default bitrates
|
||||
|
||||
defaultBitrate = round(resolutionFactor * frameRateFactor) * 1000;
|
||||
_bitrate = MIN(defaultBitrate, 100000);
|
||||
assert(bitrateTable[[self getSliderValueForBitrate:_bitrate]] == _bitrate);
|
||||
[self.bitrateSlider setValue:[self getSliderValueForBitrate:_bitrate] animated:YES];
|
||||
|
||||
[self updateBitrateText];
|
||||
}
|
||||
|
||||
- (void) newResolutionChosen {
|
||||
BOOL lastSegmentSelected = [self.resolutionSelector selectedSegmentIndex] + 1 == [self.resolutionSelector numberOfSegments];
|
||||
if (lastSegmentSelected) {
|
||||
[self promptCustomResolutionDialog];
|
||||
}
|
||||
else {
|
||||
[self updateBitrate];
|
||||
[self updateResolutionDisplayViewText];
|
||||
_lastSelectedResolutionIndex = [self.resolutionSelector selectedSegmentIndex];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) promptCustomResolutionDialog {
|
||||
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Enter Custom Resolution" message:nil preferredStyle:UIAlertControllerStyleAlert];
|
||||
|
||||
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
|
||||
textField.placeholder = @"Video Width";
|
||||
textField.clearButtonMode = UITextFieldViewModeAlways;
|
||||
textField.borderStyle = UITextBorderStyleRoundedRect;
|
||||
textField.keyboardType = UIKeyboardTypeNumberPad;
|
||||
|
||||
if (resolutionTable[RESOLUTION_TABLE_CUSTOM_INDEX].width == 0) {
|
||||
textField.text = @"";
|
||||
}
|
||||
else {
|
||||
textField.text = [NSString stringWithFormat:@"%d", (int) resolutionTable[RESOLUTION_TABLE_CUSTOM_INDEX].width];
|
||||
}
|
||||
}];
|
||||
|
||||
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
|
||||
textField.placeholder = @"Video Height";
|
||||
textField.clearButtonMode = UITextFieldViewModeAlways;
|
||||
textField.borderStyle = UITextBorderStyleRoundedRect;
|
||||
textField.keyboardType = UIKeyboardTypeNumberPad;
|
||||
|
||||
if (resolutionTable[RESOLUTION_TABLE_CUSTOM_INDEX].height == 0) {
|
||||
textField.text = @"";
|
||||
}
|
||||
else {
|
||||
textField.text = [NSString stringWithFormat:@"%d", (int) resolutionTable[RESOLUTION_TABLE_CUSTOM_INDEX].height];
|
||||
}
|
||||
}];
|
||||
|
||||
[alertController addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
NSArray * textfields = alertController.textFields;
|
||||
UITextField *widthField = textfields[0];
|
||||
UITextField *heightField = textfields[1];
|
||||
|
||||
long width = [widthField.text integerValue];
|
||||
long height = [heightField.text integerValue];
|
||||
if (width <= 0 || height <= 0) {
|
||||
// Restore the previous selection
|
||||
[self.resolutionSelector setSelectedSegmentIndex:self->_lastSelectedResolutionIndex];
|
||||
return;
|
||||
}
|
||||
|
||||
// H.264 maximum
|
||||
int maxResolutionDimension = 4096;
|
||||
if (@available(iOS 11.0, tvOS 11.0, *)) {
|
||||
if (VTIsHardwareDecodeSupported(kCMVideoCodecType_HEVC)) {
|
||||
// HEVC maximum
|
||||
maxResolutionDimension = 8192;
|
||||
}
|
||||
}
|
||||
|
||||
// Cap to maximum valid dimensions
|
||||
width = MIN(width, maxResolutionDimension);
|
||||
height = MIN(height, maxResolutionDimension);
|
||||
|
||||
// Cap to minimum valid dimensions
|
||||
width = MAX(width, 256);
|
||||
height = MAX(height, 256);
|
||||
|
||||
resolutionTable[RESOLUTION_TABLE_CUSTOM_INDEX] = CGSizeMake(width, height);
|
||||
[self updateBitrate];
|
||||
[self updateResolutionDisplayViewText];
|
||||
self->_lastSelectedResolutionIndex = [self.resolutionSelector selectedSegmentIndex];
|
||||
|
||||
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Custom Resolution Selected" message: @"Custom resolutions are not officially supported by GeForce Experience, so it will not set your host display resolution. You will need to set it manually while in game.\n\nResolutions that are not supported by your client or host PC may cause streaming errors." preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alertController addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]];
|
||||
[self presentViewController:alertController animated:YES completion:nil];
|
||||
}]];
|
||||
|
||||
[alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
|
||||
// Restore the previous selection
|
||||
[self.resolutionSelector setSelectedSegmentIndex:self->_lastSelectedResolutionIndex];
|
||||
}]];
|
||||
|
||||
[self presentViewController:alertController animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)resolutionDisplayViewTapped:(UITapGestureRecognizer *)sender {
|
||||
NSURL *url = [NSURL URLWithString:@"https://moonlight-stream.org/custom-resolution"];
|
||||
if ([[UIApplication sharedApplication] canOpenURL:url]) {
|
||||
[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) updateResolutionDisplayViewText {
|
||||
NSInteger width = [self getChosenStreamWidth];
|
||||
NSInteger height = [self getChosenStreamHeight];
|
||||
CGFloat viewFrameWidth = self.resolutionDisplayView.frame.size.width;
|
||||
CGFloat viewFrameHeight = self.resolutionDisplayView.frame.size.height;
|
||||
CGFloat padding = 10;
|
||||
CGFloat fontSize = [UIFont smallSystemFontSize];
|
||||
|
||||
for (UIView *subview in self.resolutionDisplayView.subviews) {
|
||||
[subview removeFromSuperview];
|
||||
}
|
||||
UILabel *label1 = [[UILabel alloc] init];
|
||||
label1.text = @"Set PC/Game resolution: ";
|
||||
label1.font = [UIFont systemFontOfSize:fontSize];
|
||||
[label1 sizeToFit];
|
||||
label1.frame = CGRectMake(padding, (viewFrameHeight - label1.frame.size.height) / 2, label1.frame.size.width, label1.frame.size.height);
|
||||
|
||||
UILabel *label2 = [[UILabel alloc] init];
|
||||
label2.text = [NSString stringWithFormat:@"%ld x %ld", (long)width, (long)height];
|
||||
[label2 sizeToFit];
|
||||
label2.frame = CGRectMake(viewFrameWidth - label2.frame.size.width - padding, (viewFrameHeight - label2.frame.size.height) / 2, label2.frame.size.width, label2.frame.size.height);
|
||||
|
||||
[self.resolutionDisplayView addSubview:label1];
|
||||
[self.resolutionDisplayView addSubview:label2];
|
||||
}
|
||||
|
||||
- (void) bitrateSliderMoved {
|
||||
assert(self.bitrateSlider.value < (sizeof(bitrateTable) / sizeof(*bitrateTable)));
|
||||
_bitrate = bitrateTable[(int)self.bitrateSlider.value];
|
||||
@@ -282,14 +480,46 @@ static const int bitrateTable[] = {
|
||||
}
|
||||
}
|
||||
|
||||
- (uint32_t) getChosenCodecPreference {
|
||||
// Auto is always the last segment
|
||||
if (self.codecSelector.selectedSegmentIndex == self.codecSelector.numberOfSegments - 1) {
|
||||
return CODEC_PREF_AUTO;
|
||||
}
|
||||
else {
|
||||
switch (self.codecSelector.selectedSegmentIndex) {
|
||||
case 0:
|
||||
return CODEC_PREF_H264;
|
||||
|
||||
case 1:
|
||||
return CODEC_PREF_HEVC;
|
||||
|
||||
case 2:
|
||||
return CODEC_PREF_AV1;
|
||||
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInteger) getChosenStreamHeight {
|
||||
const int resolutionTable[] = { 360, 720, 1080, 2160 };
|
||||
return resolutionTable[[self.resolutionSelector selectedSegmentIndex]];
|
||||
// because the 4k resolution can be removed
|
||||
BOOL lastSegmentSelected = [self.resolutionSelector selectedSegmentIndex] + 1 == [self.resolutionSelector numberOfSegments];
|
||||
if (lastSegmentSelected) {
|
||||
return resolutionTable[RESOLUTION_TABLE_CUSTOM_INDEX].height;
|
||||
}
|
||||
|
||||
return resolutionTable[[self.resolutionSelector selectedSegmentIndex]].height;
|
||||
}
|
||||
|
||||
- (NSInteger) getChosenStreamWidth {
|
||||
// Assumes fixed 16:9 aspect ratio
|
||||
return ([self getChosenStreamHeight] * 16) / 9;
|
||||
// because the 4k resolution can be removed
|
||||
BOOL lastSegmentSelected = [self.resolutionSelector selectedSegmentIndex] + 1 == [self.resolutionSelector numberOfSegments];
|
||||
if (lastSegmentSelected) {
|
||||
return resolutionTable[RESOLUTION_TABLE_CUSTOM_INDEX].width;
|
||||
}
|
||||
|
||||
return resolutionTable[[self.resolutionSelector selectedSegmentIndex]].width;
|
||||
}
|
||||
|
||||
- (void) saveSettings {
|
||||
@@ -300,21 +530,27 @@ static const int bitrateTable[] = {
|
||||
NSInteger onscreenControls = [self.onscreenControlSelector selectedSegmentIndex];
|
||||
BOOL optimizeGames = [self.optimizeSettingsSelector selectedSegmentIndex] == 1;
|
||||
BOOL multiController = [self.multiControllerSelector selectedSegmentIndex] == 1;
|
||||
BOOL swapABXYButtons = [self.swapABXYButtonsSelector selectedSegmentIndex] == 1;
|
||||
BOOL audioOnPC = [self.audioOnPCSelector selectedSegmentIndex] == 1;
|
||||
BOOL useHevc = [self.hevcSelector selectedSegmentIndex] == 1;
|
||||
uint32_t preferredCodec = [self getChosenCodecPreference];
|
||||
BOOL btMouseSupport = [self.btMouseSelector selectedSegmentIndex] == 1;
|
||||
BOOL useFramePacing = [self.framePacingSelector selectedSegmentIndex] == 1;
|
||||
BOOL absoluteTouchMode = [self.touchModeSelector selectedSegmentIndex] == 1;
|
||||
BOOL statsOverlay = [self.statsOverlaySelector selectedSegmentIndex] == 1;
|
||||
BOOL enableHdr = [self.hdrSelector selectedSegmentIndex] == 1;
|
||||
[dataMan saveSettingsWithBitrate:_bitrate
|
||||
framerate:framerate
|
||||
height:height
|
||||
width:width
|
||||
audioConfig:2 // Stereo
|
||||
onscreenControls:onscreenControls
|
||||
optimizeGames:optimizeGames
|
||||
multiController:multiController
|
||||
swapABXYButtons:swapABXYButtons
|
||||
audioOnPC:audioOnPC
|
||||
useHevc:useHevc
|
||||
enableHdr:NO
|
||||
preferredCodec:preferredCodec
|
||||
useFramePacing:useFramePacing
|
||||
enableHdr:enableHdr
|
||||
btMouseSupport:btMouseSupport
|
||||
absoluteTouchMode:absoluteTouchMode
|
||||
statsOverlay:statsOverlay];
|
||||
|
||||
@@ -15,10 +15,12 @@
|
||||
#if TARGET_OS_TV
|
||||
@import GameController;
|
||||
|
||||
@interface StreamFrameViewController : GCEventViewController <ConnectionCallbacks, InputPresenceDelegate, UserInteractionDelegate, UIScrollViewDelegate>
|
||||
@interface StreamFrameViewController : GCEventViewController <ConnectionCallbacks, ControllerSupportDelegate, UserInteractionDelegate, UIScrollViewDelegate>
|
||||
#else
|
||||
@interface StreamFrameViewController : UIViewController <ConnectionCallbacks, InputPresenceDelegate, UserInteractionDelegate, UIScrollViewDelegate>
|
||||
@interface StreamFrameViewController : UIViewController <ConnectionCallbacks, ControllerSupportDelegate, UserInteractionDelegate, UIScrollViewDelegate>
|
||||
#endif
|
||||
@property (nonatomic) StreamConfiguration* streamConfig;
|
||||
|
||||
-(void)updatePreferredDisplayMode:(BOOL)streamActive;
|
||||
|
||||
@end
|
||||
|
||||
@@ -18,6 +18,18 @@
|
||||
#include <arpa/inet.h>
|
||||
#include <Limelight.h>
|
||||
|
||||
#if TARGET_OS_TV
|
||||
#import <AVFoundation/AVDisplayCriteria.h>
|
||||
#import <AVKit/AVDisplayManager.h>
|
||||
#import <AVKit/UIWindow.h>
|
||||
#endif
|
||||
|
||||
@interface AVDisplayCriteria()
|
||||
@property(readonly) int videoDynamicRange;
|
||||
@property(readonly, nonatomic) float refreshRate;
|
||||
- (id)initWithRefreshRate:(float)arg1 videoDynamicRange:(int)arg2;
|
||||
@end
|
||||
|
||||
@implementation StreamFrameViewController {
|
||||
ControllerSupport *_controllerSupport;
|
||||
StreamManager *_streamMan;
|
||||
@@ -92,7 +104,7 @@
|
||||
[_spinner startAnimating];
|
||||
_spinner.center = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height / 2 - _stageLabel.frame.size.height - _spinner.frame.size.height);
|
||||
|
||||
_controllerSupport = [[ControllerSupport alloc] initWithConfig:self.streamConfig presenceDelegate:self];
|
||||
_controllerSupport = [[ControllerSupport alloc] initWithConfig:self.streamConfig delegate:self];
|
||||
_inactivityTimer = nil;
|
||||
|
||||
_streamView = [[StreamView alloc] initWithFrame:self.view.frame];
|
||||
@@ -294,6 +306,9 @@
|
||||
}
|
||||
|
||||
- (void) returnToMainFrame {
|
||||
// Reset display mode back to default
|
||||
[self updatePreferredDisplayMode:NO];
|
||||
|
||||
[_statsUpdateTimer invalidate];
|
||||
_statsUpdateTimer = nil;
|
||||
|
||||
@@ -362,6 +377,8 @@
|
||||
|
||||
[self->_streamView showOnScreenControls];
|
||||
|
||||
[self->_controllerSupport connectionEstablished];
|
||||
|
||||
if (self->_settings.statsOverlay) {
|
||||
self->_statsUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:1.0f
|
||||
target:self
|
||||
@@ -416,10 +433,27 @@
|
||||
message = @"Something went wrong on your host PC when starting the stream.\n\nMake sure you don't have any DRM-protected content open on your host PC. You can also try restarting your host PC.\n\nIf the issue persists, try reinstalling your GPU drivers and GeForce Experience.";
|
||||
break;
|
||||
|
||||
default:
|
||||
title = @"Connection Terminated";
|
||||
message = @"The connection was terminated";
|
||||
case ML_ERROR_FRAME_CONVERSION:
|
||||
title = @"Connection Error";
|
||||
message = @"The host PC reported a fatal video encoding error.\n\nTry disabling HDR mode, changing the streaming resolution, or changing your host PC's display resolution.";
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
NSString* errorString;
|
||||
if (abs(errorCode) > 1000) {
|
||||
// We'll assume large errors are hex values
|
||||
errorString = [NSString stringWithFormat:@"%08X", (uint32_t)errorCode];
|
||||
}
|
||||
else {
|
||||
// Smaller values will just be printed as decimal (probably errno.h values)
|
||||
errorString = [NSString stringWithFormat:@"%d", errorCode];
|
||||
}
|
||||
|
||||
title = @"Connection Terminated";
|
||||
message = [NSString stringWithFormat: @"The connection was terminated\n\nError code: %@", errorString];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -506,6 +540,24 @@
|
||||
[_controllerSupport rumble:controllerNumber lowFreqMotor:lowFreqMotor highFreqMotor:highFreqMotor];
|
||||
}
|
||||
|
||||
- (void) rumbleTriggers:(uint16_t)controllerNumber leftTrigger:(uint16_t)leftTrigger rightTrigger:(uint16_t)rightTrigger {
|
||||
Log(LOG_I, @"Trigger rumble on gamepad %d: %04x %04x", controllerNumber, leftTrigger, rightTrigger);
|
||||
|
||||
[_controllerSupport rumbleTriggers:controllerNumber leftTrigger:leftTrigger rightTrigger:rightTrigger];
|
||||
}
|
||||
|
||||
- (void) setMotionEventState:(uint16_t)controllerNumber motionType:(uint8_t)motionType reportRateHz:(uint16_t)reportRateHz {
|
||||
Log(LOG_I, @"Set motion state on gamepad %d: %02x %u Hz", controllerNumber, motionType, reportRateHz);
|
||||
|
||||
[_controllerSupport setMotionEventState:controllerNumber motionType:motionType reportRateHz:reportRateHz];
|
||||
}
|
||||
|
||||
- (void) setControllerLed:(uint16_t)controllerNumber r:(uint8_t)r g:(uint8_t)g b:(uint8_t)b {
|
||||
Log(LOG_I, @"Set controller LED on gamepad %d: l%02x%02x%02x", controllerNumber, r, g, b);
|
||||
|
||||
[_controllerSupport setControllerLed:controllerNumber r:r g:g b:b];
|
||||
}
|
||||
|
||||
- (void)connectionStatusUpdate:(int)status {
|
||||
Log(LOG_W, @"Connection status update: %d", status);
|
||||
|
||||
@@ -532,6 +584,42 @@
|
||||
});
|
||||
}
|
||||
|
||||
- (void) updatePreferredDisplayMode:(BOOL)streamActive {
|
||||
#if TARGET_OS_TV
|
||||
if (@available(tvOS 11.2, *)) {
|
||||
UIWindow* window = [[[UIApplication sharedApplication] delegate] window];
|
||||
AVDisplayManager* displayManager = [window avDisplayManager];
|
||||
|
||||
// This logic comes from Kodi and MrMC
|
||||
if (streamActive) {
|
||||
int dynamicRange;
|
||||
|
||||
if (LiGetCurrentHostDisplayHdrMode()) {
|
||||
dynamicRange = 2; // HDR10
|
||||
}
|
||||
else {
|
||||
dynamicRange = 0; // SDR
|
||||
}
|
||||
|
||||
AVDisplayCriteria* displayCriteria = [[AVDisplayCriteria alloc] initWithRefreshRate:[_settings.framerate floatValue]
|
||||
videoDynamicRange:dynamicRange];
|
||||
displayManager.preferredDisplayCriteria = displayCriteria;
|
||||
}
|
||||
else {
|
||||
// Switch back to the default display mode
|
||||
displayManager.preferredDisplayCriteria = nil;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void) setHdrMode:(bool)enabled {
|
||||
Log(LOG_I, @"HDR is now: %s", enabled ? "active" : "inactive");
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self updatePreferredDisplayMode:YES];
|
||||
});
|
||||
}
|
||||
|
||||
- (void) videoContentShown {
|
||||
[_spinner stopAnimating];
|
||||
[self.view setBackgroundColor:[UIColor blackColor]];
|
||||
@@ -559,6 +647,12 @@
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void) streamExitRequested {
|
||||
Log(LOG_I, @"Gamepad combo requested stream exit");
|
||||
|
||||
[self returnToMainFrame];
|
||||
}
|
||||
|
||||
- (void)userInteractionBegan {
|
||||
// Disable hiding home bar when user is interacting.
|
||||
// iOS will force it to be shown anyway, but it will
|
||||
|
||||
@@ -54,6 +54,8 @@
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>LSSupportsGameMode</key>
|
||||
<true/>
|
||||
<key>UIUserInterfaceStyle</key>
|
||||
<string>Dark</string>
|
||||
</dict>
|
||||
|
||||
@@ -25,12 +25,14 @@
|
||||
<array>
|
||||
<string>0</string>
|
||||
<string>1</string>
|
||||
<string>3</string>
|
||||
<string>2</string>
|
||||
</array>
|
||||
<key>Titles</key>
|
||||
<array>
|
||||
<string>720p</string>
|
||||
<string>1080p</string>
|
||||
<string>1440p</string>
|
||||
<string>4K</string>
|
||||
</array>
|
||||
</dict>
|
||||
@@ -73,6 +75,8 @@
|
||||
<string>20 Mbps</string>
|
||||
<string>25 Mbps</string>
|
||||
<string>50 Mbps</string>
|
||||
<string>70 Mbps</string>
|
||||
<string>80 Mbps</string>
|
||||
<string>100 Mbps</string>
|
||||
<string>150 Mbps</string>
|
||||
</array>
|
||||
@@ -86,10 +90,34 @@
|
||||
<string>20000</string>
|
||||
<string>25000</string>
|
||||
<string>50000</string>
|
||||
<string>70000</string>
|
||||
<string>80000</string>
|
||||
<string>100000</string>
|
||||
<string>150000</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSMultiValueSpecifier</string>
|
||||
<key>Title</key>
|
||||
<string>Audio Configuration</string>
|
||||
<key>Key</key>
|
||||
<string>audioConfig</string>
|
||||
<key>DefaultValue</key>
|
||||
<string>2</string>
|
||||
<key>Titles</key>
|
||||
<array>
|
||||
<string>Stereo</string>
|
||||
<string>LPCM 5.1</string>
|
||||
<string>LPCM 7.1</string>
|
||||
</array>
|
||||
<key>Values</key>
|
||||
<array>
|
||||
<string>2</string>
|
||||
<string>6</string>
|
||||
<string>8</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
@@ -132,6 +160,16 @@
|
||||
<key>DefaultValue</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSToggleSwitchSpecifier</string>
|
||||
<key>Title</key>
|
||||
<string>Swap A/B and X/Y Buttons</string>
|
||||
<key>Key</key>
|
||||
<string>swapABXYButtons</string>
|
||||
<key>DefaultValue</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSToggleSwitchSpecifier</string>
|
||||
@@ -150,13 +188,45 @@
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSToggleSwitchSpecifier</string>
|
||||
<string>PSMultiValueSpecifier</string>
|
||||
<key>Title</key>
|
||||
<string>Use HEVC Codec</string>
|
||||
<string>Preferred Codec</string>
|
||||
<key>Key</key>
|
||||
<string>useHevc</string>
|
||||
<string>preferredCodec</string>
|
||||
<key>DefaultValue</key>
|
||||
<true/>
|
||||
<string>0</string>
|
||||
<key>Values</key>
|
||||
<array>
|
||||
<string>0</string>
|
||||
<string>1</string>
|
||||
<string>2</string>
|
||||
</array>
|
||||
<key>Titles</key>
|
||||
<array>
|
||||
<string>Auto</string>
|
||||
<string>H.264</string>
|
||||
<string>HEVC</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSMultiValueSpecifier</string>
|
||||
<key>Title</key>
|
||||
<string>Frame Pacing Preference</string>
|
||||
<key>Key</key>
|
||||
<string>useFramePacing</string>
|
||||
<key>DefaultValue</key>
|
||||
<string>0</string>
|
||||
<key>Values</key>
|
||||
<array>
|
||||
<string>0</string>
|
||||
<string>1</string>
|
||||
</array>
|
||||
<key>Titles</key>
|
||||
<array>
|
||||
<string>Lowest Latency</string>
|
||||
<string>Smoothest Video</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
|
||||
@@ -9,8 +9,12 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "AppDelegate.h"
|
||||
|
||||
#define SDL_MAIN_HANDLED
|
||||
#import <SDL.h>
|
||||
|
||||
int main(int argc, char * argv[]) {
|
||||
@autoreleasepool {
|
||||
SDL_SetMainReady();
|
||||
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 48;
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
@@ -24,15 +24,26 @@
|
||||
9865DC38213287FE0005B9B9 /* LoadingFrameViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FB4A23B71A9D3637004D2EF2 /* LoadingFrameViewController.m */; };
|
||||
9865DC3C2132922E0005B9B9 /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9865DC3B2132922E0005B9B9 /* GameController.framework */; };
|
||||
9865DC3E21332D660005B9B9 /* MainFrameViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FB89462519F646E200339C8A /* MainFrameViewController.m */; };
|
||||
98882A062AF60F7000C5A11C /* libavcodec.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 98882A032AF60F5300C5A11C /* libavcodec.a */; };
|
||||
98882A072AF60F7200C5A11C /* libavformat.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 98882A052AF60F5300C5A11C /* libavformat.a */; };
|
||||
98882A082AF60F7400C5A11C /* libavutil.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 98882A042AF60F5300C5A11C /* libavutil.a */; };
|
||||
98882A0C2AF60F9E00C5A11C /* libavcodec.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 98882A0A2AF60F9000C5A11C /* libavcodec.a */; };
|
||||
98882A0D2AF60FA000C5A11C /* libavformat.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 98882A0B2AF60F9000C5A11C /* libavformat.a */; };
|
||||
98882A0E2AF60FA300C5A11C /* libavutil.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 98882A092AF60F9000C5A11C /* libavutil.a */; };
|
||||
988FCD41293B091B003050E2 /* KeyboardInputField.m in Sources */ = {isa = PBXBuildFile; fileRef = 988FCD40293B091B003050E2 /* KeyboardInputField.m */; };
|
||||
988FCD42293B091B003050E2 /* KeyboardInputField.m in Sources */ = {isa = PBXBuildFile; fileRef = 988FCD40293B091B003050E2 /* KeyboardInputField.m */; };
|
||||
9890CF6B203B7EE1006C4B06 /* libxml2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 9890CF6A203B7EE1006C4B06 /* libxml2.tbd */; };
|
||||
9896219723D15C7000211983 /* X1Kit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9896219623D15C7000211983 /* X1Kit.swift */; };
|
||||
9896219A23D56E8100211983 /* X1Kit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9896219623D15C7000211983 /* X1Kit.swift */; };
|
||||
9897B6A1221260EF00966419 /* Controller.m in Sources */ = {isa = PBXBuildFile; fileRef = 9897B6A0221260EF00966419 /* Controller.m */; };
|
||||
9897B6A62212732C00966419 /* Controller.m in Sources */ = {isa = PBXBuildFile; fileRef = 9897B6A0221260EF00966419 /* Controller.m */; };
|
||||
98B9CE6D27B2144B00B473C4 /* AVKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 98B9CE6C27B2144B00B473C4 /* AVKit.framework */; };
|
||||
98CFB82F1CAD481B0048EF74 /* libmoonlight-common.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 98AB2E841CAD46840089BB98 /* libmoonlight-common.a */; };
|
||||
98D5856D1C0EA79600F6CC00 /* TemporaryHost.m in Sources */ = {isa = PBXBuildFile; fileRef = 98D5856C1C0EA79600F6CC00 /* TemporaryHost.m */; };
|
||||
98D585701C0ED0E800F6CC00 /* TemporarySettings.m in Sources */ = {isa = PBXBuildFile; fileRef = 98D5856F1C0ED0E800F6CC00 /* TemporarySettings.m */; };
|
||||
DC1F5A07206436B20037755F /* ConnectionHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = DC1F5A06206436B20037755F /* ConnectionHelper.m */; };
|
||||
F74BEF9C2C1A705600224667 /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = F74BEF9B2C1A705600224667 /* OpenSSL */; };
|
||||
F74BEFA42C1A80E400224667 /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = F74BEFA32C1A80E400224667 /* OpenSSL */; };
|
||||
FB1A674D2131E65900507771 /* KeyboardSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = FB1A674C2131E65900507771 /* KeyboardSupport.m */; };
|
||||
FB1A67602132419700507771 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FB1A675E2132419700507771 /* Main.storyboard */; };
|
||||
FB1A67622132419A00507771 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FB1A67612132419A00507771 /* Assets.xcassets */; };
|
||||
@@ -69,8 +80,6 @@
|
||||
FB1A67E02132460A00507771 /* Logger.m in Sources */ = {isa = PBXBuildFile; fileRef = FBD1C8E11A8AD71400C6703C /* Logger.m */; };
|
||||
FB1A67E32132498A00507771 /* Limelight.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = FB290D0519B2C406004C83CF /* Limelight.xcdatamodeld */; };
|
||||
FB1A67E521324A1F00507771 /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB1A67E421324A1F00507771 /* CoreData.framework */; };
|
||||
FB1A67E621324DD600507771 /* libcrypto.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FB8946E019F6AFB800339C8A /* libcrypto.a */; };
|
||||
FB1A67E721324DD600507771 /* libssl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FB8946E119F6AFB800339C8A /* libssl.a */; };
|
||||
FB1A67E821324DE300507771 /* libopus.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FB8946EA19F6AFB800339C8A /* libopus.a */; };
|
||||
FB1A67EA21324DF300507771 /* libxml2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = FB1A67E921324DF300507771 /* libxml2.tbd */; };
|
||||
FB1A6819213284FB00507771 /* UIComputerView.m in Sources */ = {isa = PBXBuildFile; fileRef = FBDE86DF19F7A837001C18A8 /* UIComputerView.m */; };
|
||||
@@ -113,8 +122,6 @@
|
||||
FB89463519F646E200339C8A /* MainFrameViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FB89462519F646E200339C8A /* MainFrameViewController.m */; };
|
||||
FB89463619F646E200339C8A /* StreamFrameViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FB89462719F646E200339C8A /* StreamFrameViewController.m */; };
|
||||
FB89463819F6473800339C8A /* Launch Screen.xib in Resources */ = {isa = PBXBuildFile; fileRef = FB89463719F6473800339C8A /* Launch Screen.xib */; };
|
||||
FB8946EB19F6AFE100339C8A /* libcrypto.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FB8946E019F6AFB800339C8A /* libcrypto.a */; };
|
||||
FB8946EC19F6AFE400339C8A /* libssl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FB8946E119F6AFB800339C8A /* libssl.a */; };
|
||||
FB8946ED19F6AFE800339C8A /* libopus.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FB8946EA19F6AFB800339C8A /* libopus.a */; };
|
||||
FB9AFD281A7C84ED00872C98 /* HttpResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = FB9AFD271A7C84ED00872C98 /* HttpResponse.m */; };
|
||||
FB9AFD321A7D867C00872C98 /* AppAssetRetriever.m in Sources */ = {isa = PBXBuildFile; fileRef = FB9AFD311A7D867C00872C98 /* AppAssetRetriever.m */; };
|
||||
@@ -163,6 +170,7 @@
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
566E9D2B2770B23A00EF7BFE /* Moonlight v1.7.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Moonlight v1.7.xcdatamodel"; sourceTree = "<group>"; };
|
||||
693B3A9A218638CD00982F7B /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = "<group>"; };
|
||||
9803CCAB254F9EAF00EE185E /* ConnectionCallbacks.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ConnectionCallbacks.h; sourceTree = "<group>"; };
|
||||
98132E8C20BC9A62007A053F /* Moonlight v1.1.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Moonlight v1.1.xcdatamodel"; sourceTree = "<group>"; };
|
||||
@@ -178,16 +186,28 @@
|
||||
9827E7A7251436EA00F25707 /* HapticContext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HapticContext.h; sourceTree = "<group>"; };
|
||||
9832D1341BBCD5C50036EF48 /* TemporaryApp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TemporaryApp.h; path = Database/TemporaryApp.h; sourceTree = "<group>"; };
|
||||
9832D1351BBCD5C50036EF48 /* TemporaryApp.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TemporaryApp.m; path = Database/TemporaryApp.m; sourceTree = "<group>"; };
|
||||
984CD023288A310D0097D2D4 /* Moonlight v1.8.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Moonlight v1.8.xcdatamodel"; sourceTree = "<group>"; };
|
||||
98517B1B21CE0A9000481377 /* Moonlight v1.3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Moonlight v1.3.xcdatamodel"; sourceTree = "<group>"; };
|
||||
98608BDD22DC0C2C000E5672 /* Moonlight v1.4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Moonlight v1.4.xcdatamodel"; sourceTree = "<group>"; };
|
||||
9865DC3B2132922E0005B9B9 /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS11.4.sdk/System/Library/Frameworks/GameController.framework; sourceTree = DEVELOPER_DIR; };
|
||||
986CCE6C2133E45300168291 /* Moonlight v1.2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Moonlight v1.2.xcdatamodel"; sourceTree = "<group>"; };
|
||||
986E28A528EA989100758361 /* Moonlight v1.9.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Moonlight v1.9.xcdatamodel"; sourceTree = "<group>"; };
|
||||
9874E8962AE95E1D00130A3C /* Moonlight v1.10.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Moonlight v1.10.xcdatamodel"; sourceTree = "<group>"; };
|
||||
98783FEA242EAC5D00F00EF4 /* Moonlight v1.5.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Moonlight v1.5.xcdatamodel"; sourceTree = "<group>"; };
|
||||
98882A032AF60F5300C5A11C /* libavcodec.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libavcodec.a; path = libs/FFmpeg/lib/iOS/libavcodec.a; sourceTree = "<group>"; };
|
||||
98882A042AF60F5300C5A11C /* libavutil.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libavutil.a; path = libs/FFmpeg/lib/iOS/libavutil.a; sourceTree = "<group>"; };
|
||||
98882A052AF60F5300C5A11C /* libavformat.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libavformat.a; path = libs/FFmpeg/lib/iOS/libavformat.a; sourceTree = "<group>"; };
|
||||
98882A092AF60F9000C5A11C /* libavutil.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libavutil.a; path = libs/FFmpeg/lib/tvOS/libavutil.a; sourceTree = "<group>"; };
|
||||
98882A0A2AF60F9000C5A11C /* libavcodec.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libavcodec.a; path = libs/FFmpeg/lib/tvOS/libavcodec.a; sourceTree = "<group>"; };
|
||||
98882A0B2AF60F9000C5A11C /* libavformat.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libavformat.a; path = libs/FFmpeg/lib/tvOS/libavformat.a; sourceTree = "<group>"; };
|
||||
988FCD3F293B091B003050E2 /* KeyboardInputField.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyboardInputField.h; sourceTree = "<group>"; };
|
||||
988FCD40293B091B003050E2 /* KeyboardInputField.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KeyboardInputField.m; sourceTree = "<group>"; };
|
||||
9890CF6A203B7EE1006C4B06 /* libxml2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libxml2.tbd; path = usr/lib/libxml2.tbd; sourceTree = SDKROOT; };
|
||||
9896219623D15C7000211983 /* X1Kit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = X1Kit.swift; path = X1Kit/Sources/X1Kit/X1Kit.swift; sourceTree = "<group>"; };
|
||||
9897B6A0221260EF00966419 /* Controller.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Controller.m; sourceTree = "<group>"; };
|
||||
9897B6A32212610800966419 /* Controller.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Controller.h; sourceTree = "<group>"; };
|
||||
98AB2E7F1CAD46830089BB98 /* moonlight-common.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = "moonlight-common.xcodeproj"; path = "moonlight-common/moonlight-common.xcodeproj"; sourceTree = "<group>"; };
|
||||
98B9CE6C27B2144B00B473C4 /* AVKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVKit.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS15.2.sdk/System/Library/Frameworks/AVKit.framework; sourceTree = DEVELOPER_DIR; };
|
||||
98D5856B1C0EA79600F6CC00 /* TemporaryHost.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TemporaryHost.h; path = Database/TemporaryHost.h; sourceTree = "<group>"; };
|
||||
98D5856C1C0EA79600F6CC00 /* TemporaryHost.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TemporaryHost.m; path = Database/TemporaryHost.m; sourceTree = "<group>"; };
|
||||
98D5856E1C0ED0E800F6CC00 /* TemporarySettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TemporarySettings.h; path = Database/TemporarySettings.h; sourceTree = "<group>"; };
|
||||
@@ -268,83 +288,6 @@
|
||||
FB89462619F646E200339C8A /* StreamFrameViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StreamFrameViewController.h; sourceTree = "<group>"; };
|
||||
FB89462719F646E200339C8A /* StreamFrameViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StreamFrameViewController.m; sourceTree = "<group>"; };
|
||||
FB89463719F6473800339C8A /* Launch Screen.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = "Launch Screen.xib"; sourceTree = SOURCE_ROOT; };
|
||||
FB89469419F6AFB800339C8A /* aes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = aes.h; sourceTree = "<group>"; };
|
||||
FB89469519F6AFB800339C8A /* asn1.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = asn1.h; sourceTree = "<group>"; };
|
||||
FB89469619F6AFB800339C8A /* asn1_mac.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = asn1_mac.h; sourceTree = "<group>"; };
|
||||
FB89469719F6AFB800339C8A /* asn1t.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = asn1t.h; sourceTree = "<group>"; };
|
||||
FB89469819F6AFB800339C8A /* bio.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = bio.h; sourceTree = "<group>"; };
|
||||
FB89469919F6AFB800339C8A /* blowfish.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = blowfish.h; sourceTree = "<group>"; };
|
||||
FB89469A19F6AFB800339C8A /* bn.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = bn.h; sourceTree = "<group>"; };
|
||||
FB89469B19F6AFB800339C8A /* buffer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = buffer.h; sourceTree = "<group>"; };
|
||||
FB89469C19F6AFB800339C8A /* camellia.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = camellia.h; sourceTree = "<group>"; };
|
||||
FB89469D19F6AFB800339C8A /* cast.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cast.h; sourceTree = "<group>"; };
|
||||
FB89469E19F6AFB800339C8A /* cmac.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cmac.h; sourceTree = "<group>"; };
|
||||
FB89469F19F6AFB800339C8A /* cms.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cms.h; sourceTree = "<group>"; };
|
||||
FB8946A019F6AFB800339C8A /* comp.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = comp.h; sourceTree = "<group>"; };
|
||||
FB8946A119F6AFB800339C8A /* conf.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = conf.h; sourceTree = "<group>"; };
|
||||
FB8946A219F6AFB800339C8A /* conf_api.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = conf_api.h; sourceTree = "<group>"; };
|
||||
FB8946A319F6AFB800339C8A /* crypto.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = crypto.h; sourceTree = "<group>"; };
|
||||
FB8946A419F6AFB800339C8A /* des.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = des.h; sourceTree = "<group>"; };
|
||||
FB8946A519F6AFB800339C8A /* des_old.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = des_old.h; sourceTree = "<group>"; };
|
||||
FB8946A619F6AFB800339C8A /* dh.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = dh.h; sourceTree = "<group>"; };
|
||||
FB8946A719F6AFB800339C8A /* dsa.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = dsa.h; sourceTree = "<group>"; };
|
||||
FB8946A819F6AFB800339C8A /* dso.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = dso.h; sourceTree = "<group>"; };
|
||||
FB8946A919F6AFB800339C8A /* dtls1.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = dtls1.h; sourceTree = "<group>"; };
|
||||
FB8946AA19F6AFB800339C8A /* e_os2.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = e_os2.h; sourceTree = "<group>"; };
|
||||
FB8946AB19F6AFB800339C8A /* ebcdic.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ebcdic.h; sourceTree = "<group>"; };
|
||||
FB8946AC19F6AFB800339C8A /* ec.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ec.h; sourceTree = "<group>"; };
|
||||
FB8946AD19F6AFB800339C8A /* ecdh.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ecdh.h; sourceTree = "<group>"; };
|
||||
FB8946AE19F6AFB800339C8A /* ecdsa.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ecdsa.h; sourceTree = "<group>"; };
|
||||
FB8946AF19F6AFB800339C8A /* engine.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = engine.h; sourceTree = "<group>"; };
|
||||
FB8946B019F6AFB800339C8A /* err.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = err.h; sourceTree = "<group>"; };
|
||||
FB8946B119F6AFB800339C8A /* evp.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = evp.h; sourceTree = "<group>"; };
|
||||
FB8946B219F6AFB800339C8A /* hmac.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = hmac.h; sourceTree = "<group>"; };
|
||||
FB8946B319F6AFB800339C8A /* idea.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = idea.h; sourceTree = "<group>"; };
|
||||
FB8946B419F6AFB800339C8A /* krb5_asn.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = krb5_asn.h; sourceTree = "<group>"; };
|
||||
FB8946B519F6AFB800339C8A /* kssl.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = kssl.h; sourceTree = "<group>"; };
|
||||
FB8946B619F6AFB800339C8A /* lhash.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = lhash.h; sourceTree = "<group>"; };
|
||||
FB8946B719F6AFB800339C8A /* md4.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = md4.h; sourceTree = "<group>"; };
|
||||
FB8946B819F6AFB800339C8A /* md5.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = md5.h; sourceTree = "<group>"; };
|
||||
FB8946B919F6AFB800339C8A /* mdc2.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mdc2.h; sourceTree = "<group>"; };
|
||||
FB8946BA19F6AFB800339C8A /* modes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = modes.h; sourceTree = "<group>"; };
|
||||
FB8946BB19F6AFB800339C8A /* obj_mac.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = obj_mac.h; sourceTree = "<group>"; };
|
||||
FB8946BC19F6AFB800339C8A /* objects.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = objects.h; sourceTree = "<group>"; };
|
||||
FB8946BD19F6AFB800339C8A /* ocsp.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ocsp.h; sourceTree = "<group>"; };
|
||||
FB8946BE19F6AFB800339C8A /* opensslconf.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = opensslconf.h; sourceTree = "<group>"; };
|
||||
FB8946BF19F6AFB800339C8A /* opensslv.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = opensslv.h; sourceTree = "<group>"; };
|
||||
FB8946C019F6AFB800339C8A /* ossl_typ.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ossl_typ.h; sourceTree = "<group>"; };
|
||||
FB8946C119F6AFB800339C8A /* pem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = pem.h; sourceTree = "<group>"; };
|
||||
FB8946C219F6AFB800339C8A /* pem2.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = pem2.h; sourceTree = "<group>"; };
|
||||
FB8946C319F6AFB800339C8A /* pkcs12.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = pkcs12.h; sourceTree = "<group>"; };
|
||||
FB8946C419F6AFB800339C8A /* pkcs7.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = pkcs7.h; sourceTree = "<group>"; };
|
||||
FB8946C519F6AFB800339C8A /* pqueue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = pqueue.h; sourceTree = "<group>"; };
|
||||
FB8946C619F6AFB800339C8A /* rand.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = rand.h; sourceTree = "<group>"; };
|
||||
FB8946C719F6AFB800339C8A /* rc2.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = rc2.h; sourceTree = "<group>"; };
|
||||
FB8946C819F6AFB800339C8A /* rc4.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = rc4.h; sourceTree = "<group>"; };
|
||||
FB8946C919F6AFB800339C8A /* ripemd.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ripemd.h; sourceTree = "<group>"; };
|
||||
FB8946CA19F6AFB800339C8A /* rsa.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = rsa.h; sourceTree = "<group>"; };
|
||||
FB8946CB19F6AFB800339C8A /* safestack.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = safestack.h; sourceTree = "<group>"; };
|
||||
FB8946CC19F6AFB800339C8A /* seed.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = seed.h; sourceTree = "<group>"; };
|
||||
FB8946CD19F6AFB800339C8A /* sha.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = sha.h; sourceTree = "<group>"; };
|
||||
FB8946CE19F6AFB800339C8A /* srp.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = srp.h; sourceTree = "<group>"; };
|
||||
FB8946CF19F6AFB800339C8A /* srtp.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = srtp.h; sourceTree = "<group>"; };
|
||||
FB8946D019F6AFB800339C8A /* ssl.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ssl.h; sourceTree = "<group>"; };
|
||||
FB8946D119F6AFB800339C8A /* ssl2.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ssl2.h; sourceTree = "<group>"; };
|
||||
FB8946D219F6AFB800339C8A /* ssl23.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ssl23.h; sourceTree = "<group>"; };
|
||||
FB8946D319F6AFB800339C8A /* ssl3.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ssl3.h; sourceTree = "<group>"; };
|
||||
FB8946D419F6AFB800339C8A /* stack.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = stack.h; sourceTree = "<group>"; };
|
||||
FB8946D519F6AFB800339C8A /* symhacks.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = symhacks.h; sourceTree = "<group>"; };
|
||||
FB8946D619F6AFB800339C8A /* tls1.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = tls1.h; sourceTree = "<group>"; };
|
||||
FB8946D719F6AFB800339C8A /* ts.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ts.h; sourceTree = "<group>"; };
|
||||
FB8946D819F6AFB800339C8A /* txt_db.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = txt_db.h; sourceTree = "<group>"; };
|
||||
FB8946D919F6AFB800339C8A /* ui.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ui.h; sourceTree = "<group>"; };
|
||||
FB8946DA19F6AFB800339C8A /* ui_compat.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ui_compat.h; sourceTree = "<group>"; };
|
||||
FB8946DB19F6AFB800339C8A /* whrlpool.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = whrlpool.h; sourceTree = "<group>"; };
|
||||
FB8946DC19F6AFB800339C8A /* x509.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = x509.h; sourceTree = "<group>"; };
|
||||
FB8946DD19F6AFB800339C8A /* x509_vfy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = x509_vfy.h; sourceTree = "<group>"; };
|
||||
FB8946DE19F6AFB800339C8A /* x509v3.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = x509v3.h; sourceTree = "<group>"; };
|
||||
FB8946E019F6AFB800339C8A /* libcrypto.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libcrypto.a; sourceTree = "<group>"; };
|
||||
FB8946E119F6AFB800339C8A /* libssl.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libssl.a; sourceTree = "<group>"; };
|
||||
FB8946E519F6AFB800339C8A /* opus.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = opus.h; sourceTree = "<group>"; };
|
||||
FB8946E619F6AFB800339C8A /* opus_defines.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = opus_defines.h; sourceTree = "<group>"; };
|
||||
FB8946E719F6AFB800339C8A /* opus_multistream.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = opus_multistream.h; sourceTree = "<group>"; };
|
||||
@@ -386,12 +329,15 @@
|
||||
files = (
|
||||
9865DC30213260B40005B9B9 /* libmoonlight-common-tv.a in Frameworks */,
|
||||
9865DC3C2132922E0005B9B9 /* GameController.framework in Frameworks */,
|
||||
98882A0D2AF60FA000C5A11C /* libavformat.a in Frameworks */,
|
||||
98882A0E2AF60FA300C5A11C /* libavutil.a in Frameworks */,
|
||||
F74BEFA42C1A80E400224667 /* OpenSSL in Frameworks */,
|
||||
FB1A67EA21324DF300507771 /* libxml2.tbd in Frameworks */,
|
||||
FB1A67E821324DE300507771 /* libopus.a in Frameworks */,
|
||||
98181BEB2791278F00E43572 /* libSDL2.a in Frameworks */,
|
||||
FB1A67E621324DD600507771 /* libcrypto.a in Frameworks */,
|
||||
FB1A67E721324DD600507771 /* libssl.a in Frameworks */,
|
||||
FB1A67E521324A1F00507771 /* CoreData.framework in Frameworks */,
|
||||
98B9CE6D27B2144B00B473C4 /* AVKit.framework in Frameworks */,
|
||||
98882A0C2AF60F9E00C5A11C /* libavcodec.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -402,15 +348,17 @@
|
||||
9890CF6B203B7EE1006C4B06 /* libxml2.tbd in Frameworks */,
|
||||
98CFB82F1CAD481B0048EF74 /* libmoonlight-common.a in Frameworks */,
|
||||
FB8946ED19F6AFE800339C8A /* libopus.a in Frameworks */,
|
||||
FB8946EB19F6AFE100339C8A /* libcrypto.a in Frameworks */,
|
||||
FB8946EC19F6AFE400339C8A /* libssl.a in Frameworks */,
|
||||
98882A082AF60F7400C5A11C /* libavutil.a in Frameworks */,
|
||||
FB290CF419B2C406004C83CF /* CoreGraphics.framework in Frameworks */,
|
||||
FB290CF819B2C406004C83CF /* CoreData.framework in Frameworks */,
|
||||
98181BED2791281100E43572 /* CoreMotion.framework in Frameworks */,
|
||||
F74BEF9C2C1A705600224667 /* OpenSSL in Frameworks */,
|
||||
98882A072AF60F7200C5A11C /* libavformat.a in Frameworks */,
|
||||
FB290CF619B2C406004C83CF /* UIKit.framework in Frameworks */,
|
||||
FB290CF219B2C406004C83CF /* Foundation.framework in Frameworks */,
|
||||
FB4679011A57048000377732 /* CoreFoundation.framework in Frameworks */,
|
||||
98181BE92791277400E43572 /* libSDL2.a in Frameworks */,
|
||||
98882A062AF60F7000C5A11C /* libavcodec.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -470,6 +418,13 @@
|
||||
FB290CF019B2C406004C83CF /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
98882A032AF60F5300C5A11C /* libavcodec.a */,
|
||||
98882A052AF60F5300C5A11C /* libavformat.a */,
|
||||
98882A0A2AF60F9000C5A11C /* libavcodec.a */,
|
||||
98882A0B2AF60F9000C5A11C /* libavformat.a */,
|
||||
98882A042AF60F5300C5A11C /* libavutil.a */,
|
||||
98882A092AF60F9000C5A11C /* libavutil.a */,
|
||||
98B9CE6C27B2144B00B473C4 /* AVKit.framework */,
|
||||
98181BEC2791281100E43572 /* CoreMotion.framework */,
|
||||
98181BE82791275D00E43572 /* libSDL2.a */,
|
||||
98181BEA2791278300E43572 /* libSDL2.a */,
|
||||
@@ -562,6 +517,8 @@
|
||||
9819CC13254F107A008A7C8E /* RelativeTouchHandler.m */,
|
||||
9819CC20254F180F008A7C8E /* AbsoluteTouchHandler.h */,
|
||||
9819CC21254F180F008A7C8E /* AbsoluteTouchHandler.m */,
|
||||
988FCD3F293B091B003050E2 /* KeyboardInputField.h */,
|
||||
988FCD40293B091B003050E2 /* KeyboardInputField.m */,
|
||||
);
|
||||
path = Input;
|
||||
sourceTree = "<group>";
|
||||
@@ -629,120 +586,11 @@
|
||||
FB89468F19F6AFB800339C8A /* libs */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FB89469119F6AFB800339C8A /* openssl */,
|
||||
FB8946E219F6AFB800339C8A /* opus */,
|
||||
);
|
||||
path = libs;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FB89469119F6AFB800339C8A /* openssl */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FB89469219F6AFB800339C8A /* include */,
|
||||
FB8946DF19F6AFB800339C8A /* lib */,
|
||||
);
|
||||
path = openssl;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FB89469219F6AFB800339C8A /* include */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FB89469319F6AFB800339C8A /* openssl */,
|
||||
);
|
||||
path = include;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FB89469319F6AFB800339C8A /* openssl */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FB89469419F6AFB800339C8A /* aes.h */,
|
||||
FB89469519F6AFB800339C8A /* asn1.h */,
|
||||
FB89469619F6AFB800339C8A /* asn1_mac.h */,
|
||||
FB89469719F6AFB800339C8A /* asn1t.h */,
|
||||
FB89469819F6AFB800339C8A /* bio.h */,
|
||||
FB89469919F6AFB800339C8A /* blowfish.h */,
|
||||
FB89469A19F6AFB800339C8A /* bn.h */,
|
||||
FB89469B19F6AFB800339C8A /* buffer.h */,
|
||||
FB89469C19F6AFB800339C8A /* camellia.h */,
|
||||
FB89469D19F6AFB800339C8A /* cast.h */,
|
||||
FB89469E19F6AFB800339C8A /* cmac.h */,
|
||||
FB89469F19F6AFB800339C8A /* cms.h */,
|
||||
FB8946A019F6AFB800339C8A /* comp.h */,
|
||||
FB8946A119F6AFB800339C8A /* conf.h */,
|
||||
FB8946A219F6AFB800339C8A /* conf_api.h */,
|
||||
FB8946A319F6AFB800339C8A /* crypto.h */,
|
||||
FB8946A419F6AFB800339C8A /* des.h */,
|
||||
FB8946A519F6AFB800339C8A /* des_old.h */,
|
||||
FB8946A619F6AFB800339C8A /* dh.h */,
|
||||
FB8946A719F6AFB800339C8A /* dsa.h */,
|
||||
FB8946A819F6AFB800339C8A /* dso.h */,
|
||||
FB8946A919F6AFB800339C8A /* dtls1.h */,
|
||||
FB8946AA19F6AFB800339C8A /* e_os2.h */,
|
||||
FB8946AB19F6AFB800339C8A /* ebcdic.h */,
|
||||
FB8946AC19F6AFB800339C8A /* ec.h */,
|
||||
FB8946AD19F6AFB800339C8A /* ecdh.h */,
|
||||
FB8946AE19F6AFB800339C8A /* ecdsa.h */,
|
||||
FB8946AF19F6AFB800339C8A /* engine.h */,
|
||||
FB8946B019F6AFB800339C8A /* err.h */,
|
||||
FB8946B119F6AFB800339C8A /* evp.h */,
|
||||
FB8946B219F6AFB800339C8A /* hmac.h */,
|
||||
FB8946B319F6AFB800339C8A /* idea.h */,
|
||||
FB8946B419F6AFB800339C8A /* krb5_asn.h */,
|
||||
FB8946B519F6AFB800339C8A /* kssl.h */,
|
||||
FB8946B619F6AFB800339C8A /* lhash.h */,
|
||||
FB8946B719F6AFB800339C8A /* md4.h */,
|
||||
FB8946B819F6AFB800339C8A /* md5.h */,
|
||||
FB8946B919F6AFB800339C8A /* mdc2.h */,
|
||||
FB8946BA19F6AFB800339C8A /* modes.h */,
|
||||
FB8946BB19F6AFB800339C8A /* obj_mac.h */,
|
||||
FB8946BC19F6AFB800339C8A /* objects.h */,
|
||||
FB8946BD19F6AFB800339C8A /* ocsp.h */,
|
||||
FB8946BE19F6AFB800339C8A /* opensslconf.h */,
|
||||
FB8946BF19F6AFB800339C8A /* opensslv.h */,
|
||||
FB8946C019F6AFB800339C8A /* ossl_typ.h */,
|
||||
FB8946C119F6AFB800339C8A /* pem.h */,
|
||||
FB8946C219F6AFB800339C8A /* pem2.h */,
|
||||
FB8946C319F6AFB800339C8A /* pkcs12.h */,
|
||||
FB8946C419F6AFB800339C8A /* pkcs7.h */,
|
||||
FB8946C519F6AFB800339C8A /* pqueue.h */,
|
||||
FB8946C619F6AFB800339C8A /* rand.h */,
|
||||
FB8946C719F6AFB800339C8A /* rc2.h */,
|
||||
FB8946C819F6AFB800339C8A /* rc4.h */,
|
||||
FB8946C919F6AFB800339C8A /* ripemd.h */,
|
||||
FB8946CA19F6AFB800339C8A /* rsa.h */,
|
||||
FB8946CB19F6AFB800339C8A /* safestack.h */,
|
||||
FB8946CC19F6AFB800339C8A /* seed.h */,
|
||||
FB8946CD19F6AFB800339C8A /* sha.h */,
|
||||
FB8946CE19F6AFB800339C8A /* srp.h */,
|
||||
FB8946CF19F6AFB800339C8A /* srtp.h */,
|
||||
FB8946D019F6AFB800339C8A /* ssl.h */,
|
||||
FB8946D119F6AFB800339C8A /* ssl2.h */,
|
||||
FB8946D219F6AFB800339C8A /* ssl23.h */,
|
||||
FB8946D319F6AFB800339C8A /* ssl3.h */,
|
||||
FB8946D419F6AFB800339C8A /* stack.h */,
|
||||
FB8946D519F6AFB800339C8A /* symhacks.h */,
|
||||
FB8946D619F6AFB800339C8A /* tls1.h */,
|
||||
FB8946D719F6AFB800339C8A /* ts.h */,
|
||||
FB8946D819F6AFB800339C8A /* txt_db.h */,
|
||||
FB8946D919F6AFB800339C8A /* ui.h */,
|
||||
FB8946DA19F6AFB800339C8A /* ui_compat.h */,
|
||||
FB8946DB19F6AFB800339C8A /* whrlpool.h */,
|
||||
FB8946DC19F6AFB800339C8A /* x509.h */,
|
||||
FB8946DD19F6AFB800339C8A /* x509_vfy.h */,
|
||||
FB8946DE19F6AFB800339C8A /* x509v3.h */,
|
||||
);
|
||||
path = openssl;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FB8946DF19F6AFB800339C8A /* lib */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FB8946E019F6AFB800339C8A /* libcrypto.a */,
|
||||
FB8946E119F6AFB800339C8A /* libssl.a */,
|
||||
);
|
||||
path = lib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FB8946E219F6AFB800339C8A /* opus */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -854,6 +702,9 @@
|
||||
FB1A68172132509800507771 /* PBXTargetDependency */,
|
||||
);
|
||||
name = "Moonlight TV";
|
||||
packageProductDependencies = (
|
||||
F74BEFA32C1A80E400224667 /* OpenSSL */,
|
||||
);
|
||||
productName = "Moonlight TV";
|
||||
productReference = FB1A67532132419700507771 /* Moonlight TV.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
@@ -872,6 +723,9 @@
|
||||
98AB2E861CAD468B0089BB98 /* PBXTargetDependency */,
|
||||
);
|
||||
name = Moonlight;
|
||||
packageProductDependencies = (
|
||||
F74BEF9B2C1A705600224667 /* OpenSSL */,
|
||||
);
|
||||
productName = Limelight;
|
||||
productReference = FB290CEE19B2C406004C83CF /* Moonlight.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
@@ -882,8 +736,9 @@
|
||||
FB290CE619B2C406004C83CF /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = YES;
|
||||
LastSwiftUpdateCheck = 0730;
|
||||
LastUpgradeCheck = 1200;
|
||||
LastUpgradeCheck = 2700;
|
||||
ORGANIZATIONNAME = "Moonlight Game Streaming Project";
|
||||
TargetAttributes = {
|
||||
FB1A67522132419700507771 = {
|
||||
@@ -897,7 +752,6 @@
|
||||
};
|
||||
};
|
||||
FB290CED19B2C406004C83CF = {
|
||||
DevelopmentTeam = DM46QST4M7;
|
||||
LastSwiftMigration = 1140;
|
||||
};
|
||||
};
|
||||
@@ -911,6 +765,9 @@
|
||||
Base,
|
||||
);
|
||||
mainGroup = FB290CE519B2C406004C83CF;
|
||||
packageReferences = (
|
||||
F74BEF9A2C1A705600224667 /* XCRemoteSwiftPackageReference "OpenSSL-Package" */,
|
||||
);
|
||||
productRefGroup = FB290CEF19B2C406004C83CF /* Products */;
|
||||
projectDirPath = "";
|
||||
projectReferences = (
|
||||
@@ -1015,6 +872,7 @@
|
||||
9819CC1D254F1730008A7C8E /* RelativeTouchHandler.m in Sources */,
|
||||
FB1A67AB213245C500507771 /* CryptoManager.m in Sources */,
|
||||
FB1A67AF213245C500507771 /* IdManager.m in Sources */,
|
||||
988FCD42293B091B003050E2 /* KeyboardInputField.m in Sources */,
|
||||
FB1A67A3213245BD00507771 /* Connection.m in Sources */,
|
||||
FB1A67A5213245BD00507771 /* StreamConfiguration.m in Sources */,
|
||||
FB1A67A7213245BD00507771 /* StreamManager.m in Sources */,
|
||||
@@ -1075,6 +933,7 @@
|
||||
FB4A23B81A9D3637004D2EF2 /* LoadingFrameViewController.m in Sources */,
|
||||
FB9AFD3A1A7E05CE00872C98 /* ServerInfoResponse.m in Sources */,
|
||||
FB89463119F646E200339C8A /* StreamManager.m in Sources */,
|
||||
988FCD41293B091B003050E2 /* KeyboardInputField.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -1124,9 +983,8 @@
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = DM46QST4M7;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_INCREASE_PRECOMPILED_HEADER_SHARING = NO;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
@@ -1137,14 +995,25 @@
|
||||
"$(PROJECT_DIR)/libs/**",
|
||||
);
|
||||
INFOPLIST_FILE = "Moonlight TV/Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.games";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = "$(RECOMMENDED_IPHONEOS_DEPLOYMENT_TARGET)";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
"LIBRARY_SEARCH_PATHS[sdk=appletvos*]" = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/libs/openssl/lib/tvOS",
|
||||
"$(PROJECT_DIR)/libs/opus/lib/tvOS",
|
||||
"$(PROJECT_DIR)/libs/SDL2/lib/tvOS",
|
||||
"$(PROJECT_DIR)/libs/FFmpeg/lib/tvOS",
|
||||
);
|
||||
MARKETING_VERSION = 7.1.3;
|
||||
"LIBRARY_SEARCH_PATHS[sdk=appletvsimulator*]" = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/libs/opus/lib/tvOS-Sim",
|
||||
"$(PROJECT_DIR)/libs/SDL2/lib/tvOS-Sim",
|
||||
"$(PROJECT_DIR)/libs/FFmpeg/lib/tvOS-Sim",
|
||||
);
|
||||
MARKETING_VERSION = 9.0.2;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.moonlight-stream.Moonlight";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -1154,7 +1023,7 @@
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 3;
|
||||
TVOS_DEPLOYMENT_TARGET = 12.0;
|
||||
TVOS_DEPLOYMENT_TARGET = "$(RECOMMENDED_TVOS_DEPLOYMENT_TARGET)";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -1170,9 +1039,8 @@
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = DM46QST4M7;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_INCREASE_PRECOMPILED_HEADER_SHARING = NO;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
@@ -1183,14 +1051,25 @@
|
||||
"$(PROJECT_DIR)/libs/**",
|
||||
);
|
||||
INFOPLIST_FILE = "Moonlight TV/Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.games";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = "$(RECOMMENDED_IPHONEOS_DEPLOYMENT_TARGET)";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
"LIBRARY_SEARCH_PATHS[sdk=appletvos*]" = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/libs/openssl/lib/tvOS",
|
||||
"$(PROJECT_DIR)/libs/opus/lib/tvOS",
|
||||
"$(PROJECT_DIR)/libs/SDL2/lib/tvOS",
|
||||
"$(PROJECT_DIR)/libs/FFmpeg/lib/tvOS",
|
||||
);
|
||||
MARKETING_VERSION = 7.1.3;
|
||||
"LIBRARY_SEARCH_PATHS[sdk=appletvsimulator*]" = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/libs/opus/lib/tvOS-Sim",
|
||||
"$(PROJECT_DIR)/libs/SDL2/lib/tvOS-Sim",
|
||||
"$(PROJECT_DIR)/libs/FFmpeg/lib/tvOS-Sim",
|
||||
);
|
||||
MARKETING_VERSION = 9.0.2;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.moonlight-stream.Moonlight";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -1200,7 +1079,7 @@
|
||||
SWIFT_OBJC_INTERFACE_HEADER_NAME = "Moonlight-Swift.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 3;
|
||||
TVOS_DEPLOYMENT_TARGET = 12.0;
|
||||
TVOS_DEPLOYMENT_TARGET = "$(RECOMMENDED_TVOS_DEPLOYMENT_TARGET)";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -1208,6 +1087,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
@@ -1233,8 +1113,10 @@
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEVELOPMENT_TEAM = DM46QST4M7;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
@@ -1250,9 +1132,10 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.3;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SWIFT_OBJC_INTERFACE_HEADER_NAME = "";
|
||||
SWIFT_VERSION = 4.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
@@ -1263,6 +1146,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
@@ -1288,8 +1172,10 @@
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = YES;
|
||||
DEVELOPMENT_TEAM = DM46QST4M7;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
@@ -1302,9 +1188,10 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.3;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
PROVISIONING_PROFILE = "";
|
||||
SDKROOT = iphoneos;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SWIFT_OBJC_INTERFACE_HEADER_NAME = "";
|
||||
SWIFT_VERSION = 4.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
@@ -1320,7 +1207,6 @@
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
ENABLE_BITCODE = YES;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "Limelight/Limelight-Prefix.pch";
|
||||
HEADER_SEARCH_PATHS = (
|
||||
@@ -1331,15 +1217,26 @@
|
||||
"$(PROJECT_DIR)/libs/**",
|
||||
);
|
||||
INFOPLIST_FILE = "Limelight/Limelight-Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.games";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = "$(RECOMMENDED_IPHONEOS_DEPLOYMENT_TARGET)";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
"LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/libs/opus/lib/iOS",
|
||||
"$(PROJECT_DIR)/libs/openssl/lib/iOS",
|
||||
"$(PROJECT_DIR)/libs/SDL2/lib/iOS",
|
||||
"$(PROJECT_DIR)/libs/FFmpeg/lib/iOS",
|
||||
);
|
||||
MARKETING_VERSION = 7.2.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.moonlight-stream.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
"LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*]" = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/libs/opus/lib/iOS-Sim",
|
||||
"$(PROJECT_DIR)/libs/SDL2/lib/iOS-Sim",
|
||||
"$(PROJECT_DIR)/libs/FFmpeg/lib/iOS-Sim",
|
||||
);
|
||||
MARKETING_VERSION = 9.0.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.moonlight-stream.Moonlight";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE = "";
|
||||
SKIP_INSTALL = NO;
|
||||
@@ -1360,7 +1257,6 @@
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
ENABLE_BITCODE = YES;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "Limelight/Limelight-Prefix.pch";
|
||||
HEADER_SEARCH_PATHS = (
|
||||
@@ -1371,21 +1267,33 @@
|
||||
"$(PROJECT_DIR)/libs/**",
|
||||
);
|
||||
INFOPLIST_FILE = "Limelight/Limelight-Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.games";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = "$(RECOMMENDED_IPHONEOS_DEPLOYMENT_TARGET)";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
"LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/libs/opus/lib/iOS",
|
||||
"$(PROJECT_DIR)/libs/openssl/lib/iOS",
|
||||
"$(PROJECT_DIR)/libs/SDL2/lib/iOS",
|
||||
"$(PROJECT_DIR)/libs/FFmpeg/lib/iOS",
|
||||
);
|
||||
MARKETING_VERSION = 7.2.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.moonlight-stream.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
"LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*]" = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/libs/opus/lib/iOS-Sim",
|
||||
"$(PROJECT_DIR)/libs/SDL2/lib/iOS-Sim",
|
||||
"$(PROJECT_DIR)/libs/FFmpeg/lib/iOS-Sim",
|
||||
);
|
||||
MARKETING_VERSION = 9.0.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.moonlight-stream.Moonlight";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE = "";
|
||||
SKIP_INSTALL = NO;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Limelight/Input/Moonlight-Bridging-Header.h";
|
||||
SWIFT_OBJC_INTERFACE_HEADER_NAME = "Moonlight-Swift.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
||||
SWIFT_VERSION = 5.0;
|
||||
WRAPPER_EXTENSION = app;
|
||||
@@ -1424,10 +1332,38 @@
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
F74BEF9A2C1A705600224667 /* XCRemoteSwiftPackageReference "OpenSSL-Package" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/krzyzanowskim/OpenSSL-Package.git";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 3.1.5004;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
F74BEF9B2C1A705600224667 /* OpenSSL */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = F74BEF9A2C1A705600224667 /* XCRemoteSwiftPackageReference "OpenSSL-Package" */;
|
||||
productName = OpenSSL;
|
||||
};
|
||||
F74BEFA32C1A80E400224667 /* OpenSSL */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = F74BEF9A2C1A705600224667 /* XCRemoteSwiftPackageReference "OpenSSL-Package" */;
|
||||
productName = OpenSSL;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
|
||||
/* Begin XCVersionGroup section */
|
||||
FB290D0519B2C406004C83CF /* Limelight.xcdatamodeld */ = {
|
||||
isa = XCVersionGroup;
|
||||
children = (
|
||||
9874E8962AE95E1D00130A3C /* Moonlight v1.10.xcdatamodel */,
|
||||
986E28A528EA989100758361 /* Moonlight v1.9.xcdatamodel */,
|
||||
984CD023288A310D0097D2D4 /* Moonlight v1.8.xcdatamodel */,
|
||||
566E9D2B2770B23A00EF7BFE /* Moonlight v1.7.xcdatamodel */,
|
||||
9819CC25254F2734008A7C8E /* Moonlight v1.6.xcdatamodel */,
|
||||
98783FEA242EAC5D00F00EF4 /* Moonlight v1.5.xcdatamodel */,
|
||||
98608BDD22DC0C2C000E5672 /* Moonlight v1.4.xcdatamodel */,
|
||||
@@ -1440,7 +1376,7 @@
|
||||
FB4678F21A51BDCB00377732 /* Limelight 0.3.0.xcdatamodel */,
|
||||
FB290D0619B2C406004C83CF /* Limelight.xcdatamodel */,
|
||||
);
|
||||
currentVersion = 9819CC25254F2734008A7C8E /* Moonlight v1.6.xcdatamodel */;
|
||||
currentVersion = 9874E8962AE95E1D00130A3C /* Moonlight v1.10.xcdatamodel */;
|
||||
path = Limelight.xcdatamodeld;
|
||||
sourceTree = "<group>";
|
||||
versionGroupType = wrapper.xcdatamodel;
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:Moonlight.xcodeproj">
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"originHash" : "f17bf6f1131c7255a75ffd6a989b69573cb49c332e612dffb429905d8d7afe72",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "openssl-package",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/krzyzanowskim/OpenSSL-Package.git",
|
||||
"state" : {
|
||||
"revision" : "a3809db22a45e3ff658377a24da252982a306b9b",
|
||||
"version" : "3.6.3000"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 3
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
# Moonlight iOS/tvOS
|
||||
|
||||
[](https://ci.appveyor.com/project/cgutman/moonlight-ios/branch/master)
|
||||
[](https://github.com/moonlight-stream/moonlight-ios/actions/workflows/ci.yml)
|
||||
|
||||
[Moonlight for iOS/tvOS](https://moonlight-stream.org) is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield, but built for iOS and tvOS. Moonlight for iOS/tvOS allows you to stream your full collection of Steam games from
|
||||
your powerful desktop computer to your iOS device or Apple TV.
|
||||
[Moonlight for iOS/tvOS](https://moonlight-stream.org) is an open source client for [Sunshine](https://github.com/LizardByte/Sunshine) and NVIDIA GameStream. Moonlight for iOS/tvOS allows you to stream your full collection of games and apps from your powerful desktop computer to your iOS device or Apple TV.
|
||||
|
||||
Moonlight also has a [PC client](https://github.com/moonlight-stream/moonlight-qt) and [Android client](https://github.com/moonlight-stream/moonlight-android).
|
||||
|
||||
@@ -12,7 +11,7 @@ Check out [the Moonlight wiki](https://github.com/moonlight-stream/moonlight-doc
|
||||
[](https://apps.apple.com/us/app/moonlight-game-streaming/id1000551566)
|
||||
|
||||
## Building
|
||||
* Install Xcode 13 from the [App Store page](https://apps.apple.com/us/app/xcode/id497799835)
|
||||
* Install Xcode from the [App Store page](https://apps.apple.com/us/app/xcode/id497799835)
|
||||
* Run `git clone --recursive https://github.com/moonlight-stream/moonlight-ios.git`
|
||||
* If you've already clone the repo without `--recursive`, run `git submodule update --init --recursive`
|
||||
* Open Moonlight.xcodeproj in Xcode
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
version: 0.0.0.{build}
|
||||
|
||||
clone_depth: 1
|
||||
|
||||
image: macOS
|
||||
|
||||
before_build:
|
||||
- 'git submodule update --init --recursive'
|
||||
|
||||
build_script:
|
||||
- sh: set -o pipefail && xcodebuild -project Moonlight.xcodeproj -scheme "Moonlight" -destination "platform=iOS Simulator,OS=14.3,name=iPhone 11 Pro" clean build | xcpretty
|
||||
- sh: set -o pipefail && xcodebuild -project Moonlight.xcodeproj -scheme "Moonlight" -destination "platform=iOS Simulator,OS=14.3,name=iPad Pro (9.7-inch)" clean build | xcpretty
|
||||
- sh: set -o pipefail && xcodebuild -project Moonlight.xcodeproj -scheme "Moonlight TV" -destination "platform=tvOS Simulator,OS=14.3,name=Apple TV 4K" clean build | xcpretty
|
||||
|
||||
deploy: off
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="19162" targetRuntime="iOS.CocoaTouch.iPad" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES" initialViewController="EVd-wq-ego">
|
||||
<device id="ipad11_0rounded" orientation="landscape" layout="fullscreen" appearance="dark"/>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="22155" targetRuntime="iOS.CocoaTouch.iPad" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES" initialViewController="EVd-wq-ego">
|
||||
<device id="ipad11_0rounded" orientation="portrait" layout="fullscreen" appearance="dark"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19144"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22131"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
@@ -12,7 +12,7 @@
|
||||
<objects>
|
||||
<viewController id="wb7-af-jn8" customClass="MainFrameViewController" sceneMemberID="viewController">
|
||||
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" indicatorStyle="black" dataMode="prototypes" id="TZj-Lc-M9d" customClass="AppCollectionView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="1194" height="784"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="834" height="1120"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="0.3333333432674408" green="0.3333333432674408" blue="0.3333333432674408" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="20" minimumInteritemSpacing="20" id="f7l-kG-hJc">
|
||||
@@ -58,8 +58,8 @@
|
||||
<viewControllerLayoutGuide type="top" id="ef6-uF-5Hh"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="XvO-F3-j4u"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="WRy-3f-gEP">
|
||||
<rect key="frame" x="0.0" y="0.0" width="1194" height="834"/>
|
||||
<view key="view" contentMode="scaleToFill" id="WRy-3f-gEP" customClass="UIScrollView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="834" height="1194"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Resolution" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pYZ-GR-EwO">
|
||||
@@ -70,20 +70,28 @@
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="1" translatesAutoresizingMaskIntoConstraints="NO" id="ckc-Dm-8ex">
|
||||
<rect key="frame" x="16" y="49" width="209" height="29"/>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" apportionsSegmentWidthsByContent="YES" segmentControlStyle="plain" selectedSegmentIndex="1" translatesAutoresizingMaskIntoConstraints="NO" id="ckc-Dm-8ex">
|
||||
<rect key="frame" x="16" y="47" width="459" height="32"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segments>
|
||||
<segment title="360p"/>
|
||||
<segment title="720p"/>
|
||||
<segment title="1080p"/>
|
||||
<segment title="4K"/>
|
||||
<segment title="Safe Area"/>
|
||||
<segment title="Full"/>
|
||||
<segment title="Custom"/>
|
||||
</segments>
|
||||
<color key="tintColor" red="0.6716768741607666" green="0.61711704730987549" blue="0.99902987480163574" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="selectedSegmentTintColor" red="0.6716768741607666" green="0.61711704730987549" blue="0.99902987480163574" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</segmentedControl>
|
||||
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="9cG-JD-glb" userLabel="Resolution Display View">
|
||||
<rect key="frame" x="22" y="89" width="447" height="35"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
</view>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Frame Rate" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Aiv-r8-9k2">
|
||||
<rect key="frame" x="16" y="85" width="88" height="21"/>
|
||||
<rect key="frame" x="16" y="132" width="88" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<color key="tintColor" red="0.81533776120000001" green="0.85979419950000002" blue="0.91372549020000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
@@ -91,7 +99,7 @@
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="1" translatesAutoresizingMaskIntoConstraints="NO" id="lGK-vl-pdw">
|
||||
<rect key="frame" x="16" y="114" width="209" height="29"/>
|
||||
<rect key="frame" x="16" y="161" width="459" height="28"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segments>
|
||||
<segment title="30 FPS"/>
|
||||
@@ -102,7 +110,7 @@
|
||||
<color key="selectedSegmentTintColor" red="0.6716768741607666" green="0.61711704730987549" blue="0.99902987480163574" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</segmentedControl>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Bitrate: 10 Mbps" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lMt-4H-fkV">
|
||||
<rect key="frame" x="16" y="150" width="151" height="21"/>
|
||||
<rect key="frame" x="16" y="197" width="151" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<color key="tintColor" red="0.81533776120000001" green="0.85979419950000002" blue="0.91372549020000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
@@ -110,19 +118,19 @@
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<slider opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="20" minValue="1" maxValue="100" translatesAutoresizingMaskIntoConstraints="NO" id="JAY-nj-UNz">
|
||||
<rect key="frame" x="14" y="179" width="213" height="31"/>
|
||||
<rect key="frame" x="14" y="226" width="459" height="31"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<color key="tintColor" red="0.6716768741607666" green="0.61711704730987549" blue="0.99902987480163574" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</slider>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Touch Mode" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="h5g-U1-Z2J" userLabel="Touch Mode">
|
||||
<rect key="frame" x="16" y="208" width="186" height="21"/>
|
||||
<rect key="frame" x="16" y="255" width="186" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="e4G-id-vjI" userLabel="Touch Mode Selector">
|
||||
<rect key="frame" x="16" y="237" width="209" height="32"/>
|
||||
<rect key="frame" x="16" y="284" width="459" height="28"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segments>
|
||||
<segment title="Touchpad"/>
|
||||
@@ -132,7 +140,7 @@
|
||||
<color key="selectedSegmentTintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</segmentedControl>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="On-Screen Controls" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="LGV-5y-piG">
|
||||
<rect key="frame" x="16" y="276" width="153" height="21"/>
|
||||
<rect key="frame" x="16" y="323" width="153" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<color key="tintColor" red="0.81533776120000001" green="0.85979419950000002" blue="0.91372549020000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
@@ -140,7 +148,7 @@
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="1" translatesAutoresizingMaskIntoConstraints="NO" id="qSU-wh-tqA">
|
||||
<rect key="frame" x="16" y="305" width="209" height="29"/>
|
||||
<rect key="frame" x="16" y="352" width="459" height="28"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segments>
|
||||
<segment title="Off"/>
|
||||
@@ -152,14 +160,14 @@
|
||||
<color key="selectedSegmentTintColor" red="0.6716768741607666" green="0.61711704730987549" blue="0.99902987480163574" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</segmentedControl>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Optimize Game Settings" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="WSI-9V-G6c">
|
||||
<rect key="frame" x="16" y="341" width="186" height="21"/>
|
||||
<rect key="frame" x="16" y="388" width="186" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="1" translatesAutoresizingMaskIntoConstraints="NO" id="nCO-OT-dV1" userLabel="Optimize Settings Selector">
|
||||
<rect key="frame" x="16" y="370" width="209" height="28"/>
|
||||
<rect key="frame" x="16" y="417" width="459" height="28"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segments>
|
||||
<segment title="No"/>
|
||||
@@ -169,14 +177,14 @@
|
||||
<color key="selectedSegmentTintColor" red="0.6716768741607666" green="0.61711704730987549" blue="0.99902987480163574" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</segmentedControl>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Multi-Controller Mode" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bFD-Ng-abn">
|
||||
<rect key="frame" x="16" y="406" width="169" height="21"/>
|
||||
<rect key="frame" x="16" y="453" width="169" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="1" translatesAutoresizingMaskIntoConstraints="NO" id="KlC-fG-wEi" userLabel="MC Selector">
|
||||
<rect key="frame" x="16" y="435" width="209" height="28"/>
|
||||
<rect key="frame" x="16" y="482" width="459" height="28"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segments>
|
||||
<segment title="Single"/>
|
||||
@@ -185,15 +193,22 @@
|
||||
<color key="tintColor" red="0.6716768741607666" green="0.61711704730987549" blue="0.99902987480163574" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="selectedSegmentTintColor" red="0.6716768741607666" green="0.61711704730987549" blue="0.99902987480163574" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</segmentedControl>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Swap A/B and X/Y Buttons" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Dll-7x-18l">
|
||||
<rect key="frame" x="16" y="517" width="217" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Play Audio on PC" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fXq-oD-MrL">
|
||||
<rect key="frame" x="16" y="471" width="131" height="21"/>
|
||||
<rect key="frame" x="16" y="583" width="131" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="WDY-9o-6Hb" userLabel="PC Audio Selector">
|
||||
<rect key="frame" x="16" y="500" width="209" height="28"/>
|
||||
<rect key="frame" x="16" y="612" width="459" height="28"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segments>
|
||||
<segment title="No"/>
|
||||
@@ -202,32 +217,68 @@
|
||||
<color key="tintColor" red="0.6716768741607666" green="0.61711704730987549" blue="0.99902987480163574" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="selectedSegmentTintColor" red="0.6716768741607666" green="0.61711704730987549" blue="0.99902987480163574" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</segmentedControl>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="HEVC Video" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="6w5-Md-NpV">
|
||||
<rect key="frame" x="16" y="536" width="93" height="21"/>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Preferred Codec" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="6w5-Md-NpV" userLabel="Preferred Codec">
|
||||
<rect key="frame" x="16" y="648" width="126" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="aFy-0w-YPe" userLabel="HEVC Selector">
|
||||
<rect key="frame" x="16" y="565" width="209" height="28"/>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="3" translatesAutoresizingMaskIntoConstraints="NO" id="aFy-0w-YPe" userLabel="Codec Selector">
|
||||
<rect key="frame" x="16" y="675" width="459" height="32"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segments>
|
||||
<segment title="H.264"/>
|
||||
<segment title="HEVC"/>
|
||||
<segment title="AV1"/>
|
||||
<segment title="Auto"/>
|
||||
</segments>
|
||||
<color key="tintColor" red="0.6716768741607666" green="0.61711704730987549" blue="0.99902987480163574" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="selectedSegmentTintColor" red="0.6716768741607666" green="0.61711704730987549" blue="0.99902987480163574" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</segmentedControl>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="HDR (Beta)" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bZL-t1-mk7">
|
||||
<rect key="frame" x="16" y="712" width="86" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="OA2-SA-bBO" userLabel="HDR Selector">
|
||||
<rect key="frame" x="16" y="741" width="459" height="28"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segments>
|
||||
<segment title="No"/>
|
||||
<segment title="Yes"/>
|
||||
</segments>
|
||||
<color key="tintColor" red="0.6716768741607666" green="0.61711704730987549" blue="0.99902987480163574" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="selectedSegmentTintColor" red="0.6716768741607666" green="0.61711704730987549" blue="0.99902987480163574" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="tintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="selectedSegmentTintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</segmentedControl>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Frame Pacing Preference" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ZWC-Gw-pSq">
|
||||
<rect key="frame" x="16" y="776" width="459" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="7va-uJ-IfD" userLabel="framePacingSelector">
|
||||
<rect key="frame" x="16" y="805" width="459" height="28"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segments>
|
||||
<segment title="Lowest Latency"/>
|
||||
<segment title="Smoothest Video"/>
|
||||
</segments>
|
||||
<color key="tintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="selectedSegmentTintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</segmentedControl>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Citrix X1 Mouse Support" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="t9f-V8-GCm">
|
||||
<rect key="frame" x="16" y="600" width="186" height="21"/>
|
||||
<rect key="frame" x="16" y="840" width="186" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="KMg-3j-F9p" userLabel="Citrix X1 Selector">
|
||||
<rect key="frame" x="16" y="629" width="209" height="28"/>
|
||||
<rect key="frame" x="16" y="869" width="459" height="28"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segments>
|
||||
<segment title="No"/>
|
||||
@@ -237,14 +288,24 @@
|
||||
<color key="selectedSegmentTintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</segmentedControl>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Statistics Overlay" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="s2i-ZL-Gz4" userLabel="Statistics Overlay">
|
||||
<rect key="frame" x="16" y="664" width="186" height="21"/>
|
||||
<rect key="frame" x="16" y="904" width="186" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="NLH-qN-qCo" userLabel="Statistics Overlay Selector">
|
||||
<rect key="frame" x="16" y="693" width="209" height="28"/>
|
||||
<rect key="frame" x="16" y="933" width="459" height="28"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segments>
|
||||
<segment title="No"/>
|
||||
<segment title="Yes"/>
|
||||
</segments>
|
||||
<color key="tintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="selectedSegmentTintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</segmentedControl>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="lcO-6v-Tbg" userLabel="ABXY Swap Selector">
|
||||
<rect key="frame" x="16" y="544" width="459" height="28"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segments>
|
||||
<segment title="No"/>
|
||||
@@ -261,19 +322,24 @@
|
||||
<outlet property="bitrateLabel" destination="lMt-4H-fkV" id="ItM-l3-1Jk"/>
|
||||
<outlet property="bitrateSlider" destination="JAY-nj-UNz" id="fHd-v5-9Vo"/>
|
||||
<outlet property="btMouseSelector" destination="KMg-3j-F9p" id="W45-AB-sJf"/>
|
||||
<outlet property="codecSelector" destination="aFy-0w-YPe" id="DlU-0l-gwz"/>
|
||||
<outlet property="framePacingSelector" destination="7va-uJ-IfD" id="Tbv-s2-u2k"/>
|
||||
<outlet property="framerateSelector" destination="lGK-vl-pdw" id="Kc8-Zv-hdm"/>
|
||||
<outlet property="hevcSelector" destination="aFy-0w-YPe" id="DlU-0l-gwz"/>
|
||||
<outlet property="hdrSelector" destination="OA2-SA-bBO" id="uUL-mc-4We"/>
|
||||
<outlet property="multiControllerSelector" destination="KlC-fG-wEi" id="giM-F7-So5"/>
|
||||
<outlet property="onscreenControlSelector" destination="qSU-wh-tqA" id="j8d-fB-Z2c"/>
|
||||
<outlet property="optimizeSettingsSelector" destination="nCO-OT-dV1" id="FB0-Rt-C44"/>
|
||||
<outlet property="resolutionDisplayView" destination="9cG-JD-glb" id="TT1-hy-MUC"/>
|
||||
<outlet property="resolutionSelector" destination="ckc-Dm-8ex" id="rl6-rx-wd3"/>
|
||||
<outlet property="scrollView" destination="WRy-3f-gEP" id="V8B-oF-27B"/>
|
||||
<outlet property="statsOverlaySelector" destination="NLH-qN-qCo" id="EF8-JF-O3S"/>
|
||||
<outlet property="swapABXYButtonsSelector" destination="lcO-6v-Tbg" id="7VO-yt-0Sp"/>
|
||||
<outlet property="touchModeSelector" destination="e4G-id-vjI" id="Cwh-fO-Pou"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="01j-TU-OoL" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="608.59375" y="834.9609375"/>
|
||||
<point key="canvasLocation" x="607.91366906474821" y="834.67336683417091"/>
|
||||
</scene>
|
||||
<!--Reveal View Controller-->
|
||||
<scene sceneID="rR7-ZT-bc7">
|
||||
@@ -284,7 +350,7 @@
|
||||
<viewControllerLayoutGuide type="bottom" id="YRO-Lg-RCg"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="hGs-BR-t4b">
|
||||
<rect key="frame" x="0.0" y="0.0" width="1194" height="834"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="834" height="1194"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</view>
|
||||
@@ -302,7 +368,7 @@
|
||||
<objects>
|
||||
<navigationController id="baW-rW-rBd" sceneMemberID="viewController">
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" barStyle="black" translucent="NO" id="RUe-14-4Ya">
|
||||
<rect key="frame" x="0.0" y="0.0" width="1194" height="50"/>
|
||||
<rect key="frame" x="0.0" y="24" width="834" height="50"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<color key="backgroundColor" red="0.66666668653488159" green="0.66666668653488159" blue="0.66666668653488159" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="barTintColor" red="0.66666668653488159" green="0.66666668653488159" blue="0.66666668653488159" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
@@ -324,7 +390,7 @@
|
||||
<viewControllerLayoutGuide type="bottom" id="vu8-3s-HlY"/>
|
||||
</layoutGuides>
|
||||
<view key="view" multipleTouchEnabled="YES" contentMode="scaleToFill" id="SMn-1j-R3I">
|
||||
<rect key="frame" x="0.0" y="0.0" width="1194" height="834"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="834" height="1194"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<color key="backgroundColor" red="0.3333357871" green="0.33332890269999998" blue="0.33333355190000002" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
|
||||
</view>
|
||||
@@ -346,7 +412,7 @@
|
||||
<viewControllerLayoutGuide type="bottom" id="YZj-An-sqS"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="le1-tU-NEp">
|
||||
<rect key="frame" x="0.0" y="0.0" width="1194" height="834"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="834" height="1194"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" fixedFrame="YES" animating="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="N0D-dj-EuF">
|
||||
@@ -366,8 +432,11 @@
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<systemColor name="systemBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
<systemColor name="viewFlipsideBackgroundColor">
|
||||
<color red="0.1215686274509804" green="0.12941176470588239" blue="0.14117647058823529" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color red="0.12156862745098039" green="0.12941176470588237" blue="0.14117647058823529" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="19162" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES" initialViewController="DL0-L5-LOv">
|
||||
<device id="retina6_1" orientation="landscape" appearance="light"/>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="22155" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES" initialViewController="DL0-L5-LOv">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19144"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22131"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
@@ -12,7 +13,7 @@
|
||||
<objects>
|
||||
<viewController id="dgh-JZ-Q7z" customClass="MainFrameViewController" sceneMemberID="viewController">
|
||||
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" id="Rtu-AT-Alw" customClass="AppCollectionView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="896" height="370"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="804"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="0.3333333432674408" green="0.3333333432674408" blue="0.3333333432674408" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="15" minimumInteritemSpacing="15" id="YcZ-cR-3tK">
|
||||
@@ -60,7 +61,7 @@
|
||||
<viewControllerLayoutGuide type="bottom" id="iBq-4E-Dbr"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="Usg-e0-g4a">
|
||||
<rect key="frame" x="0.0" y="0.0" width="896" height="414"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</view>
|
||||
@@ -82,37 +83,45 @@
|
||||
<viewControllerLayoutGuide type="bottom" id="HaZ-6S-ZpN"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="iNk-qF-gIr" customClass="UIScrollView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="896" height="414"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Resolution" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="8zy-ri-Dqc">
|
||||
<rect key="frame" x="16" y="20" width="82" height="21"/>
|
||||
<rect key="frame" x="22" y="20" width="208" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" red="0.9513210654258728" green="0.97490358352661133" blue="0.99987185001373291" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="1" translatesAutoresizingMaskIntoConstraints="NO" id="PCM-t4-Sha">
|
||||
<rect key="frame" x="16" y="49" width="200" height="29"/>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" apportionsSegmentWidthsByContent="YES" segmentControlStyle="plain" selectedSegmentIndex="1" translatesAutoresizingMaskIntoConstraints="NO" id="PCM-t4-Sha">
|
||||
<rect key="frame" x="16" y="47" width="450" height="32"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segments>
|
||||
<segment title="360p"/>
|
||||
<segment title="720p"/>
|
||||
<segment title="1080p"/>
|
||||
<segment title="4K"/>
|
||||
<segment title="Safe Area"/>
|
||||
<segment title="Full"/>
|
||||
<segment title="Custom"/>
|
||||
</segments>
|
||||
<color key="tintColor" red="0.6716768741607666" green="0.61711704730987549" blue="0.99902987480163574" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="selectedSegmentTintColor" red="0.6716768741607666" green="0.61711704730987549" blue="0.99902987480163574" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</segmentedControl>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Frame Rate" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Kkv-DF-MAl">
|
||||
<rect key="frame" x="16" y="85" width="88" height="21"/>
|
||||
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Dej-Bj-F6i" userLabel="Resolution Display View">
|
||||
<rect key="frame" x="15" y="90" width="447" height="35"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
</view>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Frame Rate" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Kkv-DF-MAl">
|
||||
<rect key="frame" x="22" y="133" width="208" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" red="0.9513210654258728" green="0.97490358352661133" blue="0.99987185001373291" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="1" translatesAutoresizingMaskIntoConstraints="NO" id="dLF-qJ-2nY">
|
||||
<rect key="frame" x="16" y="114" width="200" height="29"/>
|
||||
<rect key="frame" x="16" y="162" width="450" height="28"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segments>
|
||||
<segment title="30 FPS"/>
|
||||
@@ -123,26 +132,26 @@
|
||||
<color key="selectedSegmentTintColor" red="0.6716768741607666" green="0.61711704730987549" blue="0.99902987480163574" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</segmentedControl>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Bitrate: 10 Mbps" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="SBv-Wn-LB7">
|
||||
<rect key="frame" x="16" y="150" width="151" height="21"/>
|
||||
<rect key="frame" x="22" y="198" width="208" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" red="0.9513210654258728" green="0.97490358352661133" blue="0.99987185001373291" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<slider opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="20" minValue="1" maxValue="100" translatesAutoresizingMaskIntoConstraints="NO" id="3nn-MI-9Xu">
|
||||
<rect key="frame" x="14" y="179" width="204" height="31"/>
|
||||
<rect key="frame" x="14" y="227" width="450" height="31"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<color key="tintColor" red="0.6716768741607666" green="0.61711704730987549" blue="0.99902987480163574" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</slider>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Touch Mode" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="CHp-14-aTt">
|
||||
<rect key="frame" x="17" y="217" width="186" height="21"/>
|
||||
<rect key="frame" x="22" y="265" width="208" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="2lg-HW-tku" userLabel="Touch Mode Selector">
|
||||
<rect key="frame" x="17" y="245" width="200" height="32"/>
|
||||
<rect key="frame" x="16" y="293" width="450" height="28"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segments>
|
||||
<segment title="Touchpad"/>
|
||||
@@ -152,14 +161,14 @@
|
||||
<color key="selectedSegmentTintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</segmentedControl>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="On-Screen Controls" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="HZT-dd-DuQ">
|
||||
<rect key="frame" x="20" y="284" width="153" height="21"/>
|
||||
<rect key="frame" x="22" y="328" width="208" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" red="0.9513210654258728" green="0.97490358352661133" blue="0.99987185001373291" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="1" translatesAutoresizingMaskIntoConstraints="NO" id="WGf-9d-eAm">
|
||||
<rect key="frame" x="20" y="313" width="200" height="29"/>
|
||||
<rect key="frame" x="16" y="357" width="450" height="28"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segments>
|
||||
<segment title="Off"/>
|
||||
@@ -171,14 +180,14 @@
|
||||
<color key="selectedSegmentTintColor" red="0.6716768741607666" green="0.61711704730987549" blue="0.99902987480163574" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</segmentedControl>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Optimize Game Settings" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hxa-gT-JXC">
|
||||
<rect key="frame" x="20" y="349" width="186" height="21"/>
|
||||
<rect key="frame" x="22" y="395" width="218" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="1" translatesAutoresizingMaskIntoConstraints="NO" id="Gob-Lu-b1y" userLabel="Optimize Settings Selector">
|
||||
<rect key="frame" x="20" y="378" width="200" height="29"/>
|
||||
<rect key="frame" x="16" y="424" width="450" height="28"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segments>
|
||||
<segment title="No"/>
|
||||
@@ -188,14 +197,14 @@
|
||||
<color key="selectedSegmentTintColor" red="0.6716768741607666" green="0.61711704730987549" blue="0.99902987480163574" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</segmentedControl>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Multi-Controller Mode" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Jps-kj-bYJ">
|
||||
<rect key="frame" x="20" y="414" width="169" height="21"/>
|
||||
<rect key="frame" x="22" y="461" width="218" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="1" translatesAutoresizingMaskIntoConstraints="NO" id="OCT-oL-Dqb" userLabel="MC Selector">
|
||||
<rect key="frame" x="20" y="443" width="200" height="29"/>
|
||||
<rect key="frame" x="16" y="490" width="450" height="28"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segments>
|
||||
<segment title="Single"/>
|
||||
@@ -204,15 +213,32 @@
|
||||
<color key="tintColor" red="0.6716768741607666" green="0.61711704730987549" blue="0.99902987480163574" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="selectedSegmentTintColor" red="0.6716768741607666" green="0.61711704730987549" blue="0.99902987480163574" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</segmentedControl>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Play Audio on PC" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="T6M-nz-TW0">
|
||||
<rect key="frame" x="20" y="479" width="131" height="21"/>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Swap A/B and X/Y Buttons" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="2p1-r1-Gvf">
|
||||
<rect key="frame" x="22" y="527" width="218" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="P8R-nu-8Ln" userLabel="ABXY Swap Selector">
|
||||
<rect key="frame" x="16" y="555" width="450" height="28"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segments>
|
||||
<segment title="No"/>
|
||||
<segment title="Yes"/>
|
||||
</segments>
|
||||
<color key="tintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="selectedSegmentTintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</segmentedControl>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Play Audio on PC" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="T6M-nz-TW0">
|
||||
<rect key="frame" x="22" y="591" width="143" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="8GA-Js-kQN" userLabel="PC Audio Selector">
|
||||
<rect key="frame" x="20" y="508" width="200" height="29"/>
|
||||
<rect key="frame" x="16" y="620" width="450" height="28"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segments>
|
||||
<segment title="No"/>
|
||||
@@ -221,32 +247,68 @@
|
||||
<color key="tintColor" red="0.6716768741607666" green="0.61711704730987549" blue="0.99902987480163574" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="selectedSegmentTintColor" red="0.6716768741607666" green="0.61711704730987549" blue="0.99902987480163574" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</segmentedControl>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="HEVC Video" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="BEG-Id-J8y">
|
||||
<rect key="frame" x="20" y="544" width="93" height="21"/>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Preferred Codec" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="BEG-Id-J8y" userLabel="Preferred Codec">
|
||||
<rect key="frame" x="22" y="656" width="134" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="AbS-CW-fjP" userLabel="HEVC Selector">
|
||||
<rect key="frame" x="20" y="573" width="200" height="29"/>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="3" translatesAutoresizingMaskIntoConstraints="NO" id="AbS-CW-fjP" userLabel="Codec Selector">
|
||||
<rect key="frame" x="16" y="681" width="450" height="32"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segments>
|
||||
<segment title="H.264"/>
|
||||
<segment title="HEVC"/>
|
||||
<segment title="AV1"/>
|
||||
<segment title="Auto"/>
|
||||
</segments>
|
||||
<color key="tintColor" red="0.6716768741607666" green="0.61711704730987549" blue="0.99902987480163574" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="selectedSegmentTintColor" red="0.6716768741607666" green="0.61711704730987549" blue="0.99902987480163574" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</segmentedControl>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="HDR (Beta)" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lBe-dn-yc7">
|
||||
<rect key="frame" x="22" y="719" width="98" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="9xD-GL-9xq" userLabel="HDR Selector">
|
||||
<rect key="frame" x="16" y="748" width="450" height="28"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segments>
|
||||
<segment title="No"/>
|
||||
<segment title="Yes"/>
|
||||
</segments>
|
||||
<color key="tintColor" red="0.6716768741607666" green="0.61711704730987549" blue="0.99902987480163574" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="selectedSegmentTintColor" red="0.6716768741607666" green="0.61711704730987549" blue="0.99902987480163574" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="tintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="selectedSegmentTintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</segmentedControl>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Frame Pacing Preference" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="x1l-m4-eAM">
|
||||
<rect key="frame" x="22" y="784" width="450" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="d2a-Ka-H2c" userLabel="Frame Pacing Selector">
|
||||
<rect key="frame" x="16" y="813" width="450" height="28"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segments>
|
||||
<segment title="Lowest Latency"/>
|
||||
<segment title="Smoothest Video"/>
|
||||
</segments>
|
||||
<color key="tintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="selectedSegmentTintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</segmentedControl>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Citrix X1 Mouse Support" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="glN-9Q-GKD">
|
||||
<rect key="frame" x="20" y="609" width="186" height="21"/>
|
||||
<rect key="frame" x="22" y="849" width="218" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="o4O-hO-teB" userLabel="Citrix X1 Selector">
|
||||
<rect key="frame" x="20" y="638" width="200" height="29"/>
|
||||
<rect key="frame" x="16" y="878" width="450" height="28"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segments>
|
||||
<segment title="No"/>
|
||||
@@ -256,14 +318,14 @@
|
||||
<color key="selectedSegmentTintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</segmentedControl>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Statistics Overlay" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="WtB-ib-ZDj">
|
||||
<rect key="frame" x="24" y="674" width="186" height="21"/>
|
||||
<rect key="frame" x="22" y="914" width="208" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="rdz-eg-2oR" userLabel="Statistics Overlay Selector">
|
||||
<rect key="frame" x="20" y="703" width="200" height="29"/>
|
||||
<rect key="frame" x="16" y="943" width="450" height="28"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segments>
|
||||
<segment title="No"/>
|
||||
@@ -280,27 +342,31 @@
|
||||
<outlet property="bitrateLabel" destination="SBv-Wn-LB7" id="Wdu-me-Bvd"/>
|
||||
<outlet property="bitrateSlider" destination="3nn-MI-9Xu" id="IuD-Rk-vPp"/>
|
||||
<outlet property="btMouseSelector" destination="o4O-hO-teB" id="bLu-uv-yUX"/>
|
||||
<outlet property="codecSelector" destination="AbS-CW-fjP" id="ohH-Tc-UzM"/>
|
||||
<outlet property="framePacingSelector" destination="d2a-Ka-H2c" id="Ocv-3E-lIm"/>
|
||||
<outlet property="framerateSelector" destination="dLF-qJ-2nY" id="hE3-hk-iwa"/>
|
||||
<outlet property="hevcSelector" destination="AbS-CW-fjP" id="ohH-Tc-UzM"/>
|
||||
<outlet property="hdrSelector" destination="9xD-GL-9xq" id="5Rf-R7-5TO"/>
|
||||
<outlet property="multiControllerSelector" destination="OCT-oL-Dqb" id="bLT-gB-FcH"/>
|
||||
<outlet property="onscreenControlSelector" destination="WGf-9d-eAm" id="hob-se-4Sn"/>
|
||||
<outlet property="optimizeSettingsSelector" destination="Gob-Lu-b1y" id="9Wl-yp-0Ou"/>
|
||||
<outlet property="resolutionDisplayView" destination="Dej-Bj-F6i" id="bi4-N6-snx"/>
|
||||
<outlet property="resolutionSelector" destination="PCM-t4-Sha" id="t60-W2-wkV"/>
|
||||
<outlet property="scrollView" destination="iNk-qF-gIr" id="h7U-Xp-ULQ"/>
|
||||
<outlet property="statsOverlaySelector" destination="rdz-eg-2oR" id="nus-Zq-nwm"/>
|
||||
<outlet property="swapABXYButtonsSelector" destination="P8R-nu-8Ln" id="anv-Y8-0Mi"/>
|
||||
<outlet property="touchModeSelector" destination="2lg-HW-tku" id="HrP-Pz-jQp"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="RWc-Km-KG5" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1974" y="1487"/>
|
||||
<point key="canvasLocation" x="1973.913043478261" y="1486.6071428571429"/>
|
||||
</scene>
|
||||
<!--Navigation Controller-->
|
||||
<scene sceneID="pfX-8A-htT">
|
||||
<objects>
|
||||
<navigationController id="ftZ-kC-fxI" sceneMemberID="viewController">
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" barStyle="black" translucent="NO" id="0ZA-Ec-QgD">
|
||||
<rect key="frame" x="0.0" y="0.0" width="896" height="44"/>
|
||||
<rect key="frame" x="0.0" y="48" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<color key="backgroundColor" red="0.66666668653488159" green="0.66666668653488159" blue="0.66666668653488159" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="barTintColor" red="0.66666668653488159" green="0.66666668653488159" blue="0.66666668653488159" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
@@ -325,7 +391,7 @@
|
||||
<viewControllerLayoutGuide type="bottom" id="kcN-yf-dtN"/>
|
||||
</layoutGuides>
|
||||
<view key="view" multipleTouchEnabled="YES" contentMode="scaleToFill" id="p4u-yn-VXk">
|
||||
<rect key="frame" x="0.0" y="0.0" width="896" height="414"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<color key="backgroundColor" red="0.3333357871" green="0.33332890269999998" blue="0.33333355190000002" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
|
||||
</view>
|
||||
@@ -347,11 +413,11 @@
|
||||
<viewControllerLayoutGuide type="bottom" id="hby-2y-jAk"/>
|
||||
</layoutGuides>
|
||||
<view key="view" opaque="NO" clearsContextBeforeDrawing="NO" contentMode="center" id="nSw-dc-mi4">
|
||||
<rect key="frame" x="0.0" y="0.0" width="896" height="414"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" fixedFrame="YES" animating="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="oNu-Gu-QeM">
|
||||
<rect key="frame" x="266" y="187" width="37" height="37"/>
|
||||
<rect key="frame" x="266" y="426" width="37" height="37"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinY="YES" flexibleMaxY="YES"/>
|
||||
</activityIndicatorView>
|
||||
</subviews>
|
||||
@@ -366,4 +432,9 @@
|
||||
<point key="canvasLocation" x="3031" y="1487"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<systemColor name="systemBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
1. Pull latest https://github.com/x2on/OpenSSL-for-iPhone
|
||||
2. Patch build-libssl.sh to adjust IOS_MIN_SDK_VERSION and TVOS_MIN_SDK_VERSION
|
||||
3. ./build-libssl.sh --disable-bitcode --targets="ios-sim-cross-x86_64 ios-sim-cross-arm64 ios-cross-arm64 tvos-sim-cross-x86_64 tvos-sim-cross-arm64 tvos-cross-arm64"
|
||||
4. ./create-xcframework.sh
|
||||
5. Copy OpenSSL.xcframework into moonlight-ios/libs
|
||||
@@ -0,0 +1 @@
|
||||
Static libraries were built from https://github.com/cgutman/moonlight-mobile-deps using AppVeyor CI
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* AC-3 parser prototypes
|
||||
* Copyright (c) 2003 Fabrice Bellard
|
||||
* Copyright (c) 2003 Michael Niedermayer
|
||||
*
|
||||
* This file is part of FFmpeg.
|
||||
*
|
||||
* FFmpeg is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* FFmpeg is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with FFmpeg; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef AVCODEC_AC3_PARSER_H
|
||||
#define AVCODEC_AC3_PARSER_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* Extract the bitstream ID and the frame size from AC-3 data.
|
||||
*/
|
||||
int av_ac3_parse_header(const uint8_t *buf, size_t size,
|
||||
uint8_t *bitstream_id, uint16_t *frame_size);
|
||||
|
||||
|
||||
#endif /* AVCODEC_AC3_PARSER_H */
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* This file is part of FFmpeg.
|
||||
*
|
||||
* FFmpeg is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* FFmpeg is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with FFmpeg; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef AVCODEC_ADTS_PARSER_H
|
||||
#define AVCODEC_ADTS_PARSER_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define AV_AAC_ADTS_HEADER_SIZE 7
|
||||
|
||||
/**
|
||||
* Extract the number of samples and frames from AAC data.
|
||||
* @param[in] buf pointer to AAC data buffer
|
||||
* @param[out] samples Pointer to where number of samples is written
|
||||
* @param[out] frames Pointer to where number of frames is written
|
||||
* @return Returns 0 on success, error code on failure.
|
||||
*/
|
||||
int av_adts_header_parse(const uint8_t *buf, uint32_t *samples,
|
||||
uint8_t *frames);
|
||||
|
||||
#endif /* AVCODEC_ADTS_PARSER_H */
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* This file is part of FFmpeg.
|
||||
*
|
||||
* FFmpeg is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* FFmpeg is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with FFmpeg; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* AV1 common definitions
|
||||
*/
|
||||
|
||||
#ifndef AVCODEC_AV1_H
|
||||
#define AVCODEC_AV1_H
|
||||
|
||||
// OBU types (section 6.2.2).
|
||||
typedef enum {
|
||||
// 0 reserved.
|
||||
AV1_OBU_SEQUENCE_HEADER = 1,
|
||||
AV1_OBU_TEMPORAL_DELIMITER = 2,
|
||||
AV1_OBU_FRAME_HEADER = 3,
|
||||
AV1_OBU_TILE_GROUP = 4,
|
||||
AV1_OBU_METADATA = 5,
|
||||
AV1_OBU_FRAME = 6,
|
||||
AV1_OBU_REDUNDANT_FRAME_HEADER = 7,
|
||||
AV1_OBU_TILE_LIST = 8,
|
||||
// 9-14 reserved.
|
||||
AV1_OBU_PADDING = 15,
|
||||
} AV1_OBU_Type;
|
||||
|
||||
// Metadata types (section 6.7.1).
|
||||
enum {
|
||||
AV1_METADATA_TYPE_HDR_CLL = 1,
|
||||
AV1_METADATA_TYPE_HDR_MDCV = 2,
|
||||
AV1_METADATA_TYPE_SCALABILITY = 3,
|
||||
AV1_METADATA_TYPE_ITUT_T35 = 4,
|
||||
AV1_METADATA_TYPE_TIMECODE = 5,
|
||||
};
|
||||
|
||||
// Frame types (section 6.8.2).
|
||||
enum {
|
||||
AV1_FRAME_KEY = 0,
|
||||
AV1_FRAME_INTER = 1,
|
||||
AV1_FRAME_INTRA_ONLY = 2,
|
||||
AV1_FRAME_SWITCH = 3,
|
||||
};
|
||||
|
||||
// Reference frames (section 6.10.24).
|
||||
enum {
|
||||
AV1_REF_FRAME_INTRA = 0,
|
||||
AV1_REF_FRAME_LAST = 1,
|
||||
AV1_REF_FRAME_LAST2 = 2,
|
||||
AV1_REF_FRAME_LAST3 = 3,
|
||||
AV1_REF_FRAME_GOLDEN = 4,
|
||||
AV1_REF_FRAME_BWDREF = 5,
|
||||
AV1_REF_FRAME_ALTREF2 = 6,
|
||||
AV1_REF_FRAME_ALTREF = 7,
|
||||
};
|
||||
|
||||
// Constants (section 3).
|
||||
enum {
|
||||
AV1_MAX_OPERATING_POINTS = 32,
|
||||
|
||||
AV1_MAX_SB_SIZE = 128,
|
||||
AV1_MI_SIZE = 4,
|
||||
|
||||
AV1_MAX_TILE_WIDTH = 4096,
|
||||
AV1_MAX_TILE_AREA = 4096 * 2304,
|
||||
AV1_MAX_TILE_ROWS = 64,
|
||||
AV1_MAX_TILE_COLS = 64,
|
||||
|
||||
AV1_NUM_REF_FRAMES = 8,
|
||||
AV1_REFS_PER_FRAME = 7,
|
||||
AV1_TOTAL_REFS_PER_FRAME = 8,
|
||||
AV1_PRIMARY_REF_NONE = 7,
|
||||
|
||||
AV1_MAX_SEGMENTS = 8,
|
||||
AV1_SEG_LVL_MAX = 8,
|
||||
|
||||
AV1_SEG_LVL_ALT_Q = 0,
|
||||
AV1_SEG_LVL_ALT_LF_Y_V = 1,
|
||||
AV1_SEG_LVL_REF_FRAME = 5,
|
||||
AV1_SEG_LVL_SKIP = 6,
|
||||
AV1_SEG_LVL_GLOBAL_MV = 7,
|
||||
|
||||
AV1_SELECT_SCREEN_CONTENT_TOOLS = 2,
|
||||
AV1_SELECT_INTEGER_MV = 2,
|
||||
|
||||
AV1_SUPERRES_NUM = 8,
|
||||
AV1_SUPERRES_DENOM_MIN = 9,
|
||||
|
||||
AV1_INTERPOLATION_FILTER_SWITCHABLE = 4,
|
||||
|
||||
AV1_GM_ABS_ALPHA_BITS = 12,
|
||||
AV1_GM_ALPHA_PREC_BITS = 15,
|
||||
AV1_GM_ABS_TRANS_ONLY_BITS = 9,
|
||||
AV1_GM_TRANS_ONLY_PREC_BITS = 3,
|
||||
AV1_GM_ABS_TRANS_BITS = 12,
|
||||
AV1_GM_TRANS_PREC_BITS = 6,
|
||||
AV1_WARPEDMODEL_PREC_BITS = 16,
|
||||
|
||||
AV1_WARP_MODEL_IDENTITY = 0,
|
||||
AV1_WARP_MODEL_TRANSLATION = 1,
|
||||
AV1_WARP_MODEL_ROTZOOM = 2,
|
||||
AV1_WARP_MODEL_AFFINE = 3,
|
||||
AV1_WARP_PARAM_REDUCE_BITS = 6,
|
||||
|
||||
AV1_DIV_LUT_BITS = 8,
|
||||
AV1_DIV_LUT_PREC_BITS = 14,
|
||||
AV1_DIV_LUT_NUM = 257,
|
||||
|
||||
AV1_MAX_LOOP_FILTER = 63,
|
||||
};
|
||||
|
||||
|
||||
// The main colour configuration information uses the same ISO/IEC 23001-8
|
||||
// (H.273) enums as FFmpeg does, so separate definitions are not required.
|
||||
|
||||
// Chroma sample position.
|
||||
enum {
|
||||
AV1_CSP_UNKNOWN = 0,
|
||||
AV1_CSP_VERTICAL = 1, // -> AVCHROMA_LOC_LEFT.
|
||||
AV1_CSP_COLOCATED = 2, // -> AVCHROMA_LOC_TOPLEFT.
|
||||
};
|
||||
|
||||
// Scalability modes (section 6.7.5)
|
||||
enum {
|
||||
AV1_SCALABILITY_L1T2 = 0,
|
||||
AV1_SCALABILITY_L1T3 = 1,
|
||||
AV1_SCALABILITY_L2T1 = 2,
|
||||
AV1_SCALABILITY_L2T2 = 3,
|
||||
AV1_SCALABILITY_L2T3 = 4,
|
||||
AV1_SCALABILITY_S2T1 = 5,
|
||||
AV1_SCALABILITY_S2T2 = 6,
|
||||
AV1_SCALABILITY_S2T3 = 7,
|
||||
AV1_SCALABILITY_L2T1h = 8,
|
||||
AV1_SCALABILITY_L2T2h = 9,
|
||||
AV1_SCALABILITY_L2T3h = 10,
|
||||
AV1_SCALABILITY_S2T1h = 11,
|
||||
AV1_SCALABILITY_S2T2h = 12,
|
||||
AV1_SCALABILITY_S2T3h = 13,
|
||||
AV1_SCALABILITY_SS = 14,
|
||||
AV1_SCALABILITY_L3T1 = 15,
|
||||
AV1_SCALABILITY_L3T2 = 16,
|
||||
AV1_SCALABILITY_L3T3 = 17,
|
||||
AV1_SCALABILITY_S3T1 = 18,
|
||||
AV1_SCALABILITY_S3T2 = 19,
|
||||
AV1_SCALABILITY_S3T3 = 20,
|
||||
AV1_SCALABILITY_L3T2_KEY = 21,
|
||||
AV1_SCALABILITY_L3T3_KEY = 22,
|
||||
AV1_SCALABILITY_L4T5_KEY = 23,
|
||||
AV1_SCALABILITY_L4T7_KEY = 24,
|
||||
AV1_SCALABILITY_L3T2_KEY_SHIFT = 25,
|
||||
AV1_SCALABILITY_L3T3_KEY_SHIFT = 26,
|
||||
AV1_SCALABILITY_L4T5_KEY_SHIFT = 27,
|
||||
AV1_SCALABILITY_L4T7_KEY_SHIFT = 28,
|
||||
};
|
||||
|
||||
// Frame Restoration types (section 6.10.15)
|
||||
enum {
|
||||
AV1_RESTORE_NONE = 0,
|
||||
AV1_RESTORE_WIENER = 1,
|
||||
AV1_RESTORE_SGRPROJ = 2,
|
||||
AV1_RESTORE_SWITCHABLE = 3,
|
||||
};
|
||||
|
||||
// TX mode (section 6.8.21)
|
||||
enum {
|
||||
AV1_ONLY_4X4 = 0,
|
||||
AV1_TX_MODE_LARGEST = 1,
|
||||
AV1_TX_MODE_SELECT = 2,
|
||||
};
|
||||
|
||||
// Sequence Headers are actually unbounded because one can use
|
||||
// an arbitrary number of leading zeroes when encoding via uvlc.
|
||||
// The following estimate is based around using the lowest number
|
||||
// of bits for uvlc encoding.
|
||||
#define AV1_SANE_SEQUENCE_HEADER_MAX_BITS 3138
|
||||
|
||||
#endif /* AVCODEC_AV1_H */
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* This file is part of FFmpeg.
|
||||
*
|
||||
* FFmpeg is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* FFmpeg is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with FFmpeg; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef AVCODEC_AVDCT_H
|
||||
#define AVCODEC_AVDCT_H
|
||||
|
||||
#include "libavutil/opt.h"
|
||||
|
||||
/**
|
||||
* AVDCT context.
|
||||
* @note function pointers can be NULL if the specific features have been
|
||||
* disabled at build time.
|
||||
*/
|
||||
typedef struct AVDCT {
|
||||
const AVClass *av_class;
|
||||
|
||||
void (*idct)(int16_t *block /* align 16 */);
|
||||
|
||||
/**
|
||||
* IDCT input permutation.
|
||||
* Several optimized IDCTs need a permutated input (relative to the
|
||||
* normal order of the reference IDCT).
|
||||
* This permutation must be performed before the idct_put/add.
|
||||
* Note, normally this can be merged with the zigzag/alternate scan<br>
|
||||
* An example to avoid confusion:
|
||||
* - (->decode coeffs -> zigzag reorder -> dequant -> reference IDCT -> ...)
|
||||
* - (x -> reference DCT -> reference IDCT -> x)
|
||||
* - (x -> reference DCT -> simple_mmx_perm = idct_permutation
|
||||
* -> simple_idct_mmx -> x)
|
||||
* - (-> decode coeffs -> zigzag reorder -> simple_mmx_perm -> dequant
|
||||
* -> simple_idct_mmx -> ...)
|
||||
*/
|
||||
uint8_t idct_permutation[64];
|
||||
|
||||
void (*fdct)(int16_t *block /* align 16 */);
|
||||
|
||||
|
||||
/**
|
||||
* DCT algorithm.
|
||||
* must use AVOptions to set this field.
|
||||
*/
|
||||
int dct_algo;
|
||||
|
||||
/**
|
||||
* IDCT algorithm.
|
||||
* must use AVOptions to set this field.
|
||||
*/
|
||||
int idct_algo;
|
||||
|
||||
void (*get_pixels)(int16_t *block /* align 16 */,
|
||||
const uint8_t *pixels /* align 8 */,
|
||||
ptrdiff_t line_size);
|
||||
|
||||
int bits_per_sample;
|
||||
|
||||
void (*get_pixels_unaligned)(int16_t *block /* align 16 */,
|
||||
const uint8_t *pixels,
|
||||
ptrdiff_t line_size);
|
||||
} AVDCT;
|
||||
|
||||
/**
|
||||
* Allocates a AVDCT context.
|
||||
* This needs to be initialized with avcodec_dct_init() after optionally
|
||||
* configuring it with AVOptions.
|
||||
*
|
||||
* To free it use av_free()
|
||||
*/
|
||||
AVDCT *avcodec_dct_alloc(void);
|
||||
int avcodec_dct_init(AVDCT *);
|
||||
|
||||
const AVClass *avcodec_dct_get_class(void);
|
||||
|
||||
#endif /* AVCODEC_AVDCT_H */
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* This file is part of FFmpeg.
|
||||
*
|
||||
* FFmpeg is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* FFmpeg is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with FFmpeg; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef AVCODEC_AVFFT_H
|
||||
#define AVCODEC_AVFFT_H
|
||||
|
||||
#include "libavutil/attributes.h"
|
||||
#include "version_major.h"
|
||||
#if FF_API_AVFFT
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @ingroup lavc_fft
|
||||
* FFT functions
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup lavc_fft FFT functions
|
||||
* @ingroup lavc_misc
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
typedef float FFTSample;
|
||||
|
||||
typedef struct FFTComplex {
|
||||
FFTSample re, im;
|
||||
} FFTComplex;
|
||||
|
||||
typedef struct FFTContext FFTContext;
|
||||
|
||||
/**
|
||||
* Set up a complex FFT.
|
||||
* @param nbits log2 of the length of the input array
|
||||
* @param inverse if 0 perform the forward transform, if 1 perform the inverse
|
||||
* @deprecated use av_tx_init from libavutil/tx.h with a type of AV_TX_FLOAT_FFT
|
||||
*/
|
||||
attribute_deprecated
|
||||
FFTContext *av_fft_init(int nbits, int inverse);
|
||||
|
||||
/**
|
||||
* Do the permutation needed BEFORE calling ff_fft_calc().
|
||||
* @deprecated without replacement
|
||||
*/
|
||||
attribute_deprecated
|
||||
void av_fft_permute(FFTContext *s, FFTComplex *z);
|
||||
|
||||
/**
|
||||
* Do a complex FFT with the parameters defined in av_fft_init(). The
|
||||
* input data must be permuted before. No 1.0/sqrt(n) normalization is done.
|
||||
* @deprecated use the av_tx_fn value returned by av_tx_init, which also does permutation
|
||||
*/
|
||||
attribute_deprecated
|
||||
void av_fft_calc(FFTContext *s, FFTComplex *z);
|
||||
|
||||
attribute_deprecated
|
||||
void av_fft_end(FFTContext *s);
|
||||
|
||||
/**
|
||||
* @deprecated use av_tx_init from libavutil/tx.h with a type of AV_TX_FLOAT_MDCT,
|
||||
* with a flag of AV_TX_FULL_IMDCT for a replacement to av_imdct_calc.
|
||||
*/
|
||||
attribute_deprecated
|
||||
FFTContext *av_mdct_init(int nbits, int inverse, double scale);
|
||||
attribute_deprecated
|
||||
void av_imdct_calc(FFTContext *s, FFTSample *output, const FFTSample *input);
|
||||
attribute_deprecated
|
||||
void av_imdct_half(FFTContext *s, FFTSample *output, const FFTSample *input);
|
||||
attribute_deprecated
|
||||
void av_mdct_calc(FFTContext *s, FFTSample *output, const FFTSample *input);
|
||||
attribute_deprecated
|
||||
void av_mdct_end(FFTContext *s);
|
||||
|
||||
/* Real Discrete Fourier Transform */
|
||||
|
||||
enum RDFTransformType {
|
||||
DFT_R2C,
|
||||
IDFT_C2R,
|
||||
IDFT_R2C,
|
||||
DFT_C2R,
|
||||
};
|
||||
|
||||
typedef struct RDFTContext RDFTContext;
|
||||
|
||||
/**
|
||||
* Set up a real FFT.
|
||||
* @param nbits log2 of the length of the input array
|
||||
* @param trans the type of transform
|
||||
*
|
||||
* @deprecated use av_tx_init from libavutil/tx.h with a type of AV_TX_FLOAT_RDFT
|
||||
*/
|
||||
attribute_deprecated
|
||||
RDFTContext *av_rdft_init(int nbits, enum RDFTransformType trans);
|
||||
attribute_deprecated
|
||||
void av_rdft_calc(RDFTContext *s, FFTSample *data);
|
||||
attribute_deprecated
|
||||
void av_rdft_end(RDFTContext *s);
|
||||
|
||||
/* Discrete Cosine Transform */
|
||||
|
||||
typedef struct DCTContext DCTContext;
|
||||
|
||||
enum DCTTransformType {
|
||||
DCT_II = 0,
|
||||
DCT_III,
|
||||
DCT_I,
|
||||
DST_I,
|
||||
};
|
||||
|
||||
/**
|
||||
* Set up DCT.
|
||||
*
|
||||
* @param nbits size of the input array:
|
||||
* (1 << nbits) for DCT-II, DCT-III and DST-I
|
||||
* (1 << nbits) + 1 for DCT-I
|
||||
* @param type the type of transform
|
||||
*
|
||||
* @note the first element of the input of DST-I is ignored
|
||||
*
|
||||
* @deprecated use av_tx_init from libavutil/tx.h with an appropriate type of AV_TX_FLOAT_DCT
|
||||
*/
|
||||
attribute_deprecated
|
||||
DCTContext *av_dct_init(int nbits, enum DCTTransformType type);
|
||||
attribute_deprecated
|
||||
void av_dct_calc(DCTContext *s, FFTSample *data);
|
||||
attribute_deprecated
|
||||
void av_dct_end (DCTContext *s);
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
#endif /* FF_API_AVFFT */
|
||||
#endif /* AVCODEC_AVFFT_H */
|
||||