Compare commits
446 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aa41bf8d97 | |||
| fba9a125bf | |||
| 27312bd146 | |||
| 8f0c267ab8 | |||
| a15d6a6b42 | |||
| 8f9061b250 | |||
| ec57499e08 | |||
| 381598c5b6 | |||
| 452d020da5 | |||
| b5f875c2e5 | |||
| a31daeda96 | |||
| 437f52f53a | |||
| 33f0f7ecf0 | |||
| 6777e79e70 | |||
| 16d1e6181b | |||
| a6c8db6c2c | |||
| 24aa0fecbe | |||
| 1a776b1990 | |||
| 27df265c81 | |||
| 84c0372719 | |||
| 3879e57c4c | |||
| dcc3dcdaba | |||
| d166635c7b | |||
| 33d484b7d1 | |||
| 26bff28e4d | |||
| 56eddff8d6 | |||
| 37b9133eb6 | |||
| 4a64967b1f | |||
| 23152b1264 | |||
| 00415aac79 | |||
| cbe602655c | |||
| 236d8b7030 | |||
| 392e3c7fe3 | |||
| 57f55e6856 | |||
| de54b27013 | |||
| c11338039f | |||
| e712669d32 | |||
| 3768ae33b7 | |||
| fdc39f0041 | |||
| 7f3b0b03a6 | |||
| 4a6a39dd4c | |||
| 6a8486a076 | |||
| 08a8a3043f | |||
| 7af290b6e1 | |||
| a896f9a28f | |||
| ea003483c4 | |||
| 5b73317e30 | |||
| 1af64b9985 | |||
| af784cf79b | |||
| a2b2131beb | |||
| 2433ce8d24 | |||
| 8b861750e5 | |||
| 99fcd3c669 | |||
| 0ddd8df272 | |||
| a96e508ffb | |||
| 1f21d12d2b | |||
| dd782ac4b2 | |||
| 51594e00b8 | |||
| 6c85f5f8c3 | |||
| d0432de981 | |||
| 2cbc94e51d | |||
| 3ea2aa1f74 | |||
| 1076b516d6 | |||
| 4e87d25851 | |||
| dadd3c7292 | |||
| 9f8abe35f9 | |||
| 0f869a7414 | |||
| aede16c85c | |||
| 61a82e6394 | |||
| 5a92925d6a | |||
| fe697c918f | |||
| bc57a285ce | |||
| 85d8943b64 | |||
| aa6c32968b | |||
| ad1808fb4e | |||
| 576610e4c3 | |||
| ace2266f14 | |||
| 41cedfa6ec | |||
| d46fab33b3 | |||
| 585dc45595 | |||
| c3c9354a00 | |||
| bdc8d08e65 | |||
| 9c792d3272 | |||
| 23bc4daf9f | |||
| fd85ca2004 | |||
| aadf88add1 | |||
| f14ce61ee3 | |||
| 539daf5789 | |||
| e8ea2a8ec1 | |||
| 9ed3b3a9df | |||
| 12487553de | |||
| 9c1a618b4a | |||
| ac0e784417 | |||
| 48cab6b203 | |||
| e1c0472069 | |||
| 2c498ce707 | |||
| bc483edb29 | |||
| 9762f4c412 | |||
| 5bfce88fc5 | |||
| 94ef66994d | |||
| 257c29daca | |||
| 173483eb84 | |||
| 06099b2663 | |||
| 33c1f0a71c | |||
| a3d78f1d80 | |||
| c573d213f8 | |||
| c72707aef9 | |||
| 313ef06c86 | |||
| 6b79340c15 | |||
| d9a5b29372 | |||
| d2b0e093fc | |||
| 945e563912 | |||
| a7efa379eb | |||
| d04df4ebe5 | |||
| 2a2c84ef3a | |||
| bc97db893a | |||
| f216834df7 | |||
| be25a7d594 | |||
| 10f43e8024 | |||
| bbb3e8d071 | |||
| 4c3af35156 | |||
| 8656228014 | |||
| 03f9ea8435 | |||
| 9cf27d8fb1 | |||
| d1b24ea6af | |||
| b07ffbde29 | |||
| 1673236940 | |||
| 06861a2d17 | |||
| ef7ac62f97 | |||
| 245a9f2751 | |||
| 1d38f158b5 | |||
| 62a526854d | |||
| 3dda940c92 | |||
| ab77c4720d | |||
| c8f1f9325e | |||
| 658940d3fb | |||
| 51b4ca401e | |||
| 10e4ca4ef3 | |||
| 2bcc2bdfe5 | |||
| 6462b580bb | |||
| b83d91c944 | |||
| 07f842bc9e | |||
| 3913e845fa | |||
| 09f0913974 | |||
| aa9ca35115 | |||
| 010dfdf834 | |||
| 150fac9c09 | |||
| ec3aef13d8 | |||
| 2b56005bd2 | |||
| 9bc893b6ad | |||
| 3feb92e788 | |||
| 1265952814 | |||
| f5ad5d97db | |||
| 5ac0939731 | |||
| b653694860 | |||
| 49051a5095 | |||
| 7734de6465 | |||
| edac646434 | |||
| 51c7665fdc | |||
| 37545821fc | |||
| 8a1ed0f146 | |||
| 7a0228fb81 | |||
| 3aecf9e031 | |||
| c2d4d221af | |||
| 53f89fbe22 | |||
| eb5f7ef7af | |||
| e2fc76d21d | |||
| 1754103175 | |||
| dacd00708f | |||
| a73129243c | |||
| 54a6aa9081 | |||
| 0fbb53c606 | |||
| eb2e79977d | |||
| b70a47f5e5 | |||
| 9d5ff72548 | |||
| 6b972b56a5 | |||
| a80d30baf7 | |||
| b9280e9a8e | |||
| c6640d201c | |||
| 795fdc3605 | |||
| 05311da33d | |||
| 2e442cb1d1 | |||
| fe322590cc | |||
| 6cf9b25c04 | |||
| 417babb3d4 | |||
| bdaaa6f0c7 | |||
| 7e92dd7fe4 | |||
| 50601e24ed | |||
| bfc3116661 | |||
| 264b6e54f2 | |||
| 1ed7ecc82f | |||
| 19b8032d06 | |||
| 1e254ea8f4 | |||
| babfc99c35 | |||
| 5c802555a2 | |||
| fbc41c9a4e | |||
| 1eca461cb1 | |||
| ebd327c7a6 | |||
| 602febe876 | |||
| 84fcd3ae6a | |||
| 84296c6e1c | |||
| 6012e0ea8c | |||
| 9c76defad0 | |||
| ffd6fab35c | |||
| 296f97f7ca | |||
| 9cbef34f29 | |||
| 7a65136d29 | |||
| acaebea846 | |||
| ce850ac12f | |||
| a93422d3ed | |||
| b2e605838e | |||
| 2e14002442 | |||
| c743949df5 | |||
| f207a3f6d1 | |||
| d6211605a1 | |||
| b16676b54a | |||
| dc9bfe5189 | |||
| 80620ed4c6 | |||
| 76bd0ab696 | |||
| e0914df58a | |||
| 20039a422e | |||
| 22b9c9ca68 | |||
| 0c546e35ec | |||
| b70370ac09 | |||
| aa10bb7dc5 | |||
| c6100a9be1 | |||
| 529a2f7bf8 | |||
| 9ec7e916c5 | |||
| 982b36cf98 | |||
| a73eab5e92 | |||
| a8479ccb5f | |||
| f55e4e0e01 | |||
| d08c32ce04 | |||
| 1d599c5e60 | |||
| e888ae59e4 | |||
| 951d544894 | |||
| 49898b34e1 | |||
| 3854a6a42e | |||
| d4da5bc281 | |||
| 04954f5242 | |||
| 9fc5496526 | |||
| e363d24b1c | |||
| 801f4027a2 | |||
| c0dc344f76 | |||
| b5b3d81f00 | |||
| 8dd8dbc1d1 | |||
| 8f31aa59a8 | |||
| 5b581b6c0f | |||
| 297ac64fde | |||
| d4490f0e17 | |||
| d04e7a3231 | |||
| 5b456aba27 | |||
| 0c065dcc1f | |||
| 531f73329d | |||
| d6ba72032d | |||
| bfdc7a2609 | |||
| 031abf03da | |||
| 6aac8e6be6 | |||
| 8ff93d21c3 | |||
| 6df3d0bc44 | |||
| 0b18e8fdb4 | |||
| 19d8ae0f78 | |||
| d7ffb5dddc | |||
| 2859b73dfe | |||
| 6f9021a5e6 | |||
| 3bfeaefdbd | |||
| db1eace975 | |||
| cab0fa176e | |||
| 2f9ae107a2 | |||
| 18c93abcb3 | |||
| bd64dfb661 | |||
| 82619063ee | |||
| 5dbf18d66e | |||
| 6a34ff2728 | |||
| f7c7487756 | |||
| f966cb4ca0 | |||
| 549563a3d2 | |||
| c5f2a3f8fe | |||
| 81a3bbd5e8 | |||
| 1509a2a799 | |||
| fc547b734f | |||
| b3700b5a19 | |||
| 2b29682095 | |||
| 286094ee33 | |||
| c7a061d24e | |||
| 4bdc2e0aba | |||
| e69061082b | |||
| 1da2ec3cb1 | |||
| 8ffc3b80b2 | |||
| 08f8b6cb8e | |||
| fb09c9692c | |||
| 4901b0c78f | |||
| 0a2117241f | |||
| f352cfd15b | |||
| ac7c5c1064 | |||
| 077cb2103d | |||
| cdeda011a4 | |||
| 894c146988 | |||
| 61cc9e151f | |||
| cfe4c9ff21 | |||
| d4bd29b320 | |||
| 7f2f2056c3 | |||
| 4dd3b2cfb7 | |||
| 2e62ad0f00 | |||
| 41ef292b82 | |||
| aa60671c88 | |||
| f1ccba39e8 | |||
| 226e580a30 | |||
| 6f8e719200 | |||
| c127af1e05 | |||
| 648904cc69 | |||
| dc85ddb3f9 | |||
| 23a7d8555f | |||
| bc9e250d34 | |||
| 2203186527 | |||
| 53d3d9ecb8 | |||
| de549f67a1 | |||
| 755c41481a | |||
| aebc2126bc | |||
| f43547fb31 | |||
| 398e4df7cf | |||
| ff68efc3f5 | |||
| 8ba2f51bda | |||
| 87b79b278b | |||
| 121e3ea9be | |||
| ec6ed79ee1 | |||
| ca125826a7 | |||
| dd0aecf108 | |||
| ef5cb2f0cd | |||
| e5a7bb40e9 | |||
| bfdda48fee | |||
| ebea1bb5c1 | |||
| 14bc1552fc | |||
| a5b80d3944 | |||
| 75d0eedc2b | |||
| 29ac7028fa | |||
| 8a63b61495 | |||
| eb9e6443e2 | |||
| 362c466a16 | |||
| 5dac42646b | |||
| c25faf6426 | |||
| 81df1245b4 | |||
| 2bf4d92185 | |||
| ae6073fe80 | |||
| d0463da2a1 | |||
| c0f8001627 | |||
| f39bf61b04 | |||
| 9c8237dab0 | |||
| b88251fa79 | |||
| 208855917e | |||
| 34bdf450e9 | |||
| 998fa1f4e9 | |||
| 5c80f7d58c | |||
| 7552181e24 | |||
| 4b2e26050e | |||
| 530b48de71 | |||
| f4721901f8 | |||
| 8b692269c1 | |||
| 079eca7b4d | |||
| fee40cdbe2 | |||
| 66920bb4cb | |||
| fdbf810aa2 | |||
| 08bfc1de4a | |||
| 76149328fe | |||
| 285f33f3f1 | |||
| b17c1b7588 | |||
| 5b25c90db8 | |||
| 931a0a5168 | |||
| f6a46438bd | |||
| 4a60ec1755 | |||
| ec222413dd | |||
| 5a28239813 | |||
| da45cba2ff | |||
| 54bc34496a | |||
| 294910ac84 | |||
| 71d2c6a5d5 | |||
| 79bf17fe24 | |||
| 31f66031bc | |||
| d3f2284791 | |||
| ec647608c4 | |||
| 597582ddd8 | |||
| c6d9889182 | |||
| 7c58234174 | |||
| ae9282b0af | |||
| 310ba646fc | |||
| d479908939 | |||
| 5cd5d68d22 | |||
| 3e0bf25acb | |||
| f3d277c94a | |||
| 04545ecbb0 | |||
| 5350651d6f | |||
| f2e2e28419 | |||
| b9031785ac | |||
| 91a72474a1 | |||
| b6e7c425c6 | |||
| 834ace4566 | |||
| 54af70005d | |||
| f2bf168925 | |||
| 27ffbd8dec | |||
| eaa82592fe | |||
| 73784585a8 | |||
| 262d562dd9 | |||
| ab4f904dc9 | |||
| fc4fdd5ee2 | |||
| 41c5b62b1a | |||
| 239cb0435c | |||
| c6ccc7a6e2 | |||
| 6cedb9019c | |||
| 8bc64f0438 | |||
| 89e6e39e58 | |||
| 645761f677 | |||
| 0fc60f7855 | |||
| ce38460d87 | |||
| de8e759d3a | |||
| 06f6134538 | |||
| ac352b3a23 | |||
| 9b8e65e552 | |||
| 35999a05f0 | |||
| 86ee30e9b4 | |||
| a81c4a1e23 | |||
| 394ce458a0 | |||
| f187e57899 | |||
| a15335872d | |||
| beb77b4dab | |||
| aa80d8cd0a | |||
| 77d197f14e | |||
| f98fbb778c | |||
| c46a0106f2 | |||
| cbf3db0be0 | |||
| 21f3710083 | |||
| 8ac5768f4f | |||
| 2458b9305c | |||
| a8909ea2a5 | |||
| ac7c35c6c2 | |||
| e4631b5a85 | |||
| e1c50b5dc5 | |||
| c6c5a5cd12 | |||
| bd4854a607 | |||
| cd0181e6f4 | |||
| 287b1d2b4d | |||
| 10c61bb0a7 | |||
| 92215ac34f | |||
| f64d50d8c8 | |||
| b74e0ce48f | |||
| 27cb0029a8 | |||
| ce6f193f06 |
@@ -3,7 +3,7 @@
|
||||
[](https://ci.appveyor.com/project/cgutman/moonlight-android/branch/master)
|
||||
[](https://hosted.weblate.org/projects/moonlight/moonlight-android/)
|
||||
|
||||
[Moonlight for Android](https://moonlight-stream.org) is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield.
|
||||
[Moonlight for Android](https://moonlight-stream.org) is an open source client for NVIDIA GameStream, as used by the NVIDIA Shield.
|
||||
|
||||
Moonlight for Android will allow you to stream your full collection of games from your Windows PC to your Android device,
|
||||
whether in your own home or over the internet.
|
||||
|
||||
+32
-18
@@ -1,25 +1,28 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
ndkVersion "22.1.7171670"
|
||||
ndkVersion "23.2.8568313"
|
||||
|
||||
compileSdkVersion 30
|
||||
compileSdk 33
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 30
|
||||
minSdk 16
|
||||
targetSdk 33
|
||||
|
||||
versionName "9.9.3"
|
||||
versionCode = 264
|
||||
versionName "10.11"
|
||||
versionCode = 301
|
||||
|
||||
// Generate native debug symbols to allow Google Play to symbolicate our native crashes
|
||||
ndk.debugSymbolLevel = 'FULL'
|
||||
}
|
||||
|
||||
flavorDimensions "root"
|
||||
flavorDimensions.add("root")
|
||||
|
||||
productFlavors {
|
||||
root {
|
||||
// Android O has native mouse capture, so don't show the rooted
|
||||
// version to devices running O on the Play Store.
|
||||
maxSdkVersion 25
|
||||
maxSdk 25
|
||||
|
||||
externalNativeBuild {
|
||||
ndkBuild {
|
||||
@@ -29,6 +32,7 @@ android {
|
||||
|
||||
applicationId "com.limelight.root"
|
||||
dimension "root"
|
||||
buildConfigField "boolean", "ROOT_BUILD", "true"
|
||||
}
|
||||
|
||||
nonRoot {
|
||||
@@ -40,12 +44,19 @@ android {
|
||||
|
||||
applicationId "com.limelight"
|
||||
dimension "root"
|
||||
buildConfigField "boolean", "ROOT_BUILD", "false"
|
||||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
compileOptions {
|
||||
encoding "UTF-8"
|
||||
sourceCompatibility JavaVersion.VERSION_11
|
||||
targetCompatibility JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
lint {
|
||||
disable 'MissingTranslation'
|
||||
lintConfig file("lint.xml")
|
||||
lintConfig file('lint.xml')
|
||||
}
|
||||
|
||||
bundle {
|
||||
@@ -55,7 +66,7 @@ android {
|
||||
enableSplit = false
|
||||
}
|
||||
density {
|
||||
// FIXME: This should not be neccessary but we get
|
||||
// FIXME: This should not be necessary but we get
|
||||
// weird crashes due to missing drawable resources
|
||||
// when this split is enabled.
|
||||
enableSplit = false
|
||||
@@ -65,6 +76,8 @@ android {
|
||||
buildTypes {
|
||||
debug {
|
||||
applicationIdSuffix ".debug"
|
||||
resValue "string", "app_label", "Moonlight (Debug)"
|
||||
resValue "string", "app_label_root", "Moonlight (Root Debug)"
|
||||
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
@@ -101,6 +114,8 @@ android {
|
||||
//
|
||||
// TL;DR: Leave the following line alone!
|
||||
applicationIdSuffix ".unofficial"
|
||||
resValue "string", "app_label", "Moonlight"
|
||||
resValue "string", "app_label_root", "Moonlight (Root)"
|
||||
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
@@ -112,16 +127,15 @@ android {
|
||||
path "src/main/jni/Android.mk"
|
||||
}
|
||||
}
|
||||
|
||||
// Generate native debug symbols to allow Google Play to symbolicate our native crashes
|
||||
android.defaultConfig.ndk.debugSymbolLevel = 'FULL'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.bouncycastle:bcprov-jdk15on:1.66'
|
||||
implementation 'org.bouncycastle:bcpkix-jdk15on:1.66'
|
||||
implementation 'org.bouncycastle:bcprov-jdk15on:1.70'
|
||||
implementation 'org.bouncycastle:bcpkix-jdk15on:1.70'
|
||||
implementation 'org.jcodec:jcodec:0.2.3'
|
||||
implementation 'com.squareup.okhttp3:okhttp:3.12.12'
|
||||
implementation 'com.squareup.okhttp3:okhttp:3.12.13'
|
||||
implementation 'com.squareup.okio:okio:1.17.5'
|
||||
implementation 'org.jmdns:jmdns:3.5.5'
|
||||
// 3.5.8 requires minSdk 19, uses StandardCharsets.UTF_8 internally
|
||||
implementation 'org.jmdns:jmdns:3.5.7'
|
||||
implementation 'com.github.cgutman:ShieldControllerExtensions:1.0'
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="app_label" translatable="false">Moonlight (Debug)</string>
|
||||
<string name="app_label_root" translatable="false">Moonlight (Root Debug)</string>
|
||||
|
||||
</resources>
|
||||
@@ -35,6 +35,7 @@
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:dataExtractionRules="@xml/backup_rules_s"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:isGame="true"
|
||||
android:banner="@drawable/atv_banner"
|
||||
@@ -43,22 +44,31 @@
|
||||
android:roundIcon="@mipmap/ic_launcher"
|
||||
android:installLocation="auto"
|
||||
android:gwpAsanMode="always"
|
||||
android:localeConfig="@xml/locales_config"
|
||||
android:enableOnBackInvokedCallback="false"
|
||||
android:theme="@style/AppTheme">
|
||||
|
||||
<provider
|
||||
android:name=".PosterContentProvider"
|
||||
android:authorities="poster.${applicationId}"
|
||||
android:enabled="true"
|
||||
android:exported="true">
|
||||
</provider>
|
||||
|
||||
<!-- Samsung multi-window support -->
|
||||
<uses-library
|
||||
android:name="com.sec.android.app.multiwindow"
|
||||
android:required="false" />
|
||||
|
||||
<meta-data
|
||||
android:name="com.sec.android.support.multiwindow"
|
||||
android:value="true" />
|
||||
|
||||
<!-- Disable Game Mode downscaling since it can break our UI dialogs and doesn't benefit
|
||||
performance much for us since we don't use GL/Vulkan for rendering anyway -->
|
||||
<meta-data
|
||||
android:name="com.android.graphics.intervention.wm.allowDownscale"
|
||||
android:value="false"/>
|
||||
|
||||
<!-- Samsung DeX support requires explicit placement of android:resizeableActivity="true"
|
||||
in each activity even though it is implied by targeting API 24+ -->
|
||||
|
||||
@@ -98,6 +108,7 @@
|
||||
<activity
|
||||
android:name=".preferences.StreamSettings"
|
||||
android:resizeableActivity="true"
|
||||
android:configChanges="mcc|mnc|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection"
|
||||
android:label="Streaming Settings">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
@@ -107,16 +118,15 @@
|
||||
android:name=".preferences.AddComputerManually"
|
||||
android:resizeableActivity="true"
|
||||
android:windowSoftInputMode="stateVisible"
|
||||
android:configChanges="mcc|mnc|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection"
|
||||
android:label="Add Computer Manually">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="com.limelight.PcView" />
|
||||
</activity>
|
||||
<!-- This will fall back to sensorLandscape at runtime on Android 4.2 and below -->
|
||||
<activity
|
||||
android:name=".Game"
|
||||
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection"
|
||||
android:screenOrientation="userLandscape"
|
||||
android:noHistory="true"
|
||||
android:supportsPictureInPicture="true"
|
||||
android:resizeableActivity="true"
|
||||
|
||||
@@ -293,6 +293,11 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
||||
|
||||
setContentView(R.layout.activity_app_view);
|
||||
|
||||
// Allow floating expanded PiP overlays while browsing apps
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
setShouldDockBigOverlays(false);
|
||||
}
|
||||
|
||||
UiHelper.notifyNewRootView(this);
|
||||
|
||||
showHiddenApps = getIntent().getBooleanExtra(SHOW_HIDDEN_APPS_EXTRA, false);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,9 +2,12 @@ package com.limelight;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.window.OnBackInvokedCallback;
|
||||
import android.window.OnBackInvokedDispatcher;
|
||||
|
||||
import com.limelight.utils.SpinnerDialog;
|
||||
|
||||
@@ -13,10 +16,26 @@ public class HelpActivity extends Activity {
|
||||
private SpinnerDialog loadingDialog;
|
||||
private WebView webView;
|
||||
|
||||
private boolean backCallbackRegistered;
|
||||
private OnBackInvokedCallback onBackInvokedCallback;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
onBackInvokedCallback = new OnBackInvokedCallback() {
|
||||
@Override
|
||||
public void onBackInvoked() {
|
||||
// We should always be able to go back because we unregister our callback
|
||||
// when we can't go back. Nonetheless, we will still check anyway.
|
||||
if (webView.canGoBack()) {
|
||||
webView.goBack();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
webView = new WebView(this);
|
||||
setContentView(webView);
|
||||
|
||||
@@ -39,6 +58,8 @@ public class HelpActivity extends Activity {
|
||||
getResources().getString(R.string.help_loading_title),
|
||||
getResources().getString(R.string.help_loading_msg), false);
|
||||
}
|
||||
|
||||
refreshBackDispatchState();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -47,6 +68,8 @@ public class HelpActivity extends Activity {
|
||||
loadingDialog.dismiss();
|
||||
loadingDialog = null;
|
||||
}
|
||||
|
||||
refreshBackDispatchState();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -59,7 +82,33 @@ public class HelpActivity extends Activity {
|
||||
webView.loadUrl(getIntent().getData().toString());
|
||||
}
|
||||
|
||||
private void refreshBackDispatchState() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
if (webView.canGoBack() && !backCallbackRegistered) {
|
||||
getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
|
||||
OnBackInvokedDispatcher.PRIORITY_DEFAULT, onBackInvokedCallback);
|
||||
backCallbackRegistered = true;
|
||||
}
|
||||
else if (!webView.canGoBack() && backCallbackRegistered) {
|
||||
getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(onBackInvokedCallback);
|
||||
backCallbackRegistered = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
if (backCallbackRegistered) {
|
||||
getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(onBackInvokedCallback);
|
||||
}
|
||||
}
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
// NOTE: This will NOT be called on Android 13+ with android:enableOnBackInvokedCallback="true"
|
||||
public void onBackPressed() {
|
||||
// Back goes back through the WebView history
|
||||
// until no more history remains
|
||||
|
||||
@@ -124,6 +124,11 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
|
||||
UiHelper.notifyNewRootView(this);
|
||||
|
||||
// Allow floating expanded PiP overlays while browsing PCs
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
setShouldDockBigOverlays(false);
|
||||
}
|
||||
|
||||
// Set default preferences if we've never been run
|
||||
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
|
||||
|
||||
@@ -155,6 +160,13 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
}
|
||||
});
|
||||
|
||||
// Amazon review didn't like the help button because the wiki was not entirely
|
||||
// navigable via the Fire TV remote (though the relevant parts were). Let's hide
|
||||
// it on Fire TV.
|
||||
if (getPackageManager().hasSystemFeature("amazon.hardware.fire_tv")) {
|
||||
helpButton.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
getFragmentManager().beginTransaction()
|
||||
.replace(R.id.pcFragmentContainer, new AdapterFragment())
|
||||
.commitAllowingStateLoss();
|
||||
@@ -367,15 +379,10 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
}
|
||||
|
||||
private void doPair(final ComputerDetails computer) {
|
||||
if (computer.state == ComputerDetails.State.OFFLINE ||
|
||||
ServerHelper.getCurrentAddressFromComputer(computer) == null) {
|
||||
if (computer.state == ComputerDetails.State.OFFLINE || computer.activeAddress == null) {
|
||||
Toast.makeText(PcView.this, getResources().getString(R.string.pair_pc_offline), Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
if (computer.runningGameId != 0) {
|
||||
Toast.makeText(PcView.this, getResources().getString(R.string.pair_pc_ingame), Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
if (managerBinder == null) {
|
||||
Toast.makeText(PcView.this, getResources().getString(R.string.error_manager_not_running), Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
@@ -393,8 +400,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
stopComputerUpdates(true);
|
||||
|
||||
httpConn = new NvHTTP(ServerHelper.getCurrentAddressFromComputer(computer),
|
||||
managerBinder.getUniqueId(),
|
||||
computer.serverCert,
|
||||
computer.httpsPort, managerBinder.getUniqueId(), computer.serverCert,
|
||||
PlatformBinding.getCryptoProvider(PcView.this));
|
||||
if (httpConn.getPairState() == PairState.PAIRED) {
|
||||
// Don't display any toast, but open the app list
|
||||
@@ -410,12 +416,17 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
|
||||
PairingManager pm = httpConn.getPairingManager();
|
||||
|
||||
PairState pairState = pm.pair(httpConn.getServerInfo(), pinStr);
|
||||
PairState pairState = pm.pair(httpConn.getServerInfo(true), pinStr);
|
||||
if (pairState == PairState.PIN_WRONG) {
|
||||
message = getResources().getString(R.string.pair_incorrect_pin);
|
||||
}
|
||||
else if (pairState == PairState.FAILED) {
|
||||
message = getResources().getString(R.string.pair_fail);
|
||||
if (computer.runningGameId != 0) {
|
||||
message = getResources().getString(R.string.pair_pc_ingame);
|
||||
}
|
||||
else {
|
||||
message = getResources().getString(R.string.pair_fail);
|
||||
}
|
||||
}
|
||||
else if (pairState == PairState.ALREADY_IN_PROGRESS) {
|
||||
message = getResources().getString(R.string.pair_already_in_progress);
|
||||
@@ -505,8 +516,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
}
|
||||
|
||||
private void doUnpair(final ComputerDetails computer) {
|
||||
if (computer.state == ComputerDetails.State.OFFLINE ||
|
||||
ServerHelper.getCurrentAddressFromComputer(computer) == null) {
|
||||
if (computer.state == ComputerDetails.State.OFFLINE || computer.activeAddress == null) {
|
||||
Toast.makeText(PcView.this, getResources().getString(R.string.error_pc_offline), Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
@@ -523,8 +533,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
String message;
|
||||
try {
|
||||
httpConn = new NvHTTP(ServerHelper.getCurrentAddressFromComputer(computer),
|
||||
managerBinder.getUniqueId(),
|
||||
computer.serverCert,
|
||||
computer.httpsPort, managerBinder.getUniqueId(), computer.serverCert,
|
||||
PlatformBinding.getCryptoProvider(PcView.this));
|
||||
if (httpConn.getPairState() == PairingManager.PairState.PAIRED) {
|
||||
httpConn.unpair();
|
||||
|
||||
@@ -13,11 +13,13 @@ import com.limelight.computers.ComputerManagerService;
|
||||
import com.limelight.nvstream.http.ComputerDetails;
|
||||
import com.limelight.nvstream.http.NvApp;
|
||||
import com.limelight.nvstream.http.PairingManager;
|
||||
import com.limelight.nvstream.wol.WakeOnLanSender;
|
||||
import com.limelight.utils.Dialog;
|
||||
import com.limelight.utils.ServerHelper;
|
||||
import com.limelight.utils.SpinnerDialog;
|
||||
import com.limelight.utils.UiHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -26,6 +28,7 @@ public class ShortcutTrampoline extends Activity {
|
||||
private NvApp app;
|
||||
private ArrayList<Intent> intentStack = new ArrayList<>();
|
||||
|
||||
private int wakeHostTries = 10;
|
||||
private ComputerDetails computer;
|
||||
private SpinnerDialog blockingLoadSpinner;
|
||||
|
||||
@@ -79,6 +82,23 @@ public class ShortcutTrampoline extends Activity {
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to wake the target PC if it's offline (up to some retry limit)
|
||||
if (details.state == ComputerDetails.State.OFFLINE && details.macAddress != null && --wakeHostTries >= 0) {
|
||||
try {
|
||||
// Make a best effort attempt to wake the target PC
|
||||
WakeOnLanSender.sendWolPacket(computer);
|
||||
|
||||
// If we sent at least one WoL packet, reset the computer state
|
||||
// to force ComputerManager to poll it again.
|
||||
managerBinder.invalidateStateForComputer(computer.uuid);
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
// If we got an exception, we couldn't send a single WoL packet,
|
||||
// so fallthrough into the offline error path.
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
if (details.state != ComputerDetails.State.UNKNOWN) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
|
||||
@@ -8,16 +8,6 @@ import com.limelight.nvstream.av.audio.AudioRenderer;
|
||||
import com.limelight.nvstream.http.LimelightCryptoProvider;
|
||||
|
||||
public class PlatformBinding {
|
||||
public static String getDeviceName() {
|
||||
String deviceName = android.os.Build.MODEL;
|
||||
deviceName = deviceName.replace(" ", "");
|
||||
return deviceName;
|
||||
}
|
||||
|
||||
public static AudioRenderer getAudioRenderer() {
|
||||
return new AndroidAudioRenderer();
|
||||
}
|
||||
|
||||
public static LimelightCryptoProvider getCryptoProvider(Context c) {
|
||||
return new AndroidCryptoProvider(c);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package com.limelight.binding.audio;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.media.AudioAttributes;
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioManager;
|
||||
import android.media.AudioTrack;
|
||||
import android.media.audiofx.AudioEffect;
|
||||
import android.os.Build;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
@@ -12,8 +15,16 @@ import com.limelight.nvstream.jni.MoonBridge;
|
||||
|
||||
public class AndroidAudioRenderer implements AudioRenderer {
|
||||
|
||||
private final Context context;
|
||||
private final boolean enableAudioFx;
|
||||
|
||||
private AudioTrack track;
|
||||
|
||||
public AndroidAudioRenderer(Context context, boolean enableAudioFx) {
|
||||
this.context = context;
|
||||
this.enableAudioFx = enableAudioFx;
|
||||
}
|
||||
|
||||
private AudioTrack createAudioTrack(int channelConfig, int sampleRate, int bufferSize, boolean lowLatency) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
return new AudioTrack(AudioManager.STREAM_MUSIC,
|
||||
@@ -161,6 +172,12 @@ public class AndroidAudioRenderer implements AudioRenderer {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip low latency options when using audio effects, since low latency mode
|
||||
// precludes the use of the audio effect pipeline (as of Android 13).
|
||||
if (enableAudioFx && lowLatency) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
track = createAudioTrack(channelConfig, sampleRate, bufferSize, lowLatency);
|
||||
track.play();
|
||||
@@ -203,10 +220,27 @@ public class AndroidAudioRenderer implements AudioRenderer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {}
|
||||
public void start() {
|
||||
if (enableAudioFx) {
|
||||
// Open an audio effect control session to allow equalizers to apply audio effects
|
||||
Intent i = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
|
||||
i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, track.getAudioSessionId());
|
||||
i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.getPackageName());
|
||||
i.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_GAME);
|
||||
context.sendBroadcast(i);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {}
|
||||
public void stop() {
|
||||
if (enableAudioFx) {
|
||||
// Close our audio effect control session when we're stopping
|
||||
Intent i = new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
|
||||
i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, track.getAudioSessionId());
|
||||
i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.getPackageName());
|
||||
context.sendBroadcast(i);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup() {
|
||||
|
||||
@@ -67,14 +67,12 @@ public class AndroidCryptoProvider implements LimelightCryptoProvider {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
FileInputStream fin = new FileInputStream(f);
|
||||
try (final FileInputStream fin = new FileInputStream(f)) {
|
||||
byte[] fileData = new byte[(int) f.length()];
|
||||
if (fin.read(fileData) != f.length()) {
|
||||
// Failed to read
|
||||
fileData = null;
|
||||
}
|
||||
fin.close();
|
||||
return fileData;
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
@@ -160,32 +158,28 @@ public class AndroidCryptoProvider implements LimelightCryptoProvider {
|
||||
}
|
||||
|
||||
private void saveCertKeyPair() {
|
||||
try {
|
||||
FileOutputStream certOut = new FileOutputStream(certFile);
|
||||
FileOutputStream keyOut = new FileOutputStream(keyFile);
|
||||
|
||||
try (final FileOutputStream certOut = new FileOutputStream(certFile);
|
||||
final FileOutputStream keyOut = new FileOutputStream(keyFile)
|
||||
) {
|
||||
// Write the certificate in OpenSSL PEM format (important for the server)
|
||||
StringWriter strWriter = new StringWriter();
|
||||
JcaPEMWriter pemWriter = new JcaPEMWriter(strWriter);
|
||||
pemWriter.writeObject(cert);
|
||||
pemWriter.close();
|
||||
try (final JcaPEMWriter pemWriter = new JcaPEMWriter(strWriter)) {
|
||||
pemWriter.writeObject(cert);
|
||||
}
|
||||
|
||||
// Line endings MUST be UNIX for the PC to accept the cert properly
|
||||
OutputStreamWriter certWriter = new OutputStreamWriter(certOut);
|
||||
String pemStr = strWriter.getBuffer().toString();
|
||||
for (int i = 0; i < pemStr.length(); i++) {
|
||||
char c = pemStr.charAt(i);
|
||||
if (c != '\r')
|
||||
certWriter.append(c);
|
||||
try (final OutputStreamWriter certWriter = new OutputStreamWriter(certOut)) {
|
||||
String pemStr = strWriter.getBuffer().toString();
|
||||
for (int i = 0; i < pemStr.length(); i++) {
|
||||
char c = pemStr.charAt(i);
|
||||
if (c != '\r')
|
||||
certWriter.append(c);
|
||||
}
|
||||
}
|
||||
certWriter.close();
|
||||
|
||||
// Write the private out in PKCS8 format
|
||||
keyOut.write(key.getEncoded());
|
||||
|
||||
certOut.close();
|
||||
keyOut.close();
|
||||
|
||||
LimeLog.info("Saved generated key pair to disk");
|
||||
} catch (IOException e) {
|
||||
// This isn't good because it means we'll have
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.limelight.binding.input;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.hardware.input.InputManager;
|
||||
@@ -7,9 +8,13 @@ import android.hardware.usb.UsbDevice;
|
||||
import android.hardware.usb.UsbManager;
|
||||
import android.media.AudioAttributes;
|
||||
import android.os.Build;
|
||||
import android.os.SystemClock;
|
||||
import android.os.CombinedVibration;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.VibrationAttributes;
|
||||
import android.os.VibrationEffect;
|
||||
import android.os.Vibrator;
|
||||
import android.os.VibratorManager;
|
||||
import android.util.SparseArray;
|
||||
import android.view.InputDevice;
|
||||
import android.view.InputEvent;
|
||||
@@ -28,9 +33,9 @@ import com.limelight.preferences.PreferenceConfiguration;
|
||||
import com.limelight.ui.GameGestures;
|
||||
import com.limelight.utils.Vector2d;
|
||||
|
||||
import org.cgutman.shieldcontrollerextensions.SceManager;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
public class ControllerHandler implements InputManager.InputDeviceListener, UsbDriverListener {
|
||||
|
||||
@@ -43,9 +48,6 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
private static final int EMULATING_SPECIAL = 0x1;
|
||||
private static final int EMULATING_SELECT = 0x2;
|
||||
|
||||
private static final int EMULATED_SPECIAL_UP_DELAY_MS = 100;
|
||||
private static final int EMULATED_SELECT_UP_DELAY_MS = 30;
|
||||
|
||||
private final Vector2d inputVector = new Vector2d();
|
||||
|
||||
private final SparseArray<InputDeviceContext> inputDeviceContexts = new SparseArray<>();
|
||||
@@ -57,6 +59,8 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
private final InputDeviceContext defaultContext = new InputDeviceContext();
|
||||
private final GameGestures gestures;
|
||||
private final Vibrator deviceVibrator;
|
||||
private final SceManager sceManager;
|
||||
private final Handler handler;
|
||||
private boolean hasGameController;
|
||||
|
||||
private final PreferenceConfiguration prefConfig;
|
||||
@@ -68,10 +72,12 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
this.gestures = gestures;
|
||||
this.prefConfig = prefConfig;
|
||||
this.deviceVibrator = (Vibrator) activityContext.getSystemService(Context.VIBRATOR_SERVICE);
|
||||
this.handler = new Handler(Looper.getMainLooper());
|
||||
|
||||
// HACK: For now we're hardcoding a 7% deadzone. Some deadzone
|
||||
// is required for controller batching support to work.
|
||||
int deadzonePercentage = 7;
|
||||
this.sceManager = new SceManager(activityContext);
|
||||
this.sceManager.start();
|
||||
|
||||
int deadzonePercentage = prefConfig.deadzonePercentage;
|
||||
|
||||
int[] ids = InputDevice.getDeviceIds();
|
||||
for (int id : ids) {
|
||||
@@ -155,11 +161,36 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
}
|
||||
}
|
||||
|
||||
// This can happen when gaining/losing input focus with some devices.
|
||||
// Input devices that have a trackpad may gain/lose AXIS_RELATIVE_X/Y.
|
||||
@Override
|
||||
public void onInputDeviceChanged(int deviceId) {
|
||||
// Remove and re-add
|
||||
onInputDeviceRemoved(deviceId);
|
||||
onInputDeviceAdded(deviceId);
|
||||
InputDevice device = InputDevice.getDevice(deviceId);
|
||||
if (device == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we don't have a context for this device, we don't need to update anything
|
||||
InputDeviceContext existingContext = inputDeviceContexts.get(deviceId);
|
||||
if (existingContext == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
LimeLog.info("Device changed: "+existingContext.name+" ("+deviceId+")");
|
||||
|
||||
// Don't release the controller number, because we will carry it over if it is present.
|
||||
// We also want to make sure the change is invisible to the host PC to avoid an add/remove
|
||||
// cycle for the gamepad which may break some games.
|
||||
existingContext.destroy();
|
||||
|
||||
InputDeviceContext newContext = createInputDeviceContextForDevice(device);
|
||||
|
||||
// Copy over existing controller number state
|
||||
newContext.assignedControllerNumber = existingContext.assignedControllerNumber;
|
||||
newContext.reservedControllerNumber = existingContext.reservedControllerNumber;
|
||||
newContext.controllerNumber = existingContext.controllerNumber;
|
||||
|
||||
inputDeviceContexts.put(deviceId, newContext);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
@@ -173,6 +204,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
deviceContext.destroy();
|
||||
}
|
||||
|
||||
sceManager.stop();
|
||||
deviceVibrator.cancel();
|
||||
}
|
||||
|
||||
@@ -478,6 +510,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
}
|
||||
LimeLog.info(dev.toString());
|
||||
|
||||
context.inputDevice = dev;
|
||||
context.name = devName;
|
||||
context.id = dev.getId();
|
||||
context.external = isExternal(dev);
|
||||
@@ -487,7 +520,10 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
context.productId = dev.getProductId();
|
||||
}
|
||||
|
||||
if (dev.getVibrator().hasVibrator()) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && hasDualAmplitudeControlledRumbleVibrators(dev.getVibratorManager())) {
|
||||
context.vibratorManager = dev.getVibratorManager();
|
||||
}
|
||||
else if (dev.getVibrator().hasVibrator()) {
|
||||
context.vibrator = dev.getVibrator();
|
||||
}
|
||||
|
||||
@@ -672,9 +708,6 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
}
|
||||
// SHIELD controllers will use small stick deadzones
|
||||
else if (devName.contains("SHIELD") || devName.contains("NVIDIA Controller")) {
|
||||
context.leftStickDeadzoneRadius = 0.07f;
|
||||
context.rightStickDeadzoneRadius = 0.07f;
|
||||
|
||||
// The big Nvidia button on the Shield controllers acts like a Search button. It
|
||||
// summons the Google Assistant on the Shield TV. On my Pixel 4, it seems to do
|
||||
// nothing, so we can hijack it to act like a mode button.
|
||||
@@ -1261,29 +1294,64 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
}
|
||||
}
|
||||
|
||||
private void toggleMouseEmulation(final GenericControllerContext context) {
|
||||
if (context.mouseEmulationTimer != null) {
|
||||
context.mouseEmulationTimer.cancel();
|
||||
context.mouseEmulationTimer = null;
|
||||
@TargetApi(31)
|
||||
private boolean hasDualAmplitudeControlledRumbleVibrators(VibratorManager vm) {
|
||||
int[] vibratorIds = vm.getVibratorIds();
|
||||
|
||||
// There must be exactly 2 vibrators on this device
|
||||
if (vibratorIds.length != 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
context.mouseEmulationActive = !context.mouseEmulationActive;
|
||||
Toast.makeText(activityContext, "Mouse emulation is: " + (context.mouseEmulationActive ? "ON" : "OFF"), Toast.LENGTH_SHORT).show();
|
||||
|
||||
if (context.mouseEmulationActive) {
|
||||
context.mouseEmulationTimer = new Timer();
|
||||
context.mouseEmulationTimer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Send mouse movement events from analog sticks
|
||||
sendEmulatedMouseEvent(context.leftStickX, context.leftStickY);
|
||||
sendEmulatedMouseEvent(context.rightStickX, context.rightStickY);
|
||||
}
|
||||
}, 50, 50);
|
||||
// Both vibrators must have amplitude control
|
||||
for (int vid : vibratorIds) {
|
||||
if (!vm.getVibrator(vid).hasAmplitudeControl()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void rumbleVibrator(Vibrator vibrator, short lowFreqMotor, short highFreqMotor) {
|
||||
// This must only be called if hasDualAmplitudeControlledRumbleVibrators() is true!
|
||||
@TargetApi(31)
|
||||
private void rumbleDualVibrators(VibratorManager vm, short lowFreqMotor, short highFreqMotor) {
|
||||
// Normalize motor values to 0-255 amplitudes for VibrationManager
|
||||
highFreqMotor = (short)((highFreqMotor >> 8) & 0xFF);
|
||||
lowFreqMotor = (short)((lowFreqMotor >> 8) & 0xFF);
|
||||
|
||||
// If they're both zero, we can just call cancel().
|
||||
if (lowFreqMotor == 0 && highFreqMotor == 0) {
|
||||
vm.cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
// There's no documentation that states that vibrators for FF_RUMBLE input devices will
|
||||
// always be enumerated in this order, but it seems consistent between Xbox Series X (USB),
|
||||
// PS3 (USB), and PS4 (USB+BT) controllers on Android 12 Beta 3.
|
||||
int[] vibratorIds = vm.getVibratorIds();
|
||||
int[] vibratorAmplitudes = new int[] { highFreqMotor, lowFreqMotor };
|
||||
|
||||
CombinedVibration.ParallelCombination combo = CombinedVibration.startParallel();
|
||||
|
||||
for (int i = 0; i < vibratorIds.length; i++) {
|
||||
// It's illegal to create a VibrationEffect with an amplitude of 0.
|
||||
// Simply excluding that vibrator from our ParallelCombination will turn it off.
|
||||
if (vibratorAmplitudes[i] != 0) {
|
||||
combo.addVibrator(vibratorIds[i], VibrationEffect.createOneShot(60000, vibratorAmplitudes[i]));
|
||||
}
|
||||
}
|
||||
|
||||
VibrationAttributes.Builder vibrationAttributes = new VibrationAttributes.Builder();
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
vibrationAttributes.setUsage(VibrationAttributes.USAGE_MEDIA);
|
||||
}
|
||||
|
||||
vm.vibrate(combo.combine(), vibrationAttributes.build());
|
||||
}
|
||||
|
||||
private void rumbleSingleVibrator(Vibrator vibrator, short lowFreqMotor, short highFreqMotor) {
|
||||
// Since we can only use a single amplitude value, compute the desired amplitude
|
||||
// by taking 80% of the big motor and 33% of the small motor, then capping to 255.
|
||||
// NB: This value is now 0-255 as required by VibrationEffect.
|
||||
@@ -1305,10 +1373,18 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
if (vibrator.hasAmplitudeControl()) {
|
||||
VibrationEffect effect = VibrationEffect.createOneShot(60000, simulatedAmplitude);
|
||||
AudioAttributes audioAttributes = new AudioAttributes.Builder()
|
||||
.setUsage(AudioAttributes.USAGE_GAME)
|
||||
.build();
|
||||
vibrator.vibrate(effect, audioAttributes);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
VibrationAttributes vibrationAttributes = new VibrationAttributes.Builder()
|
||||
.setUsage(VibrationAttributes.USAGE_MEDIA)
|
||||
.build();
|
||||
vibrator.vibrate(effect, vibrationAttributes);
|
||||
}
|
||||
else {
|
||||
AudioAttributes audioAttributes = new AudioAttributes.Builder()
|
||||
.setUsage(AudioAttributes.USAGE_GAME)
|
||||
.build();
|
||||
vibrator.vibrate(effect, audioAttributes);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1318,7 +1394,13 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
long pwmPeriod = 20;
|
||||
long onTime = (long)((simulatedAmplitude / 255.0) * pwmPeriod);
|
||||
long offTime = pwmPeriod - onTime;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
VibrationAttributes vibrationAttributes = new VibrationAttributes.Builder()
|
||||
.setUsage(VibrationAttributes.USAGE_MEDIA)
|
||||
.build();
|
||||
vibrator.vibrate(VibrationEffect.createWaveform(new long[]{0, onTime, offTime}, 0), vibrationAttributes);
|
||||
}
|
||||
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
AudioAttributes audioAttributes = new AudioAttributes.Builder()
|
||||
.setUsage(AudioAttributes.USAGE_GAME)
|
||||
.build();
|
||||
@@ -1339,9 +1421,19 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
if (deviceContext.controllerNumber == controllerNumber) {
|
||||
foundMatchingDevice = true;
|
||||
|
||||
if (deviceContext.vibrator != null) {
|
||||
// Prefer the documented Android 12 rumble API which can handle dual vibrators on PS/Xbox controllers
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && deviceContext.vibratorManager != null) {
|
||||
vibrated = true;
|
||||
rumbleVibrator(deviceContext.vibrator, lowFreqMotor, highFreqMotor);
|
||||
rumbleDualVibrators(deviceContext.vibratorManager, lowFreqMotor, highFreqMotor);
|
||||
}
|
||||
// On Shield devices, we can use their special API to rumble Shield controllers
|
||||
else if (sceManager.rumble(deviceContext.inputDevice, lowFreqMotor, highFreqMotor)) {
|
||||
vibrated = true;
|
||||
}
|
||||
// If all else fails, we have to try the old Vibrator API
|
||||
else if (deviceContext.vibrator != null) {
|
||||
vibrated = true;
|
||||
rumbleSingleVibrator(deviceContext.vibrator, lowFreqMotor, highFreqMotor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1361,12 +1453,12 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
// controls that triggered the rumble. Vibrate the device if
|
||||
// the user has requested that behavior.
|
||||
if (!foundMatchingDevice && prefConfig.onscreenController && !prefConfig.onlyL3R3 && prefConfig.vibrateOsc) {
|
||||
rumbleVibrator(deviceVibrator, lowFreqMotor, highFreqMotor);
|
||||
rumbleSingleVibrator(deviceVibrator, lowFreqMotor, highFreqMotor);
|
||||
}
|
||||
else if (foundMatchingDevice && !vibrated && prefConfig.vibrateFallbackToDevice) {
|
||||
// We found a device to vibrate but it didn't have rumble support. The user
|
||||
// has requested us to vibrate the device in this case.
|
||||
rumbleVibrator(deviceVibrator, lowFreqMotor, highFreqMotor);
|
||||
rumbleSingleVibrator(deviceVibrator, lowFreqMotor, highFreqMotor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1390,13 +1482,21 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
// If the button hasn't been down long enough, sleep for a bit before sending the up event
|
||||
// This allows "instant" button presses (like OUYA's virtual menu button) to work. This
|
||||
// path should not be triggered during normal usage.
|
||||
if (SystemClock.uptimeMillis() - event.getDownTime() < ControllerHandler.MINIMUM_BUTTON_DOWN_TIME_MS)
|
||||
int buttonDownTime = (int)(event.getEventTime() - event.getDownTime());
|
||||
if (buttonDownTime < ControllerHandler.MINIMUM_BUTTON_DOWN_TIME_MS)
|
||||
{
|
||||
// Since our sleep time is so short (10 ms), it shouldn't cause a problem doing this in the
|
||||
// UI thread.
|
||||
// Since our sleep time is so short (<= 25 ms), it shouldn't cause a problem doing this
|
||||
// in the UI thread.
|
||||
try {
|
||||
Thread.sleep(ControllerHandler.MINIMUM_BUTTON_DOWN_TIME_MS);
|
||||
} catch (InterruptedException ignored) {}
|
||||
Thread.sleep(ControllerHandler.MINIMUM_BUTTON_DOWN_TIME_MS - buttonDownTime);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
|
||||
// InterruptedException clears the thread's interrupt status. Since we can't
|
||||
// handle that here, we will re-interrupt the thread to set the interrupt
|
||||
// status back to true.
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
switch (keyCode) {
|
||||
@@ -1409,9 +1509,9 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
// Make sure it's real by checking that the key is actually down before taking
|
||||
// any action.
|
||||
if ((context.inputMap & ControllerPacket.PLAY_FLAG) != 0 &&
|
||||
SystemClock.uptimeMillis() - context.startDownTime > ControllerHandler.START_DOWN_TIME_MOUSE_MODE_MS &&
|
||||
event.getEventTime() - context.startDownTime > ControllerHandler.START_DOWN_TIME_MOUSE_MODE_MS &&
|
||||
prefConfig.mouseEmulation) {
|
||||
toggleMouseEmulation(context);
|
||||
context.toggleMouseEmulation();
|
||||
}
|
||||
context.inputMap &= ~ControllerPacket.PLAY_FLAG;
|
||||
break;
|
||||
@@ -1462,11 +1562,11 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_L1:
|
||||
context.inputMap &= ~ControllerPacket.LB_FLAG;
|
||||
context.lastLbUpTime = SystemClock.uptimeMillis();
|
||||
context.lastLbUpTime = event.getEventTime();
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_R1:
|
||||
context.inputMap &= ~ControllerPacket.RB_FLAG;
|
||||
context.lastRbUpTime = SystemClock.uptimeMillis();
|
||||
context.lastRbUpTime = event.getEventTime();
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_THUMBL:
|
||||
context.inputMap &= ~ControllerPacket.LS_CLK_FLAG;
|
||||
@@ -1502,10 +1602,6 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
context.inputMap &= ~ControllerPacket.BACK_FLAG;
|
||||
|
||||
context.emulatingButtonFlags &= ~ControllerHandler.EMULATING_SELECT;
|
||||
|
||||
try {
|
||||
Thread.sleep(EMULATED_SELECT_UP_DELAY_MS);
|
||||
} catch (InterruptedException ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1520,10 +1616,6 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
context.inputMap &= ~ControllerPacket.SPECIAL_BUTTON_FLAG;
|
||||
|
||||
context.emulatingButtonFlags &= ~ControllerHandler.EMULATING_SPECIAL;
|
||||
|
||||
try {
|
||||
Thread.sleep(EMULATED_SPECIAL_UP_DELAY_MS);
|
||||
} catch (InterruptedException ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1561,7 +1653,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
case KeyEvent.KEYCODE_BUTTON_START:
|
||||
case KeyEvent.KEYCODE_MENU:
|
||||
if (event.getRepeatCount() == 0) {
|
||||
context.startDownTime = SystemClock.uptimeMillis();
|
||||
context.startDownTime = event.getEventTime();
|
||||
}
|
||||
context.inputMap |= ControllerPacket.PLAY_FLAG;
|
||||
break;
|
||||
@@ -1652,7 +1744,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
if (!context.hasSelect) {
|
||||
if (context.inputMap == (ControllerPacket.PLAY_FLAG | ControllerPacket.LB_FLAG) ||
|
||||
(context.inputMap == ControllerPacket.PLAY_FLAG &&
|
||||
SystemClock.uptimeMillis() - context.lastLbUpTime <= MAXIMUM_BUMPER_UP_DELAY_MS))
|
||||
event.getEventTime() - context.lastLbUpTime <= MAXIMUM_BUMPER_UP_DELAY_MS))
|
||||
{
|
||||
context.inputMap &= ~(ControllerPacket.PLAY_FLAG | ControllerPacket.LB_FLAG);
|
||||
context.inputMap |= ControllerPacket.BACK_FLAG;
|
||||
@@ -1675,7 +1767,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
else {
|
||||
if (context.inputMap == (ControllerPacket.PLAY_FLAG | ControllerPacket.RB_FLAG) ||
|
||||
(context.inputMap == ControllerPacket.PLAY_FLAG &&
|
||||
SystemClock.uptimeMillis() - context.lastRbUpTime <= MAXIMUM_BUMPER_UP_DELAY_MS))
|
||||
event.getEventTime() - context.lastRbUpTime <= MAXIMUM_BUMPER_UP_DELAY_MS))
|
||||
{
|
||||
context.inputMap &= ~(ControllerPacket.PLAY_FLAG | ControllerPacket.RB_FLAG);
|
||||
context.inputMap |= ControllerPacket.SPECIAL_BUTTON_FLAG;
|
||||
@@ -1766,7 +1858,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
usbDeviceContexts.put(controller.getControllerId(), context);
|
||||
}
|
||||
|
||||
static class GenericControllerContext {
|
||||
class GenericControllerContext {
|
||||
public int id;
|
||||
public boolean external;
|
||||
|
||||
@@ -1790,20 +1882,46 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
public short leftStickY = 0x0000;
|
||||
|
||||
public boolean mouseEmulationActive;
|
||||
public Timer mouseEmulationTimer;
|
||||
public short mouseEmulationLastInputMap;
|
||||
public final int mouseEmulationReportPeriod = 50;
|
||||
|
||||
public final Runnable mouseEmulationRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!mouseEmulationActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Send mouse movement events from analog sticks
|
||||
sendEmulatedMouseEvent(leftStickX, leftStickY);
|
||||
sendEmulatedMouseEvent(rightStickX, rightStickY);
|
||||
|
||||
// Requeue the callback
|
||||
handler.postDelayed(this, mouseEmulationReportPeriod);
|
||||
}
|
||||
};
|
||||
|
||||
public void toggleMouseEmulation() {
|
||||
handler.removeCallbacks(mouseEmulationRunnable);
|
||||
mouseEmulationActive = !mouseEmulationActive;
|
||||
Toast.makeText(activityContext, "Mouse emulation is: " + (mouseEmulationActive ? "ON" : "OFF"), Toast.LENGTH_SHORT).show();
|
||||
|
||||
if (mouseEmulationActive) {
|
||||
handler.postDelayed(mouseEmulationRunnable, mouseEmulationReportPeriod);
|
||||
}
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
if (mouseEmulationTimer != null) {
|
||||
mouseEmulationTimer.cancel();
|
||||
mouseEmulationTimer = null;
|
||||
}
|
||||
mouseEmulationActive = false;
|
||||
handler.removeCallbacks(mouseEmulationRunnable);
|
||||
}
|
||||
}
|
||||
|
||||
class InputDeviceContext extends GenericControllerContext {
|
||||
public String name;
|
||||
public VibratorManager vibratorManager;
|
||||
public Vibrator vibrator;
|
||||
public InputDevice inputDevice;
|
||||
|
||||
public int leftStickXAxis = -1;
|
||||
public int leftStickYAxis = -1;
|
||||
@@ -1849,7 +1967,10 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
public void destroy() {
|
||||
super.destroy();
|
||||
|
||||
if (vibrator != null) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && vibratorManager != null) {
|
||||
vibratorManager.cancel();
|
||||
}
|
||||
else if (vibrator != null) {
|
||||
vibrator.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
package com.limelight.binding.input;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.hardware.input.InputManager;
|
||||
import android.os.Build;
|
||||
import android.util.SparseArray;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Class to translate a Android key code into the codes GFE is expecting
|
||||
* @author Diego Waxemberg
|
||||
* @author Cameron Gutman
|
||||
*/
|
||||
public class KeyboardTranslator {
|
||||
public class KeyboardTranslator implements InputManager.InputDeviceListener {
|
||||
|
||||
/**
|
||||
* GFE's prefix for every key code
|
||||
@@ -48,6 +55,55 @@ public class KeyboardTranslator {
|
||||
public static final int VK_QUOTE = 222;
|
||||
public static final int VK_PAUSE = 19;
|
||||
|
||||
private static class KeyboardMapping {
|
||||
private final InputDevice device;
|
||||
private final int[] deviceKeyCodeToQwertyKeyCode;
|
||||
|
||||
@TargetApi(33)
|
||||
public KeyboardMapping(InputDevice device) {
|
||||
int maxKeyCode = KeyEvent.getMaxKeyCode();
|
||||
|
||||
this.device = device;
|
||||
this.deviceKeyCodeToQwertyKeyCode = new int[maxKeyCode + 1];
|
||||
|
||||
// Any unmatched keycodes are treated as unknown
|
||||
Arrays.fill(deviceKeyCodeToQwertyKeyCode, KeyEvent.KEYCODE_UNKNOWN);
|
||||
|
||||
for (int i = 0; i <= maxKeyCode; i++) {
|
||||
int deviceKeyCode = device.getKeyCodeForKeyLocation(i);
|
||||
if (deviceKeyCode != KeyEvent.KEYCODE_UNKNOWN) {
|
||||
deviceKeyCodeToQwertyKeyCode[deviceKeyCode] = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(33)
|
||||
public int getDeviceKeyCodeForQwertyKeyCode(int qwertyKeyCode) {
|
||||
return device.getKeyCodeForKeyLocation(qwertyKeyCode);
|
||||
}
|
||||
|
||||
public int getQwertyKeyCodeForDeviceKeyCode(int deviceKeyCode) {
|
||||
if (deviceKeyCode > KeyEvent.getMaxKeyCode()) {
|
||||
return KeyEvent.KEYCODE_UNKNOWN;
|
||||
}
|
||||
|
||||
return deviceKeyCodeToQwertyKeyCode[deviceKeyCode];
|
||||
}
|
||||
}
|
||||
|
||||
private final SparseArray<KeyboardMapping> keyboardMappings = new SparseArray<>();
|
||||
|
||||
public KeyboardTranslator() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
for (int deviceId : InputDevice.getDeviceIds()) {
|
||||
InputDevice device = InputDevice.getDevice(deviceId);
|
||||
if (device != null && device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
|
||||
keyboardMappings.set(deviceId, new KeyboardMapping(device));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean needsShift(int keycode) {
|
||||
switch (keycode)
|
||||
{
|
||||
@@ -65,10 +121,24 @@ public class KeyboardTranslator {
|
||||
/**
|
||||
* Translates the given keycode and returns the GFE keycode
|
||||
* @param keycode the code to be translated
|
||||
* @param deviceId InputDevice.getId() or -1 if unknown
|
||||
* @return a GFE keycode for the given keycode
|
||||
*/
|
||||
public static short translate(int keycode) {
|
||||
public short translate(int keycode, int deviceId) {
|
||||
int translated;
|
||||
|
||||
// If a device ID was provided, look up the keyboard mapping
|
||||
if (deviceId >= 0) {
|
||||
KeyboardMapping mapping = keyboardMappings.get(deviceId);
|
||||
if (mapping != null) {
|
||||
// Try to map this device-specific keycode onto a QWERTY layout.
|
||||
// GFE assumes incoming keycodes are from a QWERTY keyboard.
|
||||
int qwertyKeyCode = mapping.getQwertyKeyCodeForDeviceKeyCode(keycode);
|
||||
if (qwertyKeyCode != KeyEvent.KEYCODE_UNKNOWN) {
|
||||
keycode = qwertyKeyCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is a poor man's mapping between Android key codes
|
||||
// and Windows VK_* codes. For all defined VK_ codes, see:
|
||||
@@ -294,4 +364,30 @@ public class KeyboardTranslator {
|
||||
return (short) ((KEY_PREFIX << 8) | translated);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInputDeviceAdded(int index) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
InputDevice device = InputDevice.getDevice(index);
|
||||
if (device != null && device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
|
||||
keyboardMappings.put(index, new KeyboardMapping(device));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInputDeviceRemoved(int index) {
|
||||
keyboardMappings.remove(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInputDeviceChanged(int index) {
|
||||
keyboardMappings.remove(index);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
InputDevice device = InputDevice.getDevice(index);
|
||||
if (device != null && device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
|
||||
keyboardMappings.set(index, new KeyboardMapping(device));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+95
-4
@@ -2,7 +2,9 @@ package com.limelight.binding.input.capture;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.hardware.input.InputManager;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.view.InputDevice;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
@@ -12,11 +14,13 @@ import android.view.View;
|
||||
// pointer icon hiding behavior over our stream view just in case pointer capture
|
||||
// is unavailable on this system (ex: DeX, ChromeOS)
|
||||
@TargetApi(Build.VERSION_CODES.O)
|
||||
public class AndroidNativePointerCaptureProvider extends AndroidPointerIconCaptureProvider {
|
||||
public class AndroidNativePointerCaptureProvider extends AndroidPointerIconCaptureProvider implements InputManager.InputDeviceListener {
|
||||
private InputManager inputManager;
|
||||
private View targetView;
|
||||
|
||||
public AndroidNativePointerCaptureProvider(Activity activity, View targetView) {
|
||||
super(activity, targetView);
|
||||
this.inputManager = activity.getSystemService(InputManager.class);
|
||||
this.targetView = targetView;
|
||||
}
|
||||
|
||||
@@ -24,25 +28,85 @@ public class AndroidNativePointerCaptureProvider extends AndroidPointerIconCaptu
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
|
||||
}
|
||||
|
||||
// We only capture the pointer if we have a compatible InputDevice
|
||||
// present. This is a workaround for an Android 12 regression causing
|
||||
// incorrect mouse input when using the SPen.
|
||||
// https://github.com/moonlight-stream/moonlight-android/issues/1030
|
||||
private boolean hasCaptureCompatibleInputDevice() {
|
||||
for (int id : InputDevice.getDeviceIds()) {
|
||||
InputDevice device = InputDevice.getDevice(id);
|
||||
if (device == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip touchscreens when considering compatible capture devices.
|
||||
// Samsung devices on Android 12 will report a sec_touchpad device
|
||||
// with SOURCE_TOUCHSCREEN, SOURCE_KEYBOARD, and SOURCE_MOUSE.
|
||||
// Upon enabling pointer capture, that device will switch to
|
||||
// SOURCE_KEYBOARD and SOURCE_TOUCHPAD.
|
||||
if (device.supportsSource(InputDevice.SOURCE_TOUCHSCREEN)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (device.supportsSource(InputDevice.SOURCE_MOUSE) ||
|
||||
device.supportsSource(InputDevice.SOURCE_MOUSE_RELATIVE) ||
|
||||
device.supportsSource(InputDevice.SOURCE_TOUCHPAD)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableCapture() {
|
||||
super.enableCapture();
|
||||
targetView.requestPointerCapture();
|
||||
|
||||
// Listen for device events to enable/disable capture
|
||||
inputManager.registerInputDeviceListener(this, null);
|
||||
|
||||
// Capture now if we have a capture-capable device
|
||||
if (hasCaptureCompatibleInputDevice()) {
|
||||
targetView.requestPointerCapture();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableCapture() {
|
||||
super.disableCapture();
|
||||
inputManager.unregisterInputDeviceListener(this);
|
||||
targetView.releasePointerCapture();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean focusActive) {
|
||||
if (!focusActive || !isCapturing) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Recapture the pointer if focus was regained. On Android Q,
|
||||
// we have to delay a bit before requesting capture because otherwise
|
||||
// we'll hit the "requestPointerCapture called for a window that has no focus"
|
||||
// error and it will not actually capture the cursor.
|
||||
Handler h = new Handler();
|
||||
h.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (hasCaptureCompatibleInputDevice()) {
|
||||
targetView.requestPointerCapture();
|
||||
}
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean eventHasRelativeMouseAxes(MotionEvent event) {
|
||||
// SOURCE_MOUSE_RELATIVE is how SOURCE_MOUSE appears when our view has pointer capture.
|
||||
// SOURCE_TOUCHPAD will have relative axes populated iff our view has pointer capture.
|
||||
// See https://developer.android.com/reference/android/view/View#requestPointerCapture()
|
||||
return event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE ||
|
||||
(event.getSource() == InputDevice.SOURCE_TOUCHPAD && targetView.hasPointerCapture());
|
||||
int eventSource = event.getSource();
|
||||
return (eventSource == InputDevice.SOURCE_MOUSE_RELATIVE && event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) ||
|
||||
(eventSource == InputDevice.SOURCE_TOUCHPAD && targetView.hasPointerCapture());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -66,4 +130,31 @@ public class AndroidNativePointerCaptureProvider extends AndroidPointerIconCaptu
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInputDeviceAdded(int deviceId) {
|
||||
// Check if we've added a capture-compatible device
|
||||
if (!targetView.hasPointerCapture() && hasCaptureCompatibleInputDevice()) {
|
||||
targetView.requestPointerCapture();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInputDeviceRemoved(int deviceId) {
|
||||
// Check if the capture-compatible device was removed
|
||||
if (targetView.hasPointerCapture() && !hasCaptureCompatibleInputDevice()) {
|
||||
targetView.releasePointerCapture();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInputDeviceChanged(int deviceId) {
|
||||
// Emulating a remove+add should be sufficient for our purposes.
|
||||
//
|
||||
// Note: This callback must be handled carefully because it can happen as a result of
|
||||
// calling requestPointerCapture(). This can cause trackpad devices to gain SOURCE_MOUSE_RELATIVE
|
||||
// and re-enter this callback.
|
||||
onInputDeviceRemoved(deviceId);
|
||||
onInputDeviceAdded(deviceId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ package com.limelight.binding.input.capture;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
import com.limelight.BuildConfig;
|
||||
import com.limelight.LimeLog;
|
||||
import com.limelight.LimelightBuildProps;
|
||||
import com.limelight.R;
|
||||
import com.limelight.binding.input.evdev.EvdevCaptureProviderShim;
|
||||
import com.limelight.binding.input.evdev.EvdevListener;
|
||||
@@ -16,7 +16,7 @@ public class InputCaptureManager {
|
||||
}
|
||||
// LineageOS implemented broken NVIDIA capture extensions, so avoid using them on root builds.
|
||||
// See https://github.com/LineageOS/android_frameworks_base/commit/d304f478a023430f4712dbdc3ee69d9ad02cebd3
|
||||
else if (!LimelightBuildProps.ROOT_BUILD && ShieldCaptureProvider.isCaptureProviderSupported()) {
|
||||
else if (!BuildConfig.ROOT_BUILD && ShieldCaptureProvider.isCaptureProviderSupported()) {
|
||||
LimeLog.info("Using NVIDIA mouse capture extension");
|
||||
return new ShieldCaptureProvider(activity);
|
||||
}
|
||||
|
||||
@@ -33,4 +33,6 @@ public abstract class InputCaptureProvider {
|
||||
public float getRelativeAxisY(MotionEvent event) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void onWindowFocusChanged(boolean focusActive) {}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ import android.hardware.usb.UsbDevice;
|
||||
import android.hardware.usb.UsbDeviceConnection;
|
||||
import android.hardware.usb.UsbEndpoint;
|
||||
import android.hardware.usb.UsbInterface;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
import com.limelight.binding.video.MediaCodecHelper;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
@@ -37,7 +37,9 @@ public abstract class AbstractXboxController extends AbstractController {
|
||||
// around when we call notifyDeviceAdded(), we won't be able to claim
|
||||
// the controller number used by the original InputDevice.
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {}
|
||||
} catch (InterruptedException e) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Report that we're added _before_ reporting input
|
||||
notifyDeviceAdded();
|
||||
@@ -56,7 +58,7 @@ public abstract class AbstractXboxController extends AbstractController {
|
||||
|
||||
do {
|
||||
// Read the next input state packet
|
||||
long lastMillis = MediaCodecHelper.getMonotonicMillis();
|
||||
long lastMillis = SystemClock.uptimeMillis();
|
||||
res = connection.bulkTransfer(inEndpt, buffer, buffer.length, 3000);
|
||||
|
||||
// If we get a zero length response, treat it as an error
|
||||
@@ -64,7 +66,7 @@ public abstract class AbstractXboxController extends AbstractController {
|
||||
res = -1;
|
||||
}
|
||||
|
||||
if (res == -1 && MediaCodecHelper.getMonotonicMillis() - lastMillis < 1000) {
|
||||
if (res == -1 && SystemClock.uptimeMillis() - lastMillis < 1000) {
|
||||
LimeLog.warning("Detected device I/O error");
|
||||
AbstractXboxController.this.stop();
|
||||
break;
|
||||
|
||||
@@ -29,6 +29,7 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
||||
|
||||
private UsbManager usbManager;
|
||||
private PreferenceConfiguration prefConfig;
|
||||
private boolean started;
|
||||
|
||||
private final UsbEventReceiver receiver = new UsbEventReceiver();
|
||||
private final UsbDriverBinder binder = new UsbDriverBinder();
|
||||
@@ -36,6 +37,7 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
||||
private final ArrayList<AbstractController> controllers = new ArrayList<>();
|
||||
|
||||
private UsbDriverListener listener;
|
||||
private UsbDriverStateListener stateListener;
|
||||
private int nextDeviceId;
|
||||
|
||||
@Override
|
||||
@@ -93,6 +95,11 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
||||
else if (action.equals(ACTION_USB_PERMISSION)) {
|
||||
UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
||||
|
||||
// Permission dialog is now closed
|
||||
if (stateListener != null) {
|
||||
stateListener.onUsbPermissionPromptCompleted();
|
||||
}
|
||||
|
||||
// If we got this far, we've already found we're able to handle this device
|
||||
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
|
||||
handleUsbDeviceState(device);
|
||||
@@ -112,6 +119,18 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setStateListener(UsbDriverStateListener stateListener) {
|
||||
UsbDriverService.this.stateListener = stateListener;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
UsbDriverService.this.start();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
UsbDriverService.this.stop();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleUsbDeviceState(UsbDevice device) {
|
||||
@@ -121,15 +140,29 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
||||
if (!usbManager.hasPermission(device)) {
|
||||
// Let's ask for permission
|
||||
try {
|
||||
// Tell the state listener that we're about to display a permission dialog
|
||||
if (stateListener != null) {
|
||||
stateListener.onUsbPermissionPromptStarting();
|
||||
}
|
||||
|
||||
int intentFlags = 0;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
// This PendingIntent must be mutable to allow the framework to populate EXTRA_DEVICE and EXTRA_PERMISSION_GRANTED.
|
||||
intentFlags |= PendingIntent.FLAG_MUTABLE;
|
||||
}
|
||||
|
||||
// This function is not documented as throwing any exceptions (denying access
|
||||
// is indicated by calling the PendingIntent with a false result). However,
|
||||
// Samsung Knox has some policies which block this request, but rather than
|
||||
// just returning a false result or returning 0 enumerated devices,
|
||||
// they throw an undocumented SecurityException from this call, crashing
|
||||
// the whole app. :(
|
||||
usbManager.requestPermission(device, PendingIntent.getBroadcast(UsbDriverService.this, 0, new Intent(ACTION_USB_PERMISSION), 0));
|
||||
usbManager.requestPermission(device, PendingIntent.getBroadcast(UsbDriverService.this, 0, new Intent(ACTION_USB_PERMISSION), intentFlags));
|
||||
} catch (SecurityException e) {
|
||||
Toast.makeText(this, this.getText(R.string.error_usb_prohibited), Toast.LENGTH_LONG).show();
|
||||
if (stateListener != null) {
|
||||
stateListener.onUsbPermissionPromptCompleted();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -220,16 +253,23 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
||||
((!isRecognizedInputDevice(device) || claimAllAvailable) && Xbox360Controller.canClaimDevice(device));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
this.usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
|
||||
this.prefConfig = PreferenceConfiguration.readPreferences(this);
|
||||
private void start() {
|
||||
if (started) {
|
||||
return;
|
||||
}
|
||||
|
||||
started = true;
|
||||
|
||||
// Register for USB attach broadcasts and permission completions
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
|
||||
filter.addAction(ACTION_USB_PERMISSION);
|
||||
registerReceiver(receiver, filter);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
registerReceiver(receiver, filter, RECEIVER_NOT_EXPORTED);
|
||||
}
|
||||
else {
|
||||
registerReceiver(receiver, filter);
|
||||
}
|
||||
|
||||
// Enumerate existing devices
|
||||
for (UsbDevice dev : usbManager.getDeviceList().values()) {
|
||||
@@ -240,14 +280,16 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
private void stop() {
|
||||
if (!started) {
|
||||
return;
|
||||
}
|
||||
|
||||
started = false;
|
||||
|
||||
// Stop the attachment receiver
|
||||
unregisterReceiver(receiver);
|
||||
|
||||
// Remove listeners
|
||||
listener = null;
|
||||
|
||||
// Stop all controllers
|
||||
while (controllers.size() > 0) {
|
||||
// Stop and remove the controller
|
||||
@@ -255,8 +297,28 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
this.usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
|
||||
this.prefConfig = PreferenceConfiguration.readPreferences(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
stop();
|
||||
|
||||
// Remove listeners
|
||||
listener = null;
|
||||
stateListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return binder;
|
||||
}
|
||||
|
||||
public interface UsbDriverStateListener {
|
||||
void onUsbPermissionPromptStarting();
|
||||
void onUsbPermissionPromptCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@ package com.limelight.binding.input.evdev;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
import com.limelight.LimelightBuildProps;
|
||||
import com.limelight.BuildConfig;
|
||||
import com.limelight.binding.input.capture.InputCaptureProvider;
|
||||
|
||||
public class EvdevCaptureProviderShim {
|
||||
public static boolean isCaptureProviderSupported() {
|
||||
return LimelightBuildProps.ROOT_BUILD;
|
||||
return BuildConfig.ROOT_BUILD;
|
||||
}
|
||||
|
||||
// We need to construct our capture provider using reflection because it isn't included in non-root builds
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
package com.limelight.binding.input.touch;
|
||||
|
||||
import android.os.SystemClock;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.View;
|
||||
|
||||
import com.limelight.nvstream.NvConnection;
|
||||
import com.limelight.nvstream.input.MouseButtonPacket;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
public class AbsoluteTouchContext implements TouchContext {
|
||||
private int lastTouchDownX = 0;
|
||||
private int lastTouchDownY = 0;
|
||||
@@ -21,15 +19,43 @@ public class AbsoluteTouchContext implements TouchContext {
|
||||
private boolean cancelled;
|
||||
private boolean confirmedLongPress;
|
||||
private boolean confirmedTap;
|
||||
private Timer longPressTimer;
|
||||
private Timer tapDownTimer;
|
||||
private float accumulatedScrollDelta;
|
||||
|
||||
private final Runnable longPressRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// This timer should have already expired, but cancel it just in case
|
||||
cancelTapDownTimer();
|
||||
|
||||
// Switch from a left click to a right click after a long press
|
||||
confirmedLongPress = true;
|
||||
if (confirmedTap) {
|
||||
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_LEFT);
|
||||
}
|
||||
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_RIGHT);
|
||||
}
|
||||
};
|
||||
|
||||
private final Runnable tapDownRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Start our tap
|
||||
tapConfirmed();
|
||||
}
|
||||
};
|
||||
|
||||
private final NvConnection conn;
|
||||
private final int actionIndex;
|
||||
private final View targetView;
|
||||
private final Handler handler;
|
||||
|
||||
private static final int SCROLL_SPEED_DIVISOR = 20;
|
||||
private final Runnable leftButtonUpRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_LEFT);
|
||||
}
|
||||
};
|
||||
|
||||
private static final int SCROLL_SPEED_FACTOR = 3;
|
||||
|
||||
private static final int LONG_PRESS_TIME_THRESHOLD = 650;
|
||||
private static final int LONG_PRESS_DISTANCE_THRESHOLD = 30;
|
||||
@@ -45,6 +71,7 @@ public class AbsoluteTouchContext implements TouchContext {
|
||||
this.conn = conn;
|
||||
this.actionIndex = actionIndex;
|
||||
this.targetView = view;
|
||||
this.handler = new Handler(Looper.getMainLooper());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -54,7 +81,7 @@ public class AbsoluteTouchContext implements TouchContext {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean touchDownEvent(int eventX, int eventY, boolean isNewFinger)
|
||||
public boolean touchDownEvent(int eventX, int eventY, long eventTime, boolean isNewFinger)
|
||||
{
|
||||
if (!isNewFinger) {
|
||||
// We don't handle finger transitions for absolute mode
|
||||
@@ -63,9 +90,8 @@ public class AbsoluteTouchContext implements TouchContext {
|
||||
|
||||
lastTouchLocationX = lastTouchDownX = eventX;
|
||||
lastTouchLocationY = lastTouchDownY = eventY;
|
||||
lastTouchDownTime = SystemClock.uptimeMillis();
|
||||
lastTouchDownTime = eventTime;
|
||||
cancelled = confirmedTap = confirmedLongPress = false;
|
||||
accumulatedScrollDelta = 0;
|
||||
|
||||
if (actionIndex == 0) {
|
||||
// Start the timers
|
||||
@@ -92,7 +118,7 @@ public class AbsoluteTouchContext implements TouchContext {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void touchUpEvent(int eventX, int eventY)
|
||||
public void touchUpEvent(int eventX, int eventY, long eventTime)
|
||||
{
|
||||
if (cancelled) {
|
||||
return;
|
||||
@@ -115,80 +141,35 @@ public class AbsoluteTouchContext implements TouchContext {
|
||||
// deadzone time. We'll need to send the touch down and up events now at the
|
||||
// original touch down position.
|
||||
tapConfirmed();
|
||||
try {
|
||||
// FIXME: Sleeping on the main thread sucks
|
||||
Thread.sleep(50);
|
||||
} catch (InterruptedException ignored) {}
|
||||
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_LEFT);
|
||||
|
||||
// Release the left mouse button in 100ms to allow for apps that use polling
|
||||
// to detect mouse button presses.
|
||||
handler.removeCallbacks(leftButtonUpRunnable);
|
||||
handler.postDelayed(leftButtonUpRunnable, 100);
|
||||
}
|
||||
}
|
||||
|
||||
lastTouchLocationX = lastTouchUpX = eventX;
|
||||
lastTouchLocationY = lastTouchUpY = eventY;
|
||||
lastTouchUpTime = SystemClock.uptimeMillis();
|
||||
lastTouchUpTime = eventTime;
|
||||
}
|
||||
|
||||
private synchronized void startLongPressTimer() {
|
||||
longPressTimer = new Timer(true);
|
||||
longPressTimer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (AbsoluteTouchContext.this) {
|
||||
// Check if someone cancelled us
|
||||
if (longPressTimer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Uncancellable now
|
||||
longPressTimer = null;
|
||||
|
||||
// This timer should have already expired, but cancel it just in case
|
||||
cancelTapDownTimer();
|
||||
|
||||
// Switch from a left click to a right click after a long press
|
||||
confirmedLongPress = true;
|
||||
if (confirmedTap) {
|
||||
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_LEFT);
|
||||
}
|
||||
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_RIGHT);
|
||||
}
|
||||
}
|
||||
}, LONG_PRESS_TIME_THRESHOLD);
|
||||
private void startLongPressTimer() {
|
||||
cancelLongPressTimer();
|
||||
handler.postDelayed(longPressRunnable, LONG_PRESS_TIME_THRESHOLD);
|
||||
}
|
||||
|
||||
private synchronized void cancelLongPressTimer() {
|
||||
if (longPressTimer != null) {
|
||||
longPressTimer.cancel();
|
||||
longPressTimer = null;
|
||||
}
|
||||
private void cancelLongPressTimer() {
|
||||
handler.removeCallbacks(longPressRunnable);
|
||||
}
|
||||
|
||||
private synchronized void startTapDownTimer() {
|
||||
tapDownTimer = new Timer(true);
|
||||
tapDownTimer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (AbsoluteTouchContext.this) {
|
||||
// Check if someone cancelled us
|
||||
if (tapDownTimer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Uncancellable now
|
||||
tapDownTimer = null;
|
||||
|
||||
// Start our tap
|
||||
tapConfirmed();
|
||||
}
|
||||
}
|
||||
}, TOUCH_DOWN_DEAD_ZONE_TIME_THRESHOLD);
|
||||
private void startTapDownTimer() {
|
||||
cancelTapDownTimer();
|
||||
handler.postDelayed(tapDownRunnable, TOUCH_DOWN_DEAD_ZONE_TIME_THRESHOLD);
|
||||
}
|
||||
|
||||
private synchronized void cancelTapDownTimer() {
|
||||
if (tapDownTimer != null) {
|
||||
tapDownTimer.cancel();
|
||||
tapDownTimer = null;
|
||||
}
|
||||
private void cancelTapDownTimer() {
|
||||
handler.removeCallbacks(tapDownRunnable);
|
||||
}
|
||||
|
||||
private void tapConfirmed() {
|
||||
@@ -209,7 +190,7 @@ public class AbsoluteTouchContext implements TouchContext {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean touchMoveEvent(int eventX, int eventY)
|
||||
public boolean touchMoveEvent(int eventX, int eventY, long eventTime)
|
||||
{
|
||||
if (cancelled) {
|
||||
return true;
|
||||
@@ -228,11 +209,7 @@ public class AbsoluteTouchContext implements TouchContext {
|
||||
}
|
||||
}
|
||||
else if (actionIndex == 1) {
|
||||
accumulatedScrollDelta += (eventY - lastTouchLocationY) / (float)SCROLL_SPEED_DIVISOR;
|
||||
if ((short)accumulatedScrollDelta != 0) {
|
||||
conn.sendMouseHighResScroll((short)accumulatedScrollDelta);
|
||||
accumulatedScrollDelta -= (short)accumulatedScrollDelta;
|
||||
}
|
||||
conn.sendMouseHighResScroll((short)((eventY - lastTouchLocationY) * SCROLL_SPEED_FACTOR));
|
||||
}
|
||||
|
||||
lastTouchLocationX = eventX;
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
package com.limelight.binding.input.touch;
|
||||
|
||||
import android.os.SystemClock;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.View;
|
||||
|
||||
import com.limelight.nvstream.NvConnection;
|
||||
import com.limelight.nvstream.input.MouseButtonPacket;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import com.limelight.preferences.PreferenceConfiguration;
|
||||
|
||||
public class RelativeTouchContext implements TouchContext {
|
||||
private int lastTouchX = 0;
|
||||
@@ -19,7 +18,6 @@ public class RelativeTouchContext implements TouchContext {
|
||||
private boolean confirmedMove;
|
||||
private boolean confirmedDrag;
|
||||
private boolean confirmedScroll;
|
||||
private Timer dragTimer;
|
||||
private double distanceMoved;
|
||||
private double xFactor, yFactor;
|
||||
private int pointerCount;
|
||||
@@ -30,22 +28,80 @@ public class RelativeTouchContext implements TouchContext {
|
||||
private final int referenceWidth;
|
||||
private final int referenceHeight;
|
||||
private final View targetView;
|
||||
private final PreferenceConfiguration prefConfig;
|
||||
private final Handler handler;
|
||||
|
||||
private final Runnable dragTimerRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Check if someone already set move
|
||||
if (confirmedMove) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The drag should only be processed for the primary finger
|
||||
if (actionIndex != maxPointerCountInGesture - 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We haven't been cancelled before the timer expired so begin dragging
|
||||
confirmedDrag = true;
|
||||
conn.sendMouseButtonDown(getMouseButtonIndex());
|
||||
}
|
||||
};
|
||||
|
||||
// Indexed by MouseButtonPacket.BUTTON_XXX - 1
|
||||
private final Runnable[] buttonUpRunnables = new Runnable[] {
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_LEFT);
|
||||
}
|
||||
},
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_MIDDLE);
|
||||
}
|
||||
},
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_RIGHT);
|
||||
}
|
||||
},
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_X1);
|
||||
}
|
||||
},
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_X2);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private static final int TAP_MOVEMENT_THRESHOLD = 20;
|
||||
private static final int TAP_DISTANCE_THRESHOLD = 25;
|
||||
private static final int TAP_TIME_THRESHOLD = 250;
|
||||
private static final int DRAG_TIME_THRESHOLD = 650;
|
||||
|
||||
private static final int SCROLL_SPEED_DIVISOR = 20;
|
||||
private static final int SCROLL_SPEED_FACTOR = 5;
|
||||
|
||||
public RelativeTouchContext(NvConnection conn, int actionIndex,
|
||||
int referenceWidth, int referenceHeight, View view)
|
||||
int referenceWidth, int referenceHeight,
|
||||
View view, PreferenceConfiguration prefConfig)
|
||||
{
|
||||
this.conn = conn;
|
||||
this.actionIndex = actionIndex;
|
||||
this.referenceWidth = referenceWidth;
|
||||
this.referenceHeight = referenceHeight;
|
||||
this.targetView = view;
|
||||
this.prefConfig = prefConfig;
|
||||
this.handler = new Handler(Looper.getMainLooper());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -62,7 +118,7 @@ public class RelativeTouchContext implements TouchContext {
|
||||
yDelta <= TAP_MOVEMENT_THRESHOLD;
|
||||
}
|
||||
|
||||
private boolean isTap()
|
||||
private boolean isTap(long eventTime)
|
||||
{
|
||||
if (confirmedDrag || confirmedMove || confirmedScroll) {
|
||||
return false;
|
||||
@@ -75,7 +131,7 @@ public class RelativeTouchContext implements TouchContext {
|
||||
return false;
|
||||
}
|
||||
|
||||
long timeDelta = SystemClock.uptimeMillis() - originalTouchTime;
|
||||
long timeDelta = eventTime - originalTouchTime;
|
||||
return isWithinTapBounds(lastTouchX, lastTouchY) && timeDelta <= TAP_TIME_THRESHOLD;
|
||||
}
|
||||
|
||||
@@ -90,7 +146,7 @@ public class RelativeTouchContext implements TouchContext {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean touchDownEvent(int eventX, int eventY, boolean isNewFinger)
|
||||
public boolean touchDownEvent(int eventX, int eventY, long eventTime, boolean isNewFinger)
|
||||
{
|
||||
// Get the view dimensions to scale inputs on this touch
|
||||
xFactor = referenceWidth / (double)targetView.getWidth();
|
||||
@@ -101,7 +157,7 @@ public class RelativeTouchContext implements TouchContext {
|
||||
|
||||
if (isNewFinger) {
|
||||
maxPointerCountInGesture = pointerCount;
|
||||
originalTouchTime = SystemClock.uptimeMillis();
|
||||
originalTouchTime = eventTime;
|
||||
cancelled = confirmedDrag = confirmedMove = confirmedScroll = false;
|
||||
distanceMoved = 0;
|
||||
|
||||
@@ -115,7 +171,7 @@ public class RelativeTouchContext implements TouchContext {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void touchUpEvent(int eventX, int eventY)
|
||||
public void touchUpEvent(int eventX, int eventY, long eventTime)
|
||||
{
|
||||
if (cancelled) {
|
||||
return;
|
||||
@@ -130,60 +186,29 @@ public class RelativeTouchContext implements TouchContext {
|
||||
// Raise the button after a drag
|
||||
conn.sendMouseButtonUp(buttonIndex);
|
||||
}
|
||||
else if (isTap())
|
||||
else if (isTap(eventTime))
|
||||
{
|
||||
// Lower the mouse button
|
||||
conn.sendMouseButtonDown(buttonIndex);
|
||||
|
||||
// We need to sleep a bit here because some games
|
||||
// do input detection by polling
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException ignored) {}
|
||||
|
||||
// Raise the mouse button
|
||||
conn.sendMouseButtonUp(buttonIndex);
|
||||
// Release the mouse button in 100ms to allow for apps that use polling
|
||||
// to detect mouse button presses.
|
||||
Runnable buttonUpRunnable = buttonUpRunnables[buttonIndex - 1];
|
||||
handler.removeCallbacks(buttonUpRunnable);
|
||||
handler.postDelayed(buttonUpRunnable, 100);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void startDragTimer() {
|
||||
// Cancel any existing drag timers
|
||||
private void startDragTimer() {
|
||||
cancelDragTimer();
|
||||
|
||||
dragTimer = new Timer(true);
|
||||
dragTimer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (RelativeTouchContext.this) {
|
||||
// Check if someone already set move
|
||||
if (confirmedMove) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if someone cancelled us
|
||||
if (dragTimer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Uncancellable now
|
||||
dragTimer = null;
|
||||
|
||||
// We haven't been cancelled before the timer expired so begin dragging
|
||||
confirmedDrag = true;
|
||||
conn.sendMouseButtonDown(getMouseButtonIndex());
|
||||
}
|
||||
}
|
||||
}, DRAG_TIME_THRESHOLD);
|
||||
handler.postDelayed(dragTimerRunnable, DRAG_TIME_THRESHOLD);
|
||||
}
|
||||
|
||||
private synchronized void cancelDragTimer() {
|
||||
if (dragTimer != null) {
|
||||
dragTimer.cancel();
|
||||
dragTimer = null;
|
||||
}
|
||||
private void cancelDragTimer() {
|
||||
handler.removeCallbacks(dragTimerRunnable);
|
||||
}
|
||||
|
||||
private synchronized void checkForConfirmedMove(int eventX, int eventY) {
|
||||
private void checkForConfirmedMove(int eventX, int eventY) {
|
||||
// If we've already confirmed something, get out now
|
||||
if (confirmedMove || confirmedDrag) {
|
||||
return;
|
||||
@@ -213,7 +238,7 @@ public class RelativeTouchContext implements TouchContext {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean touchMoveEvent(int eventX, int eventY)
|
||||
public boolean touchMoveEvent(int eventX, int eventY, long eventTime)
|
||||
{
|
||||
if (cancelled) {
|
||||
return true;
|
||||
@@ -243,12 +268,19 @@ public class RelativeTouchContext implements TouchContext {
|
||||
|
||||
if (pointerCount == 2) {
|
||||
if (confirmedScroll) {
|
||||
deltaY /= SCROLL_SPEED_DIVISOR;
|
||||
|
||||
conn.sendMouseHighResScroll((short) deltaY);
|
||||
conn.sendMouseHighResScroll((short)(deltaY * SCROLL_SPEED_FACTOR));
|
||||
}
|
||||
} else {
|
||||
conn.sendMouseMove((short) deltaX, (short) deltaY);
|
||||
if (prefConfig.absoluteMouseMode) {
|
||||
conn.sendMouseMoveAsMousePosition(
|
||||
(short) deltaX,
|
||||
(short) deltaY,
|
||||
(short) targetView.getWidth(),
|
||||
(short) targetView.getHeight());
|
||||
}
|
||||
else {
|
||||
conn.sendMouseMove((short) deltaX, (short) deltaY);
|
||||
}
|
||||
}
|
||||
|
||||
// If the scaling factor ended up rounding deltas to zero, wait until they are
|
||||
|
||||
@@ -3,9 +3,9 @@ package com.limelight.binding.input.touch;
|
||||
public interface TouchContext {
|
||||
int getActionIndex();
|
||||
void setPointerCount(int pointerCount);
|
||||
boolean touchDownEvent(int eventX, int eventY, boolean isNewFinger);
|
||||
boolean touchMoveEvent(int eventX, int eventY);
|
||||
void touchUpEvent(int eventX, int eventY);
|
||||
boolean touchDownEvent(int eventX, int eventY, long eventTime, boolean isNewFinger);
|
||||
boolean touchMoveEvent(int eventX, int eventY, long eventTime);
|
||||
void touchUpEvent(int eventX, int eventY, long eventTime);
|
||||
void cancelTouch();
|
||||
boolean isCancelled();
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.os.SystemClock;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -254,7 +253,7 @@ public class AnalogStick extends VirtualControllerElement {
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePosition() {
|
||||
private void updatePosition(long eventTime) {
|
||||
// get 100% way
|
||||
float complete = radius_complete - radius_analog_stick;
|
||||
|
||||
@@ -271,7 +270,7 @@ public class AnalogStick extends VirtualControllerElement {
|
||||
// We also release the deadzone if the user keeps the stick pressed for a bit to allow
|
||||
// them to make precise movements.
|
||||
stick_state = (stick_state == STICK_STATE.MOVED_ACTIVE ||
|
||||
SystemClock.uptimeMillis() - timeLastClick > timeoutDeadzone ||
|
||||
eventTime - timeLastClick > timeoutDeadzone ||
|
||||
movement_radius > radius_dead_zone) ?
|
||||
STICK_STATE.MOVED_ACTIVE : STICK_STATE.MOVED_IN_DEAD_ZONE;
|
||||
|
||||
@@ -306,13 +305,12 @@ public class AnalogStick extends VirtualControllerElement {
|
||||
// handle event depending on action
|
||||
switch (event.getActionMasked()) {
|
||||
// down event (touch event)
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
case MotionEvent.ACTION_POINTER_DOWN: {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
// set to dead zoned, will be corrected in update position if necessary
|
||||
stick_state = STICK_STATE.MOVED_IN_DEAD_ZONE;
|
||||
// check for double click
|
||||
if (lastClickState == CLICK_STATE.SINGLE &&
|
||||
timeLastClick + timeoutDoubleClick > SystemClock.uptimeMillis()) {
|
||||
event.getEventTime() - timeLastClick <= timeoutDoubleClick) {
|
||||
click_state = CLICK_STATE.DOUBLE;
|
||||
notifyOnDoubleClick();
|
||||
} else {
|
||||
@@ -320,14 +318,14 @@ public class AnalogStick extends VirtualControllerElement {
|
||||
notifyOnClick();
|
||||
}
|
||||
// reset last click timestamp
|
||||
timeLastClick = SystemClock.uptimeMillis();
|
||||
timeLastClick = event.getEventTime();
|
||||
// set item pressed and update
|
||||
setPressed(true);
|
||||
break;
|
||||
}
|
||||
// up event (revoke touch)
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_POINTER_UP: {
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
case MotionEvent.ACTION_UP: {
|
||||
setPressed(false);
|
||||
break;
|
||||
}
|
||||
@@ -335,7 +333,7 @@ public class AnalogStick extends VirtualControllerElement {
|
||||
|
||||
if (isPressed()) {
|
||||
// when is pressed calculate new positions (will trigger movement if necessary)
|
||||
updatePosition();
|
||||
updatePosition(event.getEventTime());
|
||||
} else {
|
||||
stick_state = STICK_STATE.NO_MOVEMENT;
|
||||
notifyOnRevoke();
|
||||
|
||||
+11
-38
@@ -14,8 +14,6 @@ import android.view.MotionEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
/**
|
||||
* This is a digital button on screen element. It is used to get click and double click user input.
|
||||
@@ -43,22 +41,16 @@ public class DigitalButton extends VirtualControllerElement {
|
||||
void onRelease();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private class TimerLongClickTimerTask extends TimerTask {
|
||||
@Override
|
||||
public void run() {
|
||||
onLongClickCallback();
|
||||
}
|
||||
}
|
||||
|
||||
private List<DigitalButtonListener> listeners = new ArrayList<>();
|
||||
private String text = "";
|
||||
private int icon = -1;
|
||||
private long timerLongClickTimeout = 3000;
|
||||
private Timer timerLongClick = null;
|
||||
private TimerLongClickTimerTask longClickTimerTask = null;
|
||||
private final Runnable longClickRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
onLongClickCallback();
|
||||
}
|
||||
};
|
||||
|
||||
private final Paint paint = new Paint();
|
||||
private final RectF rect = new RectF();
|
||||
@@ -177,18 +169,8 @@ public class DigitalButton extends VirtualControllerElement {
|
||||
listener.onClick();
|
||||
}
|
||||
|
||||
if (timerLongClick != null) {
|
||||
timerLongClick.cancel();
|
||||
timerLongClick = null;
|
||||
}
|
||||
if (longClickTimerTask != null) {
|
||||
longClickTimerTask.cancel();
|
||||
longClickTimerTask = null;
|
||||
}
|
||||
|
||||
timerLongClick = new Timer();
|
||||
longClickTimerTask = new TimerLongClickTimerTask();
|
||||
timerLongClick.schedule(longClickTimerTask, timerLongClickTimeout);
|
||||
virtualController.getHandler().removeCallbacks(longClickRunnable);
|
||||
virtualController.getHandler().postDelayed(longClickRunnable, timerLongClickTimeout);
|
||||
}
|
||||
|
||||
private void onLongClickCallback() {
|
||||
@@ -207,14 +189,7 @@ public class DigitalButton extends VirtualControllerElement {
|
||||
}
|
||||
|
||||
// We may be called for a release without a prior click
|
||||
if (timerLongClick != null) {
|
||||
timerLongClick.cancel();
|
||||
timerLongClick = null;
|
||||
}
|
||||
if (longClickTimerTask != null) {
|
||||
longClickTimerTask.cancel();
|
||||
longClickTimerTask = null;
|
||||
}
|
||||
virtualController.getHandler().removeCallbacks(longClickRunnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -225,8 +200,7 @@ public class DigitalButton extends VirtualControllerElement {
|
||||
int action = event.getActionMasked();
|
||||
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
case MotionEvent.ACTION_POINTER_DOWN: {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
movingButton = null;
|
||||
setPressed(true);
|
||||
onClickCallback();
|
||||
@@ -241,8 +215,7 @@ public class DigitalButton extends VirtualControllerElement {
|
||||
return true;
|
||||
}
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_POINTER_UP: {
|
||||
case MotionEvent.ACTION_UP: {
|
||||
setPressed(false);
|
||||
onReleaseCallback();
|
||||
|
||||
|
||||
@@ -162,7 +162,6 @@ public class DigitalPad extends VirtualControllerElement {
|
||||
// get masked (not specific to a pointer) action
|
||||
switch (event.getActionMasked()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
case MotionEvent.ACTION_POINTER_DOWN:
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
direction = 0;
|
||||
|
||||
@@ -184,8 +183,7 @@ public class DigitalPad extends VirtualControllerElement {
|
||||
return true;
|
||||
}
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_POINTER_UP: {
|
||||
case MotionEvent.ACTION_UP: {
|
||||
direction = 0;
|
||||
newDirectionCallback(direction);
|
||||
invalidate();
|
||||
|
||||
+52
-38
@@ -5,21 +5,20 @@
|
||||
package com.limelight.binding.input.virtual_controller;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
import com.limelight.R;
|
||||
import com.limelight.binding.input.ControllerHandler;
|
||||
import com.limelight.nvstream.NvConnection;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
public class VirtualController {
|
||||
public static class ControllerInputContext {
|
||||
@@ -40,13 +39,18 @@ public class VirtualController {
|
||||
|
||||
private static final boolean _PRINT_DEBUG_INFORMATION = false;
|
||||
|
||||
private ControllerHandler controllerHandler;
|
||||
private Context context = null;
|
||||
private final ControllerHandler controllerHandler;
|
||||
private final Context context;
|
||||
private final Handler handler;
|
||||
|
||||
private final Runnable delayedRetransmitRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
sendControllerInputContextInternal();
|
||||
}
|
||||
};
|
||||
|
||||
private FrameLayout frame_layout = null;
|
||||
private RelativeLayout relative_layout = null;
|
||||
|
||||
private Timer retransmitTimer;
|
||||
|
||||
ControllerMode currentMode = ControllerMode.Active;
|
||||
ControllerInputContext inputContext = new ControllerInputContext();
|
||||
@@ -59,10 +63,7 @@ public class VirtualController {
|
||||
this.controllerHandler = controllerHandler;
|
||||
this.frame_layout = layout;
|
||||
this.context = context;
|
||||
|
||||
relative_layout = new RelativeLayout(context);
|
||||
|
||||
frame_layout.addView(relative_layout);
|
||||
this.handler = new Handler(Looper.getMainLooper());
|
||||
|
||||
buttonConfigure = new Button(context);
|
||||
buttonConfigure.setAlpha(0.25f);
|
||||
@@ -87,7 +88,7 @@ public class VirtualController {
|
||||
|
||||
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
|
||||
|
||||
relative_layout.invalidate();
|
||||
buttonConfigure.invalidate();
|
||||
|
||||
for (VirtualControllerElement element : elements) {
|
||||
element.invalidate();
|
||||
@@ -97,32 +98,33 @@ public class VirtualController {
|
||||
|
||||
}
|
||||
|
||||
Handler getHandler() {
|
||||
return handler;
|
||||
}
|
||||
|
||||
public void hide() {
|
||||
retransmitTimer.cancel();
|
||||
relative_layout.setVisibility(View.INVISIBLE);
|
||||
for (VirtualControllerElement element : elements) {
|
||||
element.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
buttonConfigure.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
public void show() {
|
||||
relative_layout.setVisibility(View.VISIBLE);
|
||||
for (VirtualControllerElement element : elements) {
|
||||
element.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
// HACK: GFE sometimes discards gamepad packets when they are received
|
||||
// very shortly after another. This can be critical if an axis zeroing packet
|
||||
// is lost and causes an analog stick to get stuck. To avoid this, we send
|
||||
// a gamepad input packet every 100 ms to ensure any loss can be recovered.
|
||||
retransmitTimer = new Timer("OSC timer", true);
|
||||
retransmitTimer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
sendControllerInputContext();
|
||||
}
|
||||
}, 100, 100);
|
||||
buttonConfigure.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
public void removeElements() {
|
||||
for (VirtualControllerElement element : elements) {
|
||||
relative_layout.removeView(element);
|
||||
frame_layout.removeView(element);
|
||||
}
|
||||
elements.clear();
|
||||
|
||||
frame_layout.removeView(buttonConfigure);
|
||||
}
|
||||
|
||||
public void setOpacity(int opacity) {
|
||||
@@ -134,10 +136,10 @@ public class VirtualController {
|
||||
|
||||
public void addElement(VirtualControllerElement element, int x, int y, int width, int height) {
|
||||
elements.add(element);
|
||||
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(width, height);
|
||||
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(width, height);
|
||||
layoutParams.setMargins(x, y, 0, 0);
|
||||
|
||||
relative_layout.addView(element, layoutParams);
|
||||
frame_layout.addView(element, layoutParams);
|
||||
}
|
||||
|
||||
public List<VirtualControllerElement> getElements() {
|
||||
@@ -146,23 +148,20 @@ public class VirtualController {
|
||||
|
||||
private static final void _DBG(String text) {
|
||||
if (_PRINT_DEBUG_INFORMATION) {
|
||||
System.out.println("VirtualController: " + text);
|
||||
LimeLog.info("VirtualController: " + text);
|
||||
}
|
||||
}
|
||||
|
||||
public void refreshLayout() {
|
||||
relative_layout.removeAllViews();
|
||||
removeElements();
|
||||
|
||||
DisplayMetrics screen = context.getResources().getDisplayMetrics();
|
||||
|
||||
int buttonSize = (int)(screen.heightPixels*0.06f);
|
||||
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(buttonSize, buttonSize);
|
||||
params.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);
|
||||
params.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE);
|
||||
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(buttonSize, buttonSize);
|
||||
params.leftMargin = 15;
|
||||
params.topMargin = 15;
|
||||
relative_layout.addView(buttonConfigure, params);
|
||||
frame_layout.addView(buttonConfigure, params);
|
||||
|
||||
// Start with the default layout
|
||||
VirtualControllerConfigurationLoader.createDefaultLayout(this, context);
|
||||
@@ -179,7 +178,7 @@ public class VirtualController {
|
||||
return inputContext;
|
||||
}
|
||||
|
||||
void sendControllerInputContext() {
|
||||
private void sendControllerInputContextInternal() {
|
||||
_DBG("INPUT_MAP + " + inputContext.inputMap);
|
||||
_DBG("LEFT_TRIGGER " + inputContext.leftTrigger);
|
||||
_DBG("RIGHT_TRIGGER " + inputContext.rightTrigger);
|
||||
@@ -198,4 +197,19 @@ public class VirtualController {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void sendControllerInputContext() {
|
||||
// Cancel retransmissions of prior gamepad inputs
|
||||
handler.removeCallbacks(delayedRetransmitRunnable);
|
||||
|
||||
sendControllerInputContextInternal();
|
||||
|
||||
// HACK: GFE sometimes discards gamepad packets when they are received
|
||||
// very shortly after another. This can be critical if an axis zeroing packet
|
||||
// is lost and causes an analog stick to get stuck. To avoid this, we retransmit
|
||||
// the gamepad state a few times unless another input event happens before then.
|
||||
handler.postDelayed(delayedRetransmitRunnable, 25);
|
||||
handler.postDelayed(delayedRetransmitRunnable, 50);
|
||||
handler.postDelayed(delayedRetransmitRunnable, 75);
|
||||
}
|
||||
}
|
||||
|
||||
+17
-13
@@ -40,27 +40,31 @@ public class VirtualControllerConfigurationLoader {
|
||||
VirtualController.ControllerInputContext inputContext =
|
||||
controller.getControllerInputContext();
|
||||
|
||||
if (direction == DigitalPad.DIGITAL_PAD_DIRECTION_NO_DIRECTION) {
|
||||
inputContext.inputMap &= ~ControllerPacket.LEFT_FLAG;
|
||||
inputContext.inputMap &= ~ControllerPacket.RIGHT_FLAG;
|
||||
inputContext.inputMap &= ~ControllerPacket.UP_FLAG;
|
||||
inputContext.inputMap &= ~ControllerPacket.DOWN_FLAG;
|
||||
|
||||
controller.sendControllerInputContext();
|
||||
return;
|
||||
}
|
||||
if ((direction & DigitalPad.DIGITAL_PAD_DIRECTION_LEFT) > 0) {
|
||||
if ((direction & DigitalPad.DIGITAL_PAD_DIRECTION_LEFT) != 0) {
|
||||
inputContext.inputMap |= ControllerPacket.LEFT_FLAG;
|
||||
}
|
||||
if ((direction & DigitalPad.DIGITAL_PAD_DIRECTION_RIGHT) > 0) {
|
||||
else {
|
||||
inputContext.inputMap &= ~ControllerPacket.LEFT_FLAG;
|
||||
}
|
||||
if ((direction & DigitalPad.DIGITAL_PAD_DIRECTION_RIGHT) != 0) {
|
||||
inputContext.inputMap |= ControllerPacket.RIGHT_FLAG;
|
||||
}
|
||||
if ((direction & DigitalPad.DIGITAL_PAD_DIRECTION_UP) > 0) {
|
||||
else {
|
||||
inputContext.inputMap &= ~ControllerPacket.RIGHT_FLAG;
|
||||
}
|
||||
if ((direction & DigitalPad.DIGITAL_PAD_DIRECTION_UP) != 0) {
|
||||
inputContext.inputMap |= ControllerPacket.UP_FLAG;
|
||||
}
|
||||
if ((direction & DigitalPad.DIGITAL_PAD_DIRECTION_DOWN) > 0) {
|
||||
else {
|
||||
inputContext.inputMap &= ~ControllerPacket.UP_FLAG;
|
||||
}
|
||||
if ((direction & DigitalPad.DIGITAL_PAD_DIRECTION_DOWN) != 0) {
|
||||
inputContext.inputMap |= ControllerPacket.DOWN_FLAG;
|
||||
}
|
||||
else {
|
||||
inputContext.inputMap &= ~ControllerPacket.DOWN_FLAG;
|
||||
}
|
||||
|
||||
controller.sendControllerInputContext();
|
||||
}
|
||||
});
|
||||
|
||||
+16
-9
@@ -12,7 +12,7 @@ import android.graphics.Paint;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
@@ -72,7 +72,7 @@ public abstract class VirtualControllerElement extends View {
|
||||
int newPos_x = (int) getX() + x - pressed_x;
|
||||
int newPos_y = (int) getY() + y - pressed_y;
|
||||
|
||||
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
|
||||
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams();
|
||||
|
||||
layoutParams.leftMargin = newPos_x > 0 ? newPos_x : 0;
|
||||
layoutParams.topMargin = newPos_y > 0 ? newPos_y : 0;
|
||||
@@ -83,7 +83,7 @@ public abstract class VirtualControllerElement extends View {
|
||||
}
|
||||
|
||||
protected void resizeElement(int pressed_x, int pressed_y, int width, int height) {
|
||||
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
|
||||
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams();
|
||||
|
||||
int newHeight = height + (startSize_y - pressed_y);
|
||||
int newWidth = width + (startSize_x - pressed_x);
|
||||
@@ -223,13 +223,21 @@ public abstract class VirtualControllerElement extends View {
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
// Ignore secondary touches on controls
|
||||
//
|
||||
// NB: We can get an additional pointer down if the user touches a non-StreamView area
|
||||
// while also touching an OSC control, even if that pointer down doesn't correspond to
|
||||
// an area of the OSC control.
|
||||
if (event.getActionIndex() != 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (virtualController.getControllerMode() == VirtualController.ControllerMode.Active) {
|
||||
return onElementTouchEvent(event);
|
||||
}
|
||||
|
||||
switch (event.getActionMasked()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
case MotionEvent.ACTION_POINTER_DOWN: {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
position_pressed_x = event.getX();
|
||||
position_pressed_y = event.getY();
|
||||
startSize_x = getWidth();
|
||||
@@ -267,8 +275,7 @@ public abstract class VirtualControllerElement extends View {
|
||||
return true;
|
||||
}
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_POINTER_UP: {
|
||||
case MotionEvent.ACTION_UP: {
|
||||
actionCancel();
|
||||
return true;
|
||||
}
|
||||
@@ -316,7 +323,7 @@ public abstract class VirtualControllerElement extends View {
|
||||
public JSONObject getConfiguration() throws JSONException {
|
||||
JSONObject configuration = new JSONObject();
|
||||
|
||||
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
|
||||
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams();
|
||||
|
||||
configuration.put("LEFT", layoutParams.leftMargin);
|
||||
configuration.put("TOP", layoutParams.topMargin);
|
||||
@@ -327,7 +334,7 @@ public abstract class VirtualControllerElement extends View {
|
||||
}
|
||||
|
||||
public void loadConfiguration(JSONObject configuration) throws JSONException {
|
||||
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
|
||||
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams();
|
||||
|
||||
layoutParams.leftMargin = configuration.getInt("LEFT");
|
||||
layoutParams.topMargin = configuration.getInt("TOP");
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,6 +14,7 @@ import android.annotation.SuppressLint;
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ConfigurationInfo;
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaCodecInfo;
|
||||
import android.media.MediaCodecList;
|
||||
import android.media.MediaCodecInfo.CodecCapabilities;
|
||||
@@ -31,20 +32,21 @@ public class MediaCodecHelper {
|
||||
private static final List<String> blacklistedDecoderPrefixes;
|
||||
private static final List<String> spsFixupBitstreamFixupDecoderPrefixes;
|
||||
private static final List<String> blacklistedAdaptivePlaybackPrefixes;
|
||||
private static final List<String> deprioritizedHevcDecoders;
|
||||
private static final List<String> baselineProfileHackPrefixes;
|
||||
private static final List<String> directSubmitPrefixes;
|
||||
private static final List<String> constrainedHighProfilePrefixes;
|
||||
private static final List<String> whitelistedHevcDecoders;
|
||||
private static final List<String> refFrameInvalidationAvcPrefixes;
|
||||
private static final List<String> refFrameInvalidationHevcPrefixes;
|
||||
private static final List<String> blacklisted49FpsDecoderPrefixes;
|
||||
private static final List<String> blacklisted59FpsDecoderPrefixes;
|
||||
private static final List<String> useFourSlicesPrefixes;
|
||||
private static final List<String> qualcommDecoderPrefixes;
|
||||
private static final List<String> kirinDecoderPrefixes;
|
||||
private static final List<String> exynosDecoderPrefixes;
|
||||
private static final List<String> amlogicDecoderPrefixes;
|
||||
private static final List<String> knownVendorLowLatencyOptions;
|
||||
|
||||
public static final boolean IS_EMULATOR = Build.HARDWARE.equals("ranchu") || Build.HARDWARE.equals("cheets");
|
||||
public static final boolean SHOULD_BYPASS_SOFTWARE_BLOCK =
|
||||
Build.HARDWARE.equals("ranchu") || Build.HARDWARE.equals("cheets") || Build.BRAND.equals("Android-x86");
|
||||
|
||||
private static boolean isLowEndSnapdragon = false;
|
||||
private static boolean isAdreno620 = false;
|
||||
@@ -70,7 +72,10 @@ public class MediaCodecHelper {
|
||||
|
||||
static {
|
||||
refFrameInvalidationAvcPrefixes = new LinkedList<>();
|
||||
|
||||
refFrameInvalidationHevcPrefixes = new LinkedList<>();
|
||||
refFrameInvalidationHevcPrefixes.add("omx.exynos");
|
||||
refFrameInvalidationHevcPrefixes.add("c2.exynos");
|
||||
|
||||
// Qualcomm and NVIDIA may be added at runtime
|
||||
}
|
||||
@@ -82,18 +87,18 @@ public class MediaCodecHelper {
|
||||
static {
|
||||
blacklistedDecoderPrefixes = new LinkedList<>();
|
||||
|
||||
// Blacklist software decoders that don't support H264 high profile,
|
||||
// but exclude the official AOSP and CrOS emulator from this restriction.
|
||||
if (!IS_EMULATOR) {
|
||||
// Blacklist software decoders that don't support H264 high profile except on systems
|
||||
// that are expected to only have software decoders (like emulators).
|
||||
if (!SHOULD_BYPASS_SOFTWARE_BLOCK) {
|
||||
blacklistedDecoderPrefixes.add("omx.google");
|
||||
blacklistedDecoderPrefixes.add("AVCDecoder");
|
||||
}
|
||||
|
||||
// We want to avoid ffmpeg decoders since they're software decoders,
|
||||
// but on Android-x86 they might be all we have (and also relatively
|
||||
// performant on a modern x86 processor).
|
||||
if (!Build.BRAND.equals("Android-x86")) {
|
||||
blacklistedDecoderPrefixes.add("OMX.ffmpeg");
|
||||
// We want to avoid ffmpeg decoders since they're usually software decoders,
|
||||
// but we'll defer to the Android 10 isSoftwareOnly() API on newer devices
|
||||
// to determine if we should use these or not.
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
blacklistedDecoderPrefixes.add("OMX.ffmpeg");
|
||||
}
|
||||
}
|
||||
|
||||
// Force these decoders disabled because:
|
||||
@@ -119,7 +124,7 @@ public class MediaCodecHelper {
|
||||
// if adaptive playback was enabled so let's avoid it to be safe.
|
||||
blacklistedAdaptivePlaybackPrefixes.add("omx.intel");
|
||||
// The MediaTek decoder crashes at 1080p when adaptive playback is enabled
|
||||
// on some Android TV devices with H.265 only.
|
||||
// on some Android TV devices with HEVC only.
|
||||
blacklistedAdaptivePlaybackPrefixes.add("omx.mtk");
|
||||
|
||||
constrainedHighProfilePrefixes = new LinkedList<>();
|
||||
@@ -137,24 +142,26 @@ public class MediaCodecHelper {
|
||||
// Exynos seems to be the only HEVC decoder that works reliably
|
||||
whitelistedHevcDecoders.add("omx.exynos");
|
||||
|
||||
// On Darcy (Shield 2017), HEVC runs fine with no fixups required.
|
||||
// For some reason, other X1 implementations require bitstream fixups.
|
||||
if (Build.DEVICE.equalsIgnoreCase("darcy")) {
|
||||
// On Darcy (Shield 2017), HEVC runs fine with no fixups required. For some reason,
|
||||
// other X1 implementations require bitstream fixups. However, since numReferenceFrames
|
||||
// has been supported in GFE since late 2017, we'll go ahead and enable HEVC for all
|
||||
// device models.
|
||||
//
|
||||
// NVIDIA does partial HEVC acceleration on the Shield Tablet. I don't know
|
||||
// whether the performance is good enough to use for streaming, but they're
|
||||
// using the same omx.nvidia.h265.decode name as the Shield TV which has a
|
||||
// fully accelerated HEVC pipeline. AFAIK, the only K1 devices with this
|
||||
// partially accelerated HEVC decoder are the Shield Tablet and Xiaomi MiPad,
|
||||
// so I'll check for those here.
|
||||
//
|
||||
// In case there are some that I missed, I will also exclude pre-Oreo OSes since
|
||||
// only Shield ATV got an Oreo update and any newer Tegra devices will not ship
|
||||
// with an old OS like Nougat.
|
||||
if (!Build.DEVICE.equalsIgnoreCase("shieldtablet") &&
|
||||
!Build.DEVICE.equalsIgnoreCase("mocha") &&
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
whitelistedHevcDecoders.add("omx.nvidia");
|
||||
}
|
||||
else {
|
||||
// TODO: This needs a similar fixup to the Tegra 3 otherwise it buffers 16 frames
|
||||
}
|
||||
|
||||
// Older Sony ATVs (SVP-DTV15) have broken MediaTek codecs (decoder hangs after rendering the first frame).
|
||||
// I know the Fire TV 2 and 3 works, so I'll whitelist Amazon devices which seem to actually be tested.
|
||||
if (Build.MANUFACTURER.equalsIgnoreCase("Amazon")) {
|
||||
whitelistedHevcDecoders.add("omx.mtk");
|
||||
|
||||
// This broke at some point on the Fire TV 3 and now the decoder
|
||||
// never produces any output frames.
|
||||
//whitelistedHevcDecoders.add("omx.amlogic");
|
||||
}
|
||||
|
||||
// Plot twist: On newer Sony devices (BRAVIA_ATV2, BRAVIA_ATV3_4K, BRAVIA_UR1_4K) the H.264 decoder crashes
|
||||
// on several configurations (> 60 FPS and 1440p) that work with HEVC, so we'll whitelist those devices for HEVC.
|
||||
@@ -164,8 +171,17 @@ public class MediaCodecHelper {
|
||||
|
||||
// Amlogic requires 1 reference frame for HEVC to avoid hanging. Since it's been years
|
||||
// since GFE added support for maxNumReferenceFrames, we'll just enable all Amlogic SoCs
|
||||
// running Android 9 or later. HEVC is much lower latency than H.264 on Sabrina (S905X2).
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
// running Android 9 or later.
|
||||
//
|
||||
// NB: We don't do this on Sabrina (GCWGTV) because H.264 is lower latency when we use
|
||||
// vendor.low-latency.enable. We will still use HEVC if decoderCanMeetPerformancePointWithHevcAndNotAvc()
|
||||
// determines it's the only way to meet the performance requirements.
|
||||
//
|
||||
// With the Android 12 update, Sabrina now uses HEVC (with RFI) based upon FEATURE_LowLatency
|
||||
// support, which provides equivalent latency to H.264 now.
|
||||
//
|
||||
// FIXME: Should we do this for all Amlogic S905X SoCs?
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !Build.DEVICE.equalsIgnoreCase("sabrina")) {
|
||||
whitelistedHevcDecoders.add("omx.amlogic");
|
||||
}
|
||||
|
||||
@@ -187,29 +203,24 @@ public class MediaCodecHelper {
|
||||
}
|
||||
|
||||
static {
|
||||
deprioritizedHevcDecoders = new LinkedList<>();
|
||||
useFourSlicesPrefixes = new LinkedList<>();
|
||||
|
||||
// These are decoders that work but aren't used by default for various reasons.
|
||||
// Software decoders will use 4 slices per frame to allow for slice multithreading
|
||||
useFourSlicesPrefixes.add("omx.google");
|
||||
useFourSlicesPrefixes.add("AVCDecoder");
|
||||
useFourSlicesPrefixes.add("omx.ffmpeg");
|
||||
useFourSlicesPrefixes.add("c2.android");
|
||||
|
||||
// Qualcomm is currently the only decoders in this group.
|
||||
// Old Qualcomm decoders are detected at runtime
|
||||
}
|
||||
|
||||
static {
|
||||
blacklisted49FpsDecoderPrefixes = new LinkedList<>();
|
||||
blacklisted59FpsDecoderPrefixes = new LinkedList<>();
|
||||
knownVendorLowLatencyOptions = new LinkedList<>();
|
||||
|
||||
// We see a bunch of crashes on MediaTek Android TVs running
|
||||
// at 49 FPS (PAL 50 Hz - 1). Blacklist this frame rate for
|
||||
// these devices and hope they fix it in Pie.
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
|
||||
blacklisted49FpsDecoderPrefixes.add("omx.mtk");
|
||||
|
||||
// 59 FPS also seems to crash on the Sony Bravia TV ATV3 model.
|
||||
// Blacklist that frame rate on these devices too.
|
||||
if (Build.DEVICE.startsWith("BRAVIA_ATV3")) {
|
||||
blacklisted59FpsDecoderPrefixes.add("omx.mtk");
|
||||
}
|
||||
}
|
||||
knownVendorLowLatencyOptions.add("vendor.qti-ext-dec-low-latency.enable");
|
||||
knownVendorLowLatencyOptions.add("vendor.hisi-ext-low-latency-video-dec.video-scene-for-low-latency-req");
|
||||
knownVendorLowLatencyOptions.add("vendor.rtc-ext-dec-low-latency.enable");
|
||||
knownVendorLowLatencyOptions.add("vendor.low-latency.enable");
|
||||
}
|
||||
|
||||
static {
|
||||
@@ -223,12 +234,21 @@ public class MediaCodecHelper {
|
||||
kirinDecoderPrefixes = new LinkedList<>();
|
||||
|
||||
kirinDecoderPrefixes.add("omx.hisi");
|
||||
kirinDecoderPrefixes.add("c2.hisi"); // Unconfirmed
|
||||
}
|
||||
|
||||
static {
|
||||
exynosDecoderPrefixes = new LinkedList<>();
|
||||
|
||||
exynosDecoderPrefixes.add("omx.exynos");
|
||||
exynosDecoderPrefixes.add("c2.exynos");
|
||||
}
|
||||
|
||||
static {
|
||||
amlogicDecoderPrefixes = new LinkedList<>();
|
||||
|
||||
amlogicDecoderPrefixes.add("omx.amlogic");
|
||||
amlogicDecoderPrefixes.add("c2.amlogic"); // Unconfirmed
|
||||
}
|
||||
|
||||
private static boolean isPowerVR(String glRenderer) {
|
||||
@@ -289,6 +309,40 @@ public class MediaCodecHelper {
|
||||
return;
|
||||
}
|
||||
|
||||
// Older Sony ATVs (SVP-DTV15) have broken MediaTek codecs (decoder hangs after rendering the first frame).
|
||||
// I know the Fire TV 2 and 3 works, so I'll whitelist Amazon devices which seem to actually be tested.
|
||||
// We still have to check Build.MANUFACTURER to catch Amazon Fire tablets.
|
||||
if (context.getPackageManager().hasSystemFeature("amazon.hardware.fire_tv") ||
|
||||
Build.MANUFACTURER.equalsIgnoreCase("Amazon")) {
|
||||
// HEVC and RFI have been confirmed working on Fire TV 2, Fire TV Stick 2, Fire TV 4K Max,
|
||||
// Fire HD 8 2020, and Fire HD 8 2022 models.
|
||||
//
|
||||
// This is probably a good enough sample to conclude that all MediaTek Fire OS devices
|
||||
// are likely to be okay.
|
||||
whitelistedHevcDecoders.add("omx.mtk");
|
||||
refFrameInvalidationHevcPrefixes.add("omx.mtk");
|
||||
refFrameInvalidationHevcPrefixes.add("c2.mtk");
|
||||
|
||||
// This requires setting vdec-lowlatency on the Fire TV 3, otherwise the decoder
|
||||
// never produces any output frames. See comment above for details on why we only
|
||||
// do this for Fire TV devices.
|
||||
whitelistedHevcDecoders.add("omx.amlogic");
|
||||
|
||||
// Fire TV 3 seems to produce random artifacts on HEVC streams after packet loss.
|
||||
// Enabling RFI turns these artifacts into full decoder output hangs, so let's not enable
|
||||
// that for Fire OS 6 Amlogic devices. We will leave HEVC enabled because that's the only
|
||||
// way these devices can hit 4K. Hopefully this is just a problem with the BSP used in
|
||||
// the Fire OS 6 Amlogic devices, so we will leave this enabled for Fire OS 7+.
|
||||
//
|
||||
// Apart from a few TV models, the main Amlogic-based Fire TV devices are the Fire TV
|
||||
// Cubes and Fire TV 3. This check will exclude the Fire TV 3 and Fire TV Cube 1, but
|
||||
// allow the newer Fire TV Cubes to use HEVC RFI.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
refFrameInvalidationHevcPrefixes.add("omx.amlogic");
|
||||
refFrameInvalidationHevcPrefixes.add("c2.amlogic");
|
||||
}
|
||||
}
|
||||
|
||||
ActivityManager activityManager =
|
||||
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
ConfigurationInfo configInfo = activityManager.getDeviceConfigurationInfo();
|
||||
@@ -300,19 +354,17 @@ public class MediaCodecHelper {
|
||||
|
||||
// Tegra K1 and later can do reference frame invalidation properly
|
||||
if (configInfo.reqGlEsVersion >= 0x30000) {
|
||||
LimeLog.info("Added omx.nvidia to AVC reference frame invalidation support list");
|
||||
LimeLog.info("Added omx.nvidia/c2.nvidia to reference frame invalidation support list");
|
||||
refFrameInvalidationAvcPrefixes.add("omx.nvidia");
|
||||
refFrameInvalidationHevcPrefixes.add("omx.nvidia");
|
||||
refFrameInvalidationAvcPrefixes.add("c2.nvidia"); // Unconfirmed
|
||||
refFrameInvalidationHevcPrefixes.add("c2.nvidia"); // Unconfirmed
|
||||
|
||||
LimeLog.info("Added omx.qcom/c2.qti to AVC reference frame invalidation support list");
|
||||
LimeLog.info("Added omx.qcom/c2.qti to reference frame invalidation support list");
|
||||
refFrameInvalidationAvcPrefixes.add("omx.qcom");
|
||||
refFrameInvalidationHevcPrefixes.add("omx.qcom");
|
||||
refFrameInvalidationAvcPrefixes.add("c2.qti");
|
||||
|
||||
// Prior to M, we were tricking the decoder into using baseline profile, which
|
||||
// won't support RFI properly.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
LimeLog.info("Added omx.intel to AVC reference frame invalidation support list");
|
||||
refFrameInvalidationAvcPrefixes.add("omx.intel");
|
||||
}
|
||||
refFrameInvalidationHevcPrefixes.add("c2.qti");
|
||||
}
|
||||
|
||||
// Qualcomm's early HEVC decoders break hard on our HEVC stream. The best check to
|
||||
@@ -324,16 +376,15 @@ public class MediaCodecHelper {
|
||||
// (see comment on isGLES31SnapdragonRenderer).
|
||||
//
|
||||
if (isGLES31SnapdragonRenderer(glRenderer)) {
|
||||
// We prefer reference frame invalidation support (which is only doable on AVC on
|
||||
// older Qualcomm chips) vs. enabling HEVC by default. The user can override using the settings
|
||||
// to force HEVC on. If HDR or mobile data will be used, we'll override this and use
|
||||
// HEVC anyway.
|
||||
LimeLog.info("Added omx.qcom/c2.qti to deprioritized HEVC decoders based on GLES 3.1+ support");
|
||||
deprioritizedHevcDecoders.add("omx.qcom");
|
||||
deprioritizedHevcDecoders.add("c2.qti");
|
||||
LimeLog.info("Added omx.qcom/c2.qti to HEVC decoders based on GLES 3.1+ support");
|
||||
whitelistedHevcDecoders.add("omx.qcom");
|
||||
whitelistedHevcDecoders.add("c2.qti");
|
||||
}
|
||||
else {
|
||||
blacklistedDecoderPrefixes.add("OMX.qcom.video.decoder.hevc");
|
||||
|
||||
// These older decoders need 4 slices per frame for best performance
|
||||
useFourSlicesPrefixes.add("omx.qcom");
|
||||
}
|
||||
|
||||
// Older MediaTek SoCs have issues with HEVC rendering but the newer chips with
|
||||
@@ -347,8 +398,9 @@ public class MediaCodecHelper {
|
||||
// decoder hangs on the newer GE8100, GE8300, and GE8320 GPUs, so we limit it to the
|
||||
// Series6XT GPUs where we know it works.
|
||||
if (glRenderer.contains("GX6")) {
|
||||
LimeLog.info("Added omx.mtk to RFI list for HEVC");
|
||||
LimeLog.info("Added omx.mtk/c2.mtk to RFI list for HEVC");
|
||||
refFrameInvalidationHevcPrefixes.add("omx.mtk");
|
||||
refFrameInvalidationHevcPrefixes.add("c2.mtk");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -373,10 +425,6 @@ public class MediaCodecHelper {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static long getMonotonicMillis() {
|
||||
return System.nanoTime() / 1000000L;
|
||||
}
|
||||
|
||||
private static boolean decoderSupportsAndroidRLowLatency(MediaCodecInfo decoderInfo, String mimeType) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
try {
|
||||
@@ -393,6 +441,35 @@ public class MediaCodecHelper {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean decoderSupportsKnownVendorLowLatencyOption(String decoderName) {
|
||||
// It's only possible to probe vendor parameters on Android 12 and above.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
MediaCodec testCodec = null;
|
||||
try {
|
||||
// Unfortunately we have to create an actual codec instance to get supported options.
|
||||
testCodec = MediaCodec.createByCodecName(decoderName);
|
||||
|
||||
// See if any of the vendor parameters match ones we know about
|
||||
for (String supportedOption : testCodec.getSupportedVendorParameters()) {
|
||||
for (String knownLowLatencyOption : knownVendorLowLatencyOptions) {
|
||||
if (supportedOption.equalsIgnoreCase(knownLowLatencyOption)) {
|
||||
LimeLog.info(decoderName + " supports known low latency option: " + supportedOption);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Tolerate buggy codecs
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (testCodec != null) {
|
||||
testCodec.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean decoderSupportsMaxOperatingRate(String decoderName) {
|
||||
// Operate at maximum rate to lower latency as much as possible on
|
||||
// some Qualcomm platforms. We could also set KEY_PRIORITY to 0 (realtime)
|
||||
@@ -408,41 +485,119 @@ public class MediaCodecHelper {
|
||||
!isAdreno620;
|
||||
}
|
||||
|
||||
public static void setDecoderLowLatencyOptions(MediaFormat videoFormat, MediaCodecInfo decoderInfo, String mimeType) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && decoderSupportsAndroidRLowLatency(decoderInfo, mimeType)) {
|
||||
videoFormat.setInteger(MediaFormat.KEY_LOW_LATENCY, 1);
|
||||
public static boolean setDecoderLowLatencyOptions(MediaFormat videoFormat, MediaCodecInfo decoderInfo, int tryNumber) {
|
||||
// Options here should be tried in the order of most to least risky. The decoder will use
|
||||
// the first MediaFormat that doesn't fail in configure().
|
||||
|
||||
boolean setNewOption = false;
|
||||
|
||||
if (tryNumber < 1) {
|
||||
// Official Android 11+ low latency option (KEY_LOW_LATENCY).
|
||||
videoFormat.setInteger("low-latency", 1);
|
||||
setNewOption = true;
|
||||
|
||||
// If this decoder officially supports FEATURE_LowLatency, we will just use that alone
|
||||
// for try 0. Otherwise, we'll include it as best effort with other options.
|
||||
if (decoderSupportsAndroidRLowLatency(decoderInfo, videoFormat.getString(MediaFormat.KEY_MIME))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
// MediaCodec supports vendor-defined format keys using the "vendor.<extension name>.<parameter name>" syntax.
|
||||
// These allow access to functionality that is not exposed through documented MediaFormat.KEY_* values.
|
||||
// https://cs.android.com/android/platform/superproject/+/master:hardware/qcom/sdm845/media/mm-video-v4l2/vidc/common/inc/vidc_vendor_extensions.h;l=67
|
||||
|
||||
if (tryNumber < 2 &&
|
||||
(!Build.MANUFACTURER.equalsIgnoreCase("xiaomi") || Build.VERSION.SDK_INT > Build.VERSION_CODES.M)) {
|
||||
// MediaTek decoders don't use vendor-defined keys for low latency mode. Instead, they have a modified
|
||||
// version of AOSP's ACodec.cpp which supports the "vdec-lowlatency" option. This option is passed down
|
||||
// to the decoder as OMX.MTK.index.param.video.LowLatencyDecode.
|
||||
//
|
||||
// MediaCodec vendor extension support was introduced in Android 8.0:
|
||||
// https://cs.android.com/android/_/android/platform/frameworks/av/+/01c10f8cdcd58d1e7025f426a72e6e75ba5d7fc2
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
// Try vendor-specific low latency options
|
||||
if (isDecoderInList(qualcommDecoderPrefixes, decoderInfo.getName())) {
|
||||
// Examples of Qualcomm's vendor extensions for Snapdragon 845:
|
||||
// https://cs.android.com/android/platform/superproject/+/master:hardware/qcom/sdm845/media/mm-video-v4l2/vidc/vdec/src/omx_vdec_extensions.hpp
|
||||
// https://cs.android.com/android/_/android/platform/hardware/qcom/sm8150/media/+/0621ceb1c1b19564999db8293574a0e12952ff6c
|
||||
videoFormat.setInteger("vendor.qti-ext-dec-low-latency.enable", 1);
|
||||
// This option is also plumbed for Amazon Amlogic-based devices like the Fire TV 3. Not only does it
|
||||
// reduce latency on Amlogic, it fixes the HEVC bug that causes the decoder to not output any frames.
|
||||
// Unfortunately, it does the exact opposite for the Xiaomi MITV4-ANSM0, breaking it in the way that
|
||||
// Fire TV was broken prior to vdec-lowlatency :(
|
||||
//
|
||||
// On Fire TV 3, vdec-lowlatency is translated to OMX.amazon.fireos.index.video.lowLatencyDecode.
|
||||
//
|
||||
// https://github.com/yuan1617/Framwork/blob/master/frameworks/av/media/libstagefright/ACodec.cpp
|
||||
// https://github.com/iykex/vendor_mediatek_proprietary_hardware/blob/master/libomx/video/MtkOmxVdecEx/MtkOmxVdecEx.h
|
||||
videoFormat.setInteger("vdec-lowlatency", 1);
|
||||
setNewOption = true;
|
||||
}
|
||||
|
||||
// MediaCodec supports vendor-defined format keys using the "vendor.<extension name>.<parameter name>" syntax.
|
||||
// These allow access to functionality that is not exposed through documented MediaFormat.KEY_* values.
|
||||
// https://cs.android.com/android/platform/superproject/+/master:hardware/qcom/sdm845/media/mm-video-v4l2/vidc/common/inc/vidc_vendor_extensions.h;l=67
|
||||
//
|
||||
// MediaCodec vendor extension support was introduced in Android 8.0:
|
||||
// https://cs.android.com/android/_/android/platform/frameworks/av/+/01c10f8cdcd58d1e7025f426a72e6e75ba5d7fc2
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
// Try vendor-specific low latency options
|
||||
//
|
||||
// NOTE: Update knownVendorLowLatencyOptions if you modify this code!
|
||||
if (isDecoderInList(qualcommDecoderPrefixes, decoderInfo.getName())) {
|
||||
// Examples of Qualcomm's vendor extensions for Snapdragon 845:
|
||||
// https://cs.android.com/android/platform/superproject/+/master:hardware/qcom/sdm845/media/mm-video-v4l2/vidc/vdec/src/omx_vdec_extensions.hpp
|
||||
// https://cs.android.com/android/_/android/platform/hardware/qcom/sm8150/media/+/0621ceb1c1b19564999db8293574a0e12952ff6c
|
||||
//
|
||||
// We will first try both, then try vendor.qti-ext-dec-low-latency.enable alone if that fails
|
||||
if (tryNumber < 3) {
|
||||
videoFormat.setInteger("vendor.qti-ext-dec-picture-order.enable", 1);
|
||||
setNewOption = true;
|
||||
}
|
||||
else if (isDecoderInList(kirinDecoderPrefixes, decoderInfo.getName())) {
|
||||
if (tryNumber < 4) {
|
||||
videoFormat.setInteger("vendor.qti-ext-dec-low-latency.enable", 1);
|
||||
setNewOption = true;
|
||||
}
|
||||
}
|
||||
else if (isDecoderInList(kirinDecoderPrefixes, decoderInfo.getName())) {
|
||||
if (tryNumber < 3) {
|
||||
// Kirin low latency options
|
||||
// https://developer.huawei.com/consumer/cn/forum/topic/0202325564295980115
|
||||
videoFormat.setInteger("vendor.hisi-ext-low-latency-video-dec.video-scene-for-low-latency-req", 1);
|
||||
videoFormat.setInteger("vendor.hisi-ext-low-latency-video-dec.video-scene-for-low-latency-rdy", -1);
|
||||
setNewOption = true;
|
||||
}
|
||||
else if (isDecoderInList(exynosDecoderPrefixes, decoderInfo.getName())) {
|
||||
}
|
||||
else if (isDecoderInList(exynosDecoderPrefixes, decoderInfo.getName())) {
|
||||
if (tryNumber < 3) {
|
||||
// Exynos low latency option for H.264 decoder
|
||||
videoFormat.setInteger("vendor.rtc-ext-dec-low-latency.enable", 1);
|
||||
setNewOption = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (MediaCodecHelper.decoderSupportsMaxOperatingRate(decoderInfo.getName())) {
|
||||
videoFormat.setInteger(MediaFormat.KEY_OPERATING_RATE, Short.MAX_VALUE);
|
||||
else if (isDecoderInList(amlogicDecoderPrefixes, decoderInfo.getName())) {
|
||||
if (tryNumber < 3) {
|
||||
// Amlogic low latency vendor extension
|
||||
// https://github.com/codewalkerster/android_vendor_amlogic_common_prebuilt_libstagefrighthw/commit/41fefc4e035c476d58491324a5fe7666bfc2989e
|
||||
videoFormat.setInteger("vendor.low-latency.enable", 1);
|
||||
setNewOption = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: We should probably integrate this into the try system
|
||||
if (MediaCodecHelper.decoderSupportsMaxOperatingRate(decoderInfo.getName())) {
|
||||
videoFormat.setInteger(MediaFormat.KEY_OPERATING_RATE, Short.MAX_VALUE);
|
||||
}
|
||||
|
||||
return setNewOption;
|
||||
}
|
||||
|
||||
public static boolean decoderSupportsFusedIdrFrame(MediaCodecInfo decoderInfo, String mimeType) {
|
||||
// If adaptive playback is supported, we can submit new CSD together with a keyframe
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
try {
|
||||
if (decoderInfo.getCapabilitiesForType(mimeType).
|
||||
isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback))
|
||||
{
|
||||
LimeLog.info("Decoder supports fused IDR frames (FEATURE_AdaptivePlayback)");
|
||||
return true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Tolerate buggy codecs
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean decoderSupportsAdaptivePlayback(MediaCodecInfo decoderInfo, String mimeType) {
|
||||
@@ -486,15 +641,14 @@ public class MediaCodecHelper {
|
||||
return isDecoderInList(baselineProfileHackPrefixes, decoderName);
|
||||
}
|
||||
|
||||
public static boolean decoderBlacklistedForFrameRate(String decoderName, int fps) {
|
||||
if (fps == 49) {
|
||||
return isDecoderInList(blacklisted49FpsDecoderPrefixes, decoderName);
|
||||
}
|
||||
else if (fps == 59) {
|
||||
return isDecoderInList(blacklisted59FpsDecoderPrefixes, decoderName);
|
||||
public static byte getDecoderOptimalSlicesPerFrame(String decoderName) {
|
||||
if (isDecoderInList(useFourSlicesPrefixes, decoderName)) {
|
||||
// 4 slices per frame reduces decoding latency on older Qualcomm devices
|
||||
return 4;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
// 1 slice per frame produces the optimal encoding efficiency
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -513,25 +667,24 @@ public class MediaCodecHelper {
|
||||
return isDecoderInList(refFrameInvalidationAvcPrefixes, decoderName);
|
||||
}
|
||||
|
||||
public static boolean decoderSupportsRefFrameInvalidationHevc(String decoderName) {
|
||||
return isDecoderInList(refFrameInvalidationHevcPrefixes, decoderName);
|
||||
public static boolean decoderSupportsRefFrameInvalidationHevc(MediaCodecInfo decoderInfo) {
|
||||
// HEVC decoders seem to universally support RFI, but it can have huge latency penalties
|
||||
// for some decoders due to the number of references frames being > 1. Old Amlogic
|
||||
// decoders are known to have this problem.
|
||||
//
|
||||
// If the decoder supports FEATURE_LowLatency or any vendor low latency option,
|
||||
// we will use that as an indication that it can handle HEVC RFI without excessively
|
||||
// buffering frames.
|
||||
if (decoderSupportsAndroidRLowLatency(decoderInfo, "video/hevc") ||
|
||||
decoderSupportsKnownVendorLowLatencyOption(decoderInfo.getName())) {
|
||||
LimeLog.info("Enabling HEVC RFI based on low latency option support");
|
||||
return true;
|
||||
}
|
||||
|
||||
return isDecoderInList(refFrameInvalidationHevcPrefixes, decoderInfo.getName());
|
||||
}
|
||||
|
||||
public static boolean decoderIsWhitelistedForHevc(String decoderName, boolean meteredData, PreferenceConfiguration prefs) {
|
||||
// TODO: Shield Tablet K1/LTE?
|
||||
//
|
||||
// NVIDIA does partial HEVC acceleration on the Shield Tablet. I don't know
|
||||
// whether the performance is good enough to use for streaming, but they're
|
||||
// using the same omx.nvidia.h265.decode name as the Shield TV which has a
|
||||
// fully accelerated HEVC pipeline. AFAIK, the only K1 device with this
|
||||
// partially accelerated HEVC decoder is the Shield Tablet, so I'll
|
||||
// check for it here.
|
||||
//
|
||||
// TODO: Temporarily disabled with NVIDIA HEVC support
|
||||
/*if (Build.DEVICE.equalsIgnoreCase("shieldtablet")) {
|
||||
return false;
|
||||
}*/
|
||||
|
||||
public static boolean decoderIsWhitelistedForHevc(MediaCodecInfo decoderInfo) {
|
||||
// Google didn't have official support for HEVC (or more importantly, a CTS test) until
|
||||
// Lollipop. I've seen some MediaTek devices on 4.4 crash when attempting to use HEVC,
|
||||
// so I'm restricting HEVC usage to Lollipop and higher.
|
||||
@@ -545,26 +698,36 @@ public class MediaCodecHelper {
|
||||
// OMX.qcom.video.decoder.hevcswvdec
|
||||
// OMX.SEC.hevc.sw.dec
|
||||
//
|
||||
if (decoderName.contains("sw")) {
|
||||
if (decoderInfo.getName().contains("sw")) {
|
||||
LimeLog.info("Disallowing HEVC on software decoder: " + decoderInfo.getName());
|
||||
return false;
|
||||
}
|
||||
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && (!decoderInfo.isHardwareAccelerated() || decoderInfo.isSoftwareOnly())) {
|
||||
LimeLog.info("Disallowing HEVC on software decoder: " + decoderInfo.getName());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Some devices have HEVC decoders that we prefer not to use
|
||||
// typically because it can't support reference frame invalidation.
|
||||
// However, we will use it for HDR and for streaming over mobile networks
|
||||
// since it works fine otherwise. We will also use it for 4K because RFI
|
||||
// is currently disabled due to issues with video corruption.
|
||||
if (isDecoderInList(deprioritizedHevcDecoders, decoderName)) {
|
||||
if (meteredData || (prefs.width == 3840 && prefs.height == 2160)) {
|
||||
LimeLog.info("Selected deprioritized decoder");
|
||||
// If this device is media performance class 12 or higher, we will assume any hardware
|
||||
// HEVC decoder present is fast and modern enough for streaming.
|
||||
//
|
||||
// [5.3/H-1-1] MUST NOT drop more than 2 frames in 10 seconds (i.e less than 0.333 percent frame drop) for a 1080p 60 fps video session under load.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
LimeLog.info("Media performance class: " + Build.VERSION.MEDIA_PERFORMANCE_CLASS);
|
||||
if (Build.VERSION.MEDIA_PERFORMANCE_CLASS >= Build.VERSION_CODES.S) {
|
||||
LimeLog.info("Allowing HEVC based on media performance class");
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return isDecoderInList(whitelistedHevcDecoders, decoderName);
|
||||
// If the decoder supports FEATURE_LowLatency, we will assume it is fast and modern enough
|
||||
// to be preferable for streaming over H.264 decoders.
|
||||
if (decoderSupportsAndroidRLowLatency(decoderInfo, "video/hevc")) {
|
||||
LimeLog.info("Allowing HEVC based on FEATURE_LowLatency support");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise, we use our list of known working HEVC decoders
|
||||
return isDecoderInList(whitelistedHevcDecoders, decoderInfo.getName());
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@@ -637,7 +800,7 @@ public class MediaCodecHelper {
|
||||
private static boolean isCodecBlacklisted(MediaCodecInfo codecInfo) {
|
||||
// Use the new isSoftwareOnly() function on Android Q
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
if (!IS_EMULATOR && codecInfo.isSoftwareOnly()) {
|
||||
if (!SHOULD_BYPASS_SOFTWARE_BLOCK && codecInfo.isSoftwareOnly()) {
|
||||
LimeLog.info("Skipping software-only decoder: "+codecInfo.getName());
|
||||
return true;
|
||||
}
|
||||
@@ -768,8 +931,7 @@ public class MediaCodecHelper {
|
||||
|
||||
public static String readCpuinfo() throws Exception {
|
||||
StringBuilder cpuInfo = new StringBuilder();
|
||||
BufferedReader br = new BufferedReader(new FileReader(new File("/proc/cpuinfo")));
|
||||
try {
|
||||
try (final BufferedReader br = new BufferedReader(new FileReader(new File("/proc/cpuinfo")))) {
|
||||
for (;;) {
|
||||
int ch = br.read();
|
||||
if (ch == -1)
|
||||
@@ -778,8 +940,6 @@ public class MediaCodecHelper {
|
||||
}
|
||||
|
||||
return cpuInfo.toString();
|
||||
} finally {
|
||||
br.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import com.limelight.nvstream.http.ComputerDetails;
|
||||
import com.limelight.nvstream.http.NvHTTP;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
@@ -27,6 +28,7 @@ public class ComputerDatabaseManager {
|
||||
private static final String SERVER_CERT_COLUMN_NAME = "ServerCert";
|
||||
|
||||
private static final char ADDRESS_DELIMITER = ';';
|
||||
private static final char PORT_DELIMITER = '_';
|
||||
|
||||
private SQLiteDatabase computerDb;
|
||||
|
||||
@@ -74,10 +76,10 @@ public class ComputerDatabaseManager {
|
||||
values.put(COMPUTER_NAME_COLUMN_NAME, details.name);
|
||||
|
||||
StringBuilder addresses = new StringBuilder();
|
||||
addresses.append(details.localAddress != null ? details.localAddress : "");
|
||||
addresses.append(ADDRESS_DELIMITER).append(details.remoteAddress != null ? details.remoteAddress : "");
|
||||
addresses.append(ADDRESS_DELIMITER).append(details.manualAddress != null ? details.manualAddress : "");
|
||||
addresses.append(ADDRESS_DELIMITER).append(details.ipv6Address != null ? details.ipv6Address : "");
|
||||
addresses.append(details.localAddress != null ? splitTupleToAddress(details.localAddress) : "");
|
||||
addresses.append(ADDRESS_DELIMITER).append(details.remoteAddress != null ? splitTupleToAddress(details.remoteAddress) : "");
|
||||
addresses.append(ADDRESS_DELIMITER).append(details.manualAddress != null ? splitTupleToAddress(details.manualAddress) : "");
|
||||
addresses.append(ADDRESS_DELIMITER).append(details.ipv6Address != null ? splitTupleToAddress(details.ipv6Address) : "");
|
||||
|
||||
values.put(ADDRESSES_COLUMN_NAME, addresses.toString());
|
||||
values.put(MAC_ADDRESS_COLUMN_NAME, details.macAddress);
|
||||
@@ -103,6 +105,24 @@ public class ComputerDatabaseManager {
|
||||
return input;
|
||||
}
|
||||
|
||||
private static ComputerDetails.AddressTuple splitAddressToTuple(String input) {
|
||||
if (input == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String[] parts = input.split(""+PORT_DELIMITER, -1);
|
||||
if (parts.length == 1) {
|
||||
return new ComputerDetails.AddressTuple(parts[0], NvHTTP.DEFAULT_HTTP_PORT);
|
||||
}
|
||||
else {
|
||||
return new ComputerDetails.AddressTuple(parts[0], Integer.parseInt(parts[1]));
|
||||
}
|
||||
}
|
||||
|
||||
private static String splitTupleToAddress(ComputerDetails.AddressTuple tuple) {
|
||||
return tuple.address+PORT_DELIMITER+tuple.port;
|
||||
}
|
||||
|
||||
private ComputerDetails getComputerFromCursor(Cursor c) {
|
||||
ComputerDetails details = new ComputerDetails();
|
||||
|
||||
@@ -111,10 +131,18 @@ public class ComputerDatabaseManager {
|
||||
|
||||
String[] addresses = c.getString(2).split(""+ADDRESS_DELIMITER, -1);
|
||||
|
||||
details.localAddress = readNonEmptyString(addresses[0]);
|
||||
details.remoteAddress = readNonEmptyString(addresses[1]);
|
||||
details.manualAddress = readNonEmptyString(addresses[2]);
|
||||
details.ipv6Address = readNonEmptyString(addresses[3]);
|
||||
details.localAddress = splitAddressToTuple(readNonEmptyString(addresses[0]));
|
||||
details.remoteAddress = splitAddressToTuple(readNonEmptyString(addresses[1]));
|
||||
details.manualAddress = splitAddressToTuple(readNonEmptyString(addresses[2]));
|
||||
details.ipv6Address = splitAddressToTuple(readNonEmptyString(addresses[3]));
|
||||
|
||||
// External port is persisted in the remote address field
|
||||
if (details.remoteAddress != null) {
|
||||
details.externalPort = details.remoteAddress.port;
|
||||
}
|
||||
else {
|
||||
details.externalPort = NvHTTP.DEFAULT_HTTP_PORT;
|
||||
}
|
||||
|
||||
details.macAddress = c.getString(3);
|
||||
|
||||
@@ -136,28 +164,26 @@ public class ComputerDatabaseManager {
|
||||
}
|
||||
|
||||
public List<ComputerDetails> getAllComputers() {
|
||||
Cursor c = computerDb.rawQuery("SELECT * FROM "+COMPUTER_TABLE_NAME, null);
|
||||
LinkedList<ComputerDetails> computerList = new LinkedList<>();
|
||||
while (c.moveToNext()) {
|
||||
computerList.add(getComputerFromCursor(c));
|
||||
try (final Cursor c = computerDb.rawQuery("SELECT * FROM "+COMPUTER_TABLE_NAME, null)) {
|
||||
LinkedList<ComputerDetails> computerList = new LinkedList<>();
|
||||
while (c.moveToNext()) {
|
||||
computerList.add(getComputerFromCursor(c));
|
||||
}
|
||||
return computerList;
|
||||
}
|
||||
|
||||
c.close();
|
||||
|
||||
return computerList;
|
||||
}
|
||||
|
||||
public ComputerDetails getComputerByUUID(String uuid) {
|
||||
Cursor c = computerDb.query(COMPUTER_TABLE_NAME, null, COMPUTER_UUID_COLUMN_NAME+"=?", new String[]{ uuid }, null, null, null);
|
||||
if (!c.moveToFirst()) {
|
||||
// No matching computer
|
||||
c.close();
|
||||
return null;
|
||||
try (final Cursor c = computerDb.query(
|
||||
COMPUTER_TABLE_NAME, null, COMPUTER_UUID_COLUMN_NAME+"=?",
|
||||
new String[]{ uuid }, null, null, null)
|
||||
) {
|
||||
if (!c.moveToFirst()) {
|
||||
// No matching computer
|
||||
return null;
|
||||
}
|
||||
|
||||
return getComputerFromCursor(c);
|
||||
}
|
||||
|
||||
ComputerDetails details = getComputerFromCursor(c);
|
||||
c.close();
|
||||
|
||||
return details;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@ import java.io.OutputStream;
|
||||
import java.io.StringReader;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
@@ -49,8 +47,7 @@ public class ComputerManagerService extends Service {
|
||||
private static final int APPLIST_POLLING_PERIOD_MS = 30000;
|
||||
private static final int APPLIST_FAILED_POLLING_RETRY_MS = 2000;
|
||||
private static final int MDNS_QUERY_PERIOD_MS = 1000;
|
||||
private static final int FAST_POLL_TIMEOUT = 1000;
|
||||
private static final int OFFLINE_POLL_TRIES = 5;
|
||||
private static final int OFFLINE_POLL_TRIES = 3;
|
||||
private static final int INITIAL_POLL_TRIES = 2;
|
||||
private static final int EMPTY_LIST_THRESHOLD = 3;
|
||||
private static final int POLL_DATA_TTL_MS = 30000;
|
||||
@@ -67,6 +64,8 @@ public class ComputerManagerService extends Service {
|
||||
private boolean pollingActive = false;
|
||||
private final Lock defaultNetworkLock = new ReentrantLock();
|
||||
|
||||
private ConnectivityManager.NetworkCallback networkCallback;
|
||||
|
||||
private DiscoveryService.DiscoveryBinder discoveryBinder;
|
||||
private final ServiceConnection discoveryServiceConnection = new ServiceConnection() {
|
||||
public void onServiceConnected(ComponentName className, IBinder binder) {
|
||||
@@ -142,7 +141,7 @@ public class ComputerManagerService extends Service {
|
||||
// then use STUN to populate the external address field if
|
||||
// it's not set already.
|
||||
if (details.remoteAddress == null) {
|
||||
InetAddress addr = InetAddress.getByName(details.activeAddress);
|
||||
InetAddress addr = InetAddress.getByName(details.activeAddress.address);
|
||||
if (addr.isSiteLocalAddress()) {
|
||||
populateExternalAddress(details);
|
||||
}
|
||||
@@ -232,7 +231,13 @@ public class ComputerManagerService extends Service {
|
||||
// Wait for the bind notification
|
||||
discoveryServiceConnection.wait(1000);
|
||||
}
|
||||
} catch (InterruptedException ignored) {
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
|
||||
// InterruptedException clears the thread's interrupt status. Since we can't
|
||||
// handle that here, we will re-interrupt the thread to set the interrupt
|
||||
// status back to true.
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -241,11 +246,18 @@ public class ComputerManagerService extends Service {
|
||||
while (activePolls.get() != 0) {
|
||||
try {
|
||||
Thread.sleep(250);
|
||||
} catch (InterruptedException ignored) {}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
|
||||
// InterruptedException clears the thread's interrupt status. Since we can't
|
||||
// handle that here, we will re-interrupt the thread to set the interrupt
|
||||
// status back to true.
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean addComputerBlocking(ComputerDetails fakeDetails) {
|
||||
public boolean addComputerBlocking(ComputerDetails fakeDetails) throws InterruptedException {
|
||||
return ComputerManagerService.this.addComputerBlocking(fakeDetails);
|
||||
}
|
||||
|
||||
@@ -359,7 +371,12 @@ public class ComputerManagerService extends Service {
|
||||
|
||||
// Perform the STUN request if we're not on a VPN or if we bound to a network
|
||||
if (!activeNetworkIsVpn || boundToNetwork) {
|
||||
details.remoteAddress = NvConnection.findExternalAddressForMdns("stun.moonlight-stream.org", 3478);
|
||||
String stunResolvedAddress = NvConnection.findExternalAddressForMdns("stun.moonlight-stream.org", 3478);
|
||||
if (stunResolvedAddress != null) {
|
||||
// We don't know for sure what the external port is, so we will have to guess.
|
||||
// When we contact the PC (if we haven't already), it will update the port.
|
||||
details.remoteAddress = new ComputerDetails.AddressTuple(stunResolvedAddress, details.guessExternalPort());
|
||||
}
|
||||
}
|
||||
|
||||
// Unbind from the network
|
||||
@@ -386,7 +403,7 @@ public class ComputerManagerService extends Service {
|
||||
|
||||
// Populate the computer template with mDNS info
|
||||
if (computer.getLocalAddress() != null) {
|
||||
details.localAddress = computer.getLocalAddress().getHostAddress();
|
||||
details.localAddress = new ComputerDetails.AddressTuple(computer.getLocalAddress().getHostAddress(), computer.getPort());
|
||||
|
||||
// Since we're on the same network, we can use STUN to find
|
||||
// our WAN address, which is also very likely the WAN address
|
||||
@@ -396,12 +413,21 @@ public class ComputerManagerService extends Service {
|
||||
}
|
||||
}
|
||||
if (computer.getIpv6Address() != null) {
|
||||
details.ipv6Address = computer.getIpv6Address().getHostAddress();
|
||||
details.ipv6Address = new ComputerDetails.AddressTuple(computer.getIpv6Address().getHostAddress(), computer.getPort());
|
||||
}
|
||||
|
||||
// Kick off a serverinfo poll on this machine
|
||||
if (!addComputerBlocking(details)) {
|
||||
LimeLog.warning("Auto-discovered PC failed to respond: "+details);
|
||||
try {
|
||||
// Kick off a blocking serverinfo poll on this machine
|
||||
if (!addComputerBlocking(details)) {
|
||||
LimeLog.warning("Auto-discovered PC failed to respond: "+details);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
|
||||
// InterruptedException clears the thread's interrupt status. Since we can't
|
||||
// handle that here, we will re-interrupt the thread to set the interrupt
|
||||
// status back to true.
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -449,28 +475,25 @@ public class ComputerManagerService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean addComputerBlocking(ComputerDetails fakeDetails) {
|
||||
public boolean addComputerBlocking(ComputerDetails fakeDetails) throws InterruptedException {
|
||||
// Block while we try to fill the details
|
||||
try {
|
||||
// We cannot use runPoll() here because it will attempt to persist the state of the machine
|
||||
// in the database, which would be bad because we don't have our pinned cert loaded yet.
|
||||
if (pollComputer(fakeDetails)) {
|
||||
// See if we have record of this PC to pull its pinned cert
|
||||
synchronized (pollingTuples) {
|
||||
for (PollingTuple tuple : pollingTuples) {
|
||||
if (tuple.computer.uuid.equals(fakeDetails.uuid)) {
|
||||
fakeDetails.serverCert = tuple.computer.serverCert;
|
||||
break;
|
||||
}
|
||||
|
||||
// We cannot use runPoll() here because it will attempt to persist the state of the machine
|
||||
// in the database, which would be bad because we don't have our pinned cert loaded yet.
|
||||
if (pollComputer(fakeDetails)) {
|
||||
// See if we have record of this PC to pull its pinned cert
|
||||
synchronized (pollingTuples) {
|
||||
for (PollingTuple tuple : pollingTuples) {
|
||||
if (tuple.computer.uuid.equals(fakeDetails.uuid)) {
|
||||
fakeDetails.serverCert = tuple.computer.serverCert;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Poll again, possibly with the pinned cert, to get accurate pairing information.
|
||||
// This will insert the host into the database too.
|
||||
runPoll(fakeDetails, true, 0);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
return false;
|
||||
|
||||
// Poll again, possibly with the pinned cert, to get accurate pairing information.
|
||||
// This will insert the host into the database too.
|
||||
runPoll(fakeDetails, true, 0);
|
||||
}
|
||||
|
||||
// If the machine is reachable, it was successful
|
||||
@@ -527,17 +550,21 @@ public class ComputerManagerService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
private ComputerDetails tryPollIp(ComputerDetails details, String address) {
|
||||
// Fast poll this address first to determine if we can connect at the TCP layer
|
||||
if (!fastPollIp(address)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
private ComputerDetails tryPollIp(ComputerDetails details, ComputerDetails.AddressTuple address) {
|
||||
try {
|
||||
NvHTTP http = new NvHTTP(address, idManager.getUniqueId(), details.serverCert,
|
||||
// If the current address's port number matches the active address's port number, we can also assume
|
||||
// the HTTPS port will also match. This assumption is currently safe because Sunshine sets all ports
|
||||
// as offsets from the base HTTP port and doesn't allow custom HttpsPort responses for WAN vs LAN.
|
||||
boolean portMatchesActiveAddress = details.state == ComputerDetails.State.ONLINE &&
|
||||
details.activeAddress != null && address.port == details.activeAddress.port;
|
||||
|
||||
NvHTTP http = new NvHTTP(address, portMatchesActiveAddress ? details.httpsPort : 0, idManager.getUniqueId(), details.serverCert,
|
||||
PlatformBinding.getCryptoProvider(ComputerManagerService.this));
|
||||
|
||||
ComputerDetails newDetails = http.getComputerDetails();
|
||||
// If this PC is currently online at this address, extend the timeouts to allow more time for the PC to respond.
|
||||
boolean isLikelyOnline = details.state == ComputerDetails.State.ONLINE && address.equals(details.activeAddress);
|
||||
|
||||
ComputerDetails newDetails = http.getComputerDetails(isLikelyOnline);
|
||||
|
||||
// Check if this is the PC we expected
|
||||
if (newDetails.uuid == null) {
|
||||
@@ -551,146 +578,140 @@ public class ComputerManagerService extends Service {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Set the new active address
|
||||
newDetails.activeAddress = address;
|
||||
|
||||
return newDetails;
|
||||
} catch (XmlPullParserException | IOException e) {
|
||||
} catch (XmlPullParserException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Just try to establish a TCP connection to speculatively detect a running
|
||||
// GFE server
|
||||
private boolean fastPollIp(String address) {
|
||||
if (address == null) {
|
||||
// Don't bother if our address is null
|
||||
return false;
|
||||
private static class ParallelPollTuple {
|
||||
public ComputerDetails.AddressTuple address;
|
||||
public ComputerDetails existingDetails;
|
||||
|
||||
public boolean complete;
|
||||
public Thread pollingThread;
|
||||
public ComputerDetails returnedDetails;
|
||||
|
||||
public ParallelPollTuple(ComputerDetails.AddressTuple address, ComputerDetails existingDetails) {
|
||||
this.address = address;
|
||||
this.existingDetails = existingDetails;
|
||||
}
|
||||
|
||||
Socket s = new Socket();
|
||||
try {
|
||||
s.connect(new InetSocketAddress(address, NvHTTP.HTTPS_PORT), FAST_POLL_TIMEOUT);
|
||||
s.close();
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
public void interrupt() {
|
||||
if (pollingThread != null) {
|
||||
pollingThread.interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void startFastPollThread(final String address, final boolean[] info) {
|
||||
Thread t = new Thread() {
|
||||
private void startParallelPollThread(ParallelPollTuple tuple, HashSet<ComputerDetails.AddressTuple> uniqueAddresses) {
|
||||
// Don't bother starting a polling thread for an address that doesn't exist
|
||||
// or if the address has already been polled with an earlier tuple
|
||||
if (tuple.address == null || !uniqueAddresses.add(tuple.address)) {
|
||||
tuple.complete = true;
|
||||
tuple.returnedDetails = null;
|
||||
return;
|
||||
}
|
||||
|
||||
tuple.pollingThread = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
boolean pollRes = fastPollIp(address);
|
||||
ComputerDetails details = tryPollIp(tuple.existingDetails, tuple.address);
|
||||
|
||||
synchronized (info) {
|
||||
info[0] = true; // Done
|
||||
info[1] = pollRes; // Polling result
|
||||
synchronized (tuple) {
|
||||
tuple.complete = true; // Done
|
||||
tuple.returnedDetails = details; // Polling result
|
||||
|
||||
info.notify();
|
||||
tuple.notify();
|
||||
}
|
||||
}
|
||||
};
|
||||
t.setName("Fast Poll - "+address);
|
||||
t.start();
|
||||
tuple.pollingThread.setName("Parallel Poll - "+tuple.address+" - "+tuple.existingDetails.name);
|
||||
tuple.pollingThread.start();
|
||||
}
|
||||
|
||||
private String fastPollPc(final String localAddress, final String remoteAddress, final String manualAddress, final String ipv6Address) throws InterruptedException {
|
||||
final boolean[] remoteInfo = new boolean[2];
|
||||
final boolean[] localInfo = new boolean[2];
|
||||
final boolean[] manualInfo = new boolean[2];
|
||||
final boolean[] ipv6Info = new boolean[2];
|
||||
private ComputerDetails parallelPollPc(ComputerDetails details) throws InterruptedException {
|
||||
ParallelPollTuple localInfo = new ParallelPollTuple(details.localAddress, details);
|
||||
ParallelPollTuple manualInfo = new ParallelPollTuple(details.manualAddress, details);
|
||||
ParallelPollTuple remoteInfo = new ParallelPollTuple(details.remoteAddress, details);
|
||||
ParallelPollTuple ipv6Info = new ParallelPollTuple(details.ipv6Address, details);
|
||||
|
||||
startFastPollThread(localAddress, localInfo);
|
||||
startFastPollThread(remoteAddress, remoteInfo);
|
||||
startFastPollThread(manualAddress, manualInfo);
|
||||
startFastPollThread(ipv6Address, ipv6Info);
|
||||
// These must be started in order of precedence for the deduplication algorithm
|
||||
// to result in the correct behavior.
|
||||
HashSet<ComputerDetails.AddressTuple> uniqueAddresses = new HashSet<>();
|
||||
startParallelPollThread(localInfo, uniqueAddresses);
|
||||
startParallelPollThread(manualInfo, uniqueAddresses);
|
||||
startParallelPollThread(remoteInfo, uniqueAddresses);
|
||||
startParallelPollThread(ipv6Info, uniqueAddresses);
|
||||
|
||||
// Check local first
|
||||
synchronized (localInfo) {
|
||||
while (!localInfo[0]) {
|
||||
localInfo.wait(500);
|
||||
try {
|
||||
// Check local first
|
||||
synchronized (localInfo) {
|
||||
while (!localInfo.complete) {
|
||||
localInfo.wait();
|
||||
}
|
||||
|
||||
if (localInfo.returnedDetails != null) {
|
||||
localInfo.returnedDetails.activeAddress = localInfo.address;
|
||||
return localInfo.returnedDetails;
|
||||
}
|
||||
}
|
||||
|
||||
if (localInfo[1]) {
|
||||
return localAddress;
|
||||
}
|
||||
}
|
||||
// Now manual
|
||||
synchronized (manualInfo) {
|
||||
while (!manualInfo.complete) {
|
||||
manualInfo.wait();
|
||||
}
|
||||
|
||||
// Now manual
|
||||
synchronized (manualInfo) {
|
||||
while (!manualInfo[0]) {
|
||||
manualInfo.wait(500);
|
||||
if (manualInfo.returnedDetails != null) {
|
||||
manualInfo.returnedDetails.activeAddress = manualInfo.address;
|
||||
return manualInfo.returnedDetails;
|
||||
}
|
||||
}
|
||||
|
||||
if (manualInfo[1]) {
|
||||
return manualAddress;
|
||||
}
|
||||
}
|
||||
// Now remote IPv4
|
||||
synchronized (remoteInfo) {
|
||||
while (!remoteInfo.complete) {
|
||||
remoteInfo.wait();
|
||||
}
|
||||
|
||||
// Now remote IPv4
|
||||
synchronized (remoteInfo) {
|
||||
while (!remoteInfo[0]) {
|
||||
remoteInfo.wait(500);
|
||||
if (remoteInfo.returnedDetails != null) {
|
||||
remoteInfo.returnedDetails.activeAddress = remoteInfo.address;
|
||||
return remoteInfo.returnedDetails;
|
||||
}
|
||||
}
|
||||
|
||||
if (remoteInfo[1]) {
|
||||
return remoteAddress;
|
||||
}
|
||||
}
|
||||
// Now global IPv6
|
||||
synchronized (ipv6Info) {
|
||||
while (!ipv6Info.complete) {
|
||||
ipv6Info.wait();
|
||||
}
|
||||
|
||||
// Now global IPv6
|
||||
synchronized (ipv6Info) {
|
||||
while (!ipv6Info[0]) {
|
||||
ipv6Info.wait(500);
|
||||
}
|
||||
|
||||
if (ipv6Info[1]) {
|
||||
return ipv6Address;
|
||||
if (ipv6Info.returnedDetails != null) {
|
||||
ipv6Info.returnedDetails.activeAddress = ipv6Info.address;
|
||||
return ipv6Info.returnedDetails;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
// Stop any further polling if we've found a working address or we've been
|
||||
// interrupted by an attempt to stop polling.
|
||||
localInfo.interrupt();
|
||||
manualInfo.interrupt();
|
||||
remoteInfo.interrupt();
|
||||
ipv6Info.interrupt();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean pollComputer(ComputerDetails details) throws InterruptedException {
|
||||
ComputerDetails polledDetails;
|
||||
|
||||
// Do a TCP-level connection to the HTTP server to see if it's listening.
|
||||
// Do not write this address to details.activeAddress because:
|
||||
// a) it's only a candidate and may be wrong (multiple PCs behind a single router)
|
||||
// b) if it's null, it will be unexpectedly nulling the activeAddress of a possibly online PC
|
||||
LimeLog.info("Starting fast poll for "+details.name+" ("+details.localAddress +", "+details.remoteAddress +", "+details.manualAddress+", "+details.ipv6Address+")");
|
||||
String candidateAddress = fastPollPc(details.localAddress, details.remoteAddress, details.manualAddress, details.ipv6Address);
|
||||
LimeLog.info("Fast poll for "+details.name+" returned candidate address: "+candidateAddress);
|
||||
|
||||
// If no connection could be established to either IP address, there's nothing we can do
|
||||
if (candidateAddress == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try using the active address from fast-poll
|
||||
polledDetails = tryPollIp(details, candidateAddress);
|
||||
if (polledDetails == null) {
|
||||
// If that failed, try all unique addresses except what we've
|
||||
// already tried
|
||||
HashSet<String> uniqueAddresses = new HashSet<>();
|
||||
uniqueAddresses.add(details.localAddress);
|
||||
uniqueAddresses.add(details.manualAddress);
|
||||
uniqueAddresses.add(details.remoteAddress);
|
||||
uniqueAddresses.add(details.ipv6Address);
|
||||
for (String addr : uniqueAddresses) {
|
||||
if (addr == null || addr.equals(candidateAddress)) {
|
||||
continue;
|
||||
}
|
||||
polledDetails = tryPollIp(details, addr);
|
||||
if (polledDetails != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Poll all addresses in parallel to speed up the process
|
||||
LimeLog.info("Starting parallel poll for "+details.name+" ("+details.localAddress +", "+details.remoteAddress +", "+details.manualAddress+", "+details.ipv6Address+")");
|
||||
ComputerDetails polledDetails = parallelPollPc(details);
|
||||
LimeLog.info("Parallel poll for "+details.name+" returned address: "+details.activeAddress);
|
||||
|
||||
if (polledDetails != null) {
|
||||
details.update(polledDetails);
|
||||
@@ -725,10 +746,49 @@ public class ComputerManagerService extends Service {
|
||||
}
|
||||
|
||||
releaseLocalDatabaseReference();
|
||||
|
||||
// Monitor for network changes to invalidate our PC state
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
networkCallback = new ConnectivityManager.NetworkCallback() {
|
||||
@Override
|
||||
public void onAvailable(Network network) {
|
||||
LimeLog.info("Resetting PC state for new available network");
|
||||
synchronized (pollingTuples) {
|
||||
for (PollingTuple tuple : pollingTuples) {
|
||||
tuple.computer.state = ComputerDetails.State.UNKNOWN;
|
||||
if (listener != null) {
|
||||
listener.notifyComputerUpdated(tuple.computer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLost(Network network) {
|
||||
LimeLog.info("Offlining PCs due to network loss");
|
||||
synchronized (pollingTuples) {
|
||||
for (PollingTuple tuple : pollingTuples) {
|
||||
tuple.computer.state = ComputerDetails.State.OFFLINE;
|
||||
if (listener != null) {
|
||||
listener.notifyComputerUpdated(tuple.computer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
connMgr.registerDefaultNetworkCallback(networkCallback);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
connMgr.unregisterNetworkCallback(networkCallback);
|
||||
}
|
||||
|
||||
if (discoveryBinder != null) {
|
||||
// Unbind from the discovery service
|
||||
unbindService(discoveryServiceConnection);
|
||||
@@ -816,7 +876,7 @@ public class ComputerManagerService extends Service {
|
||||
PollingTuple tuple = getPollingTuple(computer);
|
||||
|
||||
try {
|
||||
NvHTTP http = new NvHTTP(ServerHelper.getCurrentAddressFromComputer(computer), idManager.getUniqueId(),
|
||||
NvHTTP http = new NvHTTP(ServerHelper.getCurrentAddressFromComputer(computer), computer.httpsPort, idManager.getUniqueId(),
|
||||
computer.serverCert, PlatformBinding.getCryptoProvider(ComputerManagerService.this));
|
||||
|
||||
String appList;
|
||||
@@ -844,18 +904,12 @@ public class ComputerManagerService extends Service {
|
||||
if (!appList.isEmpty() &&
|
||||
(!list.isEmpty() || emptyAppListResponses >= EMPTY_LIST_THRESHOLD)) {
|
||||
// Open the cache file
|
||||
OutputStream cacheOut = null;
|
||||
try {
|
||||
cacheOut = CacheHelper.openCacheFileForOutput(getCacheDir(), "applist", computer.uuid);
|
||||
try (final OutputStream cacheOut = CacheHelper.openCacheFileForOutput(
|
||||
getCacheDir(), "applist", computer.uuid)
|
||||
) {
|
||||
CacheHelper.writeStringToOutputStream(cacheOut, appList);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
if (cacheOut != null) {
|
||||
cacheOut.close();
|
||||
}
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
|
||||
// Reset empty count if it wasn't empty this time
|
||||
|
||||
@@ -33,12 +33,11 @@ public class IdentityManager {
|
||||
private static String loadUniqueId(Context c) {
|
||||
// 2 Hex digits per byte
|
||||
char[] uid = new char[UID_SIZE_IN_BYTES * 2];
|
||||
InputStreamReader reader = null;
|
||||
LimeLog.info("Reading UID from disk");
|
||||
try {
|
||||
reader = new InputStreamReader(c.openFileInput(UNIQUE_ID_FILE_NAME));
|
||||
if (reader.read(uid) != UID_SIZE_IN_BYTES * 2)
|
||||
{
|
||||
try (final InputStreamReader reader =
|
||||
new InputStreamReader(c.openFileInput(UNIQUE_ID_FILE_NAME))
|
||||
) {
|
||||
if (reader.read(uid) != UID_SIZE_IN_BYTES * 2) {
|
||||
LimeLog.severe("UID file data is truncated");
|
||||
return null;
|
||||
}
|
||||
@@ -50,12 +49,6 @@ public class IdentityManager {
|
||||
LimeLog.severe("Error while reading UID file");
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
} finally {
|
||||
if (reader != null) {
|
||||
try {
|
||||
reader.close();
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,20 +57,14 @@ public class IdentityManager {
|
||||
LimeLog.info("Generating new UID");
|
||||
String uidStr = String.format((Locale)null, "%016x", new Random().nextLong());
|
||||
|
||||
OutputStreamWriter writer = null;
|
||||
try {
|
||||
writer = new OutputStreamWriter(c.openFileOutput(UNIQUE_ID_FILE_NAME, 0));
|
||||
try (final OutputStreamWriter writer =
|
||||
new OutputStreamWriter(c.openFileOutput(UNIQUE_ID_FILE_NAME, 0))
|
||||
) {
|
||||
writer.write(uidStr);
|
||||
LimeLog.info("UID written to disk");
|
||||
} catch (IOException e) {
|
||||
LimeLog.severe("Error while writing UID file");
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (writer != null) {
|
||||
try {
|
||||
writer.close();
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
// We can return a UID even if I/O fails
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.database.sqlite.SQLiteException;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
import com.limelight.nvstream.http.ComputerDetails;
|
||||
import com.limelight.nvstream.http.NvHTTP;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
@@ -30,26 +31,26 @@ public class LegacyDatabaseReader {
|
||||
// too. To disambiguate, we'll need to prefix them with a string
|
||||
// greater than the allowable IP address length.
|
||||
try {
|
||||
details.localAddress = InetAddress.getByAddress(c.getBlob(2)).getHostAddress();
|
||||
details.localAddress = new ComputerDetails.AddressTuple(InetAddress.getByAddress(c.getBlob(2)).getHostAddress(), NvHTTP.DEFAULT_HTTP_PORT);
|
||||
LimeLog.warning("DB: Legacy local address for " + details.name);
|
||||
} catch (UnknownHostException e) {
|
||||
// This is probably a hostname/address with the prefix string
|
||||
String stringData = c.getString(2);
|
||||
if (stringData.startsWith(ADDRESS_PREFIX)) {
|
||||
details.localAddress = c.getString(2).substring(ADDRESS_PREFIX.length());
|
||||
details.localAddress = new ComputerDetails.AddressTuple(c.getString(2).substring(ADDRESS_PREFIX.length()), NvHTTP.DEFAULT_HTTP_PORT);
|
||||
} else {
|
||||
LimeLog.severe("DB: Corrupted local address for " + details.name);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
details.remoteAddress = InetAddress.getByAddress(c.getBlob(3)).getHostAddress();
|
||||
details.remoteAddress = new ComputerDetails.AddressTuple(InetAddress.getByAddress(c.getBlob(3)).getHostAddress(), NvHTTP.DEFAULT_HTTP_PORT);
|
||||
LimeLog.warning("DB: Legacy remote address for " + details.name);
|
||||
} catch (UnknownHostException e) {
|
||||
// This is probably a hostname/address with the prefix string
|
||||
String stringData = c.getString(3);
|
||||
if (stringData.startsWith(ADDRESS_PREFIX)) {
|
||||
details.remoteAddress = c.getString(3).substring(ADDRESS_PREFIX.length());
|
||||
details.remoteAddress = new ComputerDetails.AddressTuple(c.getString(3).substring(ADDRESS_PREFIX.length()), NvHTTP.DEFAULT_HTTP_PORT);
|
||||
} else {
|
||||
LimeLog.severe("DB: Corrupted remote address for " + details.name);
|
||||
}
|
||||
@@ -68,37 +69,34 @@ public class LegacyDatabaseReader {
|
||||
}
|
||||
|
||||
private static List<ComputerDetails> getAllComputers(SQLiteDatabase db) {
|
||||
Cursor c = db.rawQuery("SELECT * FROM " + COMPUTER_TABLE_NAME, null);
|
||||
LinkedList<ComputerDetails> computerList = new LinkedList<>();
|
||||
while (c.moveToNext()) {
|
||||
ComputerDetails details = getComputerFromCursor(c);
|
||||
try (final Cursor c = db.rawQuery("SELECT * FROM " + COMPUTER_TABLE_NAME, null)) {
|
||||
LinkedList<ComputerDetails> computerList = new LinkedList<>();
|
||||
while (c.moveToNext()) {
|
||||
ComputerDetails details = getComputerFromCursor(c);
|
||||
|
||||
// If a critical field is corrupt or missing, skip the database entry
|
||||
if (details.uuid == null) {
|
||||
continue;
|
||||
// If a critical field is corrupt or missing, skip the database entry
|
||||
if (details.uuid == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
computerList.add(details);
|
||||
}
|
||||
|
||||
computerList.add(details);
|
||||
return computerList;
|
||||
}
|
||||
|
||||
c.close();
|
||||
|
||||
return computerList;
|
||||
}
|
||||
|
||||
public static List<ComputerDetails> migrateAllComputers(Context c) {
|
||||
SQLiteDatabase computerDb = null;
|
||||
try {
|
||||
try (final SQLiteDatabase computerDb = SQLiteDatabase.openDatabase(
|
||||
c.getDatabasePath(COMPUTER_DB_NAME).getPath(),
|
||||
null, SQLiteDatabase.OPEN_READONLY)
|
||||
) {
|
||||
// Open the existing database
|
||||
computerDb = SQLiteDatabase.openDatabase(c.getDatabasePath(COMPUTER_DB_NAME).getPath(), null, SQLiteDatabase.OPEN_READONLY);
|
||||
return getAllComputers(computerDb);
|
||||
} catch (SQLiteException e) {
|
||||
return new LinkedList<ComputerDetails>();
|
||||
} finally {
|
||||
// Close and delete the old DB
|
||||
if (computerDb != null) {
|
||||
computerDb.close();
|
||||
}
|
||||
c.deleteDatabase(COMPUTER_DB_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
|
||||
import com.limelight.nvstream.http.ComputerDetails;
|
||||
import com.limelight.nvstream.http.NvHTTP;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.security.cert.CertificateException;
|
||||
@@ -23,9 +24,9 @@ public class LegacyDatabaseReader2 {
|
||||
|
||||
details.uuid = c.getString(0);
|
||||
details.name = c.getString(1);
|
||||
details.localAddress = c.getString(2);
|
||||
details.remoteAddress = c.getString(3);
|
||||
details.manualAddress = c.getString(4);
|
||||
details.localAddress = new ComputerDetails.AddressTuple(c.getString(2), NvHTTP.DEFAULT_HTTP_PORT);
|
||||
details.remoteAddress = new ComputerDetails.AddressTuple(c.getString(3), NvHTTP.DEFAULT_HTTP_PORT);
|
||||
details.manualAddress = new ComputerDetails.AddressTuple(c.getString(4), NvHTTP.DEFAULT_HTTP_PORT);
|
||||
details.macAddress = c.getString(5);
|
||||
|
||||
// This column wasn't always present in the old schema
|
||||
@@ -49,37 +50,34 @@ public class LegacyDatabaseReader2 {
|
||||
}
|
||||
|
||||
public static List<ComputerDetails> getAllComputers(SQLiteDatabase computerDb) {
|
||||
Cursor c = computerDb.rawQuery("SELECT * FROM "+COMPUTER_TABLE_NAME, null);
|
||||
LinkedList<ComputerDetails> computerList = new LinkedList<>();
|
||||
while (c.moveToNext()) {
|
||||
ComputerDetails details = getComputerFromCursor(c);
|
||||
try (final Cursor c = computerDb.rawQuery("SELECT * FROM "+COMPUTER_TABLE_NAME, null)) {
|
||||
LinkedList<ComputerDetails> computerList = new LinkedList<>();
|
||||
while (c.moveToNext()) {
|
||||
ComputerDetails details = getComputerFromCursor(c);
|
||||
|
||||
// If a critical field is corrupt or missing, skip the database entry
|
||||
if (details.uuid == null) {
|
||||
continue;
|
||||
// If a critical field is corrupt or missing, skip the database entry
|
||||
if (details.uuid == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
computerList.add(details);
|
||||
}
|
||||
|
||||
computerList.add(details);
|
||||
return computerList;
|
||||
}
|
||||
|
||||
c.close();
|
||||
|
||||
return computerList;
|
||||
}
|
||||
|
||||
public static List<ComputerDetails> migrateAllComputers(Context c) {
|
||||
SQLiteDatabase computerDb = null;
|
||||
try {
|
||||
try (final SQLiteDatabase computerDb = SQLiteDatabase.openDatabase(
|
||||
c.getDatabasePath(COMPUTER_DB_NAME).getPath(),
|
||||
null, SQLiteDatabase.OPEN_READONLY)
|
||||
) {
|
||||
// Open the existing database
|
||||
computerDb = SQLiteDatabase.openDatabase(c.getDatabasePath(COMPUTER_DB_NAME).getPath(), null, SQLiteDatabase.OPEN_READONLY);
|
||||
return getAllComputers(computerDb);
|
||||
} catch (SQLiteException e) {
|
||||
return new LinkedList<ComputerDetails>();
|
||||
} finally {
|
||||
// Close and delete the old DB
|
||||
if (computerDb != null) {
|
||||
computerDb.close();
|
||||
}
|
||||
c.deleteDatabase(COMPUTER_DB_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,6 +128,13 @@ public class CachedAppAssetLoader {
|
||||
try {
|
||||
Thread.sleep((int) (1000 + (Math.random() * 500)));
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
|
||||
// InterruptedException clears the thread's interrupt status. Since we can't
|
||||
// handle that here, we will re-interrupt the thread to set the interrupt
|
||||
// status back to true.
|
||||
Thread.currentThread().interrupt();
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,21 +154,15 @@ public class DiskAssetLoader {
|
||||
}
|
||||
|
||||
public void populateCacheWithStream(CachedAppAssetLoader.LoaderTuple tuple, InputStream input) {
|
||||
OutputStream out = null;
|
||||
boolean success = false;
|
||||
try {
|
||||
out = CacheHelper.openCacheFileForOutput(cacheDir, "boxart", tuple.computer.uuid, tuple.app.getAppId() + ".png");
|
||||
try (final OutputStream out = CacheHelper.openCacheFileForOutput(
|
||||
cacheDir, "boxart", tuple.computer.uuid, tuple.app.getAppId() + ".png")
|
||||
) {
|
||||
CacheHelper.writeInputStreamToOutputStream(input, out, MAX_ASSET_SIZE);
|
||||
success = true;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (out != null) {
|
||||
try {
|
||||
out.close();
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
LimeLog.warning("Unable to populate cache with tuple: "+tuple);
|
||||
CacheHelper.deleteCacheFile(cacheDir, "boxart", tuple.computer.uuid, tuple.app.getAppId() + ".png");
|
||||
|
||||
@@ -22,8 +22,9 @@ public class NetworkAssetLoader {
|
||||
public InputStream getBitmapStream(CachedAppAssetLoader.LoaderTuple tuple) {
|
||||
InputStream in = null;
|
||||
try {
|
||||
NvHTTP http = new NvHTTP(ServerHelper.getCurrentAddressFromComputer(tuple.computer), uniqueId,
|
||||
tuple.computer.serverCert, PlatformBinding.getCryptoProvider(context));
|
||||
NvHTTP http = new NvHTTP(ServerHelper.getCurrentAddressFromComputer(tuple.computer),
|
||||
tuple.computer.httpsPort, uniqueId, tuple.computer.serverCert,
|
||||
PlatformBinding.getCryptoProvider(context));
|
||||
in = http.getBoxArt(tuple.app);
|
||||
} catch (IOException ignored) {}
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package com.limelight.nvstream;
|
||||
|
||||
import com.limelight.nvstream.http.ComputerDetails;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
public class ConnectionContext {
|
||||
public String serverAddress;
|
||||
public ComputerDetails.AddressTuple serverAddress;
|
||||
public int httpsPort;
|
||||
public X509Certificate serverCert;
|
||||
public StreamConfiguration streamConfig;
|
||||
public NvConnectionListener connListener;
|
||||
@@ -15,9 +18,15 @@ public class ConnectionContext {
|
||||
// This is the version quad from the appversion tag of /serverinfo
|
||||
public String serverAppVersion;
|
||||
public String serverGfeVersion;
|
||||
|
||||
// This is the sessionUrl0 tag from /resume and /launch
|
||||
public String rtspSessionUrl;
|
||||
|
||||
public int negotiatedWidth, negotiatedHeight;
|
||||
public boolean negotiatedHdr;
|
||||
|
||||
public int negotiatedRemoteStreaming;
|
||||
public int negotiatedPacketSize;
|
||||
|
||||
public int videoCapabilities;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,27 @@
|
||||
package com.limelight.nvstream;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.IpPrefix;
|
||||
import android.net.LinkProperties;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.RouteInfo;
|
||||
import android.os.Build;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
import javax.crypto.KeyGenerator;
|
||||
@@ -17,6 +32,7 @@ import org.xmlpull.v1.XmlPullParserException;
|
||||
import com.limelight.LimeLog;
|
||||
import com.limelight.nvstream.av.audio.AudioRenderer;
|
||||
import com.limelight.nvstream.av.video.VideoDecoderRenderer;
|
||||
import com.limelight.nvstream.http.ComputerDetails;
|
||||
import com.limelight.nvstream.http.GfeHttpResponseException;
|
||||
import com.limelight.nvstream.http.LimelightCryptoProvider;
|
||||
import com.limelight.nvstream.http.NvApp;
|
||||
@@ -27,26 +43,36 @@ import com.limelight.nvstream.jni.MoonBridge;
|
||||
|
||||
public class NvConnection {
|
||||
// Context parameters
|
||||
private String host;
|
||||
private LimelightCryptoProvider cryptoProvider;
|
||||
private String uniqueId;
|
||||
private ConnectionContext context;
|
||||
private static Semaphore connectionAllowed = new Semaphore(1);
|
||||
private final boolean isMonkey;
|
||||
|
||||
public NvConnection(String host, String uniqueId, StreamConfiguration config, LimelightCryptoProvider cryptoProvider, X509Certificate serverCert)
|
||||
{
|
||||
this.host = host;
|
||||
private final boolean batchMouseInput;
|
||||
private final Context appContext;
|
||||
|
||||
private static final int MOUSE_BATCH_PERIOD_MS = 5;
|
||||
private Timer mouseInputTimer;
|
||||
private final Object mouseInputLock = new Object();
|
||||
private short relMouseX, relMouseY, relMouseWidth, relMouseHeight;
|
||||
private short absMouseX, absMouseY, absMouseWidth, absMouseHeight;
|
||||
|
||||
public NvConnection(Context appContext, ComputerDetails.AddressTuple host, int httpsPort, String uniqueId, StreamConfiguration config, LimelightCryptoProvider cryptoProvider, X509Certificate serverCert, boolean batchMouseInput)
|
||||
{
|
||||
this.appContext = appContext;
|
||||
this.cryptoProvider = cryptoProvider;
|
||||
this.uniqueId = uniqueId;
|
||||
this.batchMouseInput = batchMouseInput;
|
||||
|
||||
this.context = new ConnectionContext();
|
||||
this.context.serverAddress = host;
|
||||
this.context.httpsPort = httpsPort;
|
||||
this.context.streamConfig = config;
|
||||
this.context.serverCert = serverCert;
|
||||
|
||||
// This is unique per connection
|
||||
this.context.riKey = generateRiAesKey();
|
||||
context.riKeyId = generateRiKeyId();
|
||||
this.context.riKeyId = generateRiKeyId();
|
||||
|
||||
this.isMonkey = ActivityManager.isUserAMonkey();
|
||||
}
|
||||
@@ -70,6 +96,11 @@ public class NvConnection {
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
// Stop sending additional input
|
||||
if (mouseInputTimer != null) {
|
||||
mouseInputTimer.cancel();
|
||||
}
|
||||
|
||||
// Interrupt any pending connection. This is thread-safe.
|
||||
MoonBridge.interruptConnection();
|
||||
|
||||
@@ -83,12 +114,148 @@ public class NvConnection {
|
||||
// Now a pending connection can be processed
|
||||
connectionAllowed.release();
|
||||
}
|
||||
|
||||
private void flushMousePosition() {
|
||||
synchronized (mouseInputLock) {
|
||||
if (relMouseX != 0 || relMouseY != 0) {
|
||||
if (relMouseWidth != 0 || relMouseHeight != 0) {
|
||||
MoonBridge.sendMouseMoveAsMousePosition(relMouseX, relMouseY, relMouseWidth, relMouseHeight);
|
||||
}
|
||||
else {
|
||||
MoonBridge.sendMouseMove(relMouseX, relMouseY);
|
||||
}
|
||||
relMouseX = relMouseY = relMouseWidth = relMouseHeight = 0;
|
||||
}
|
||||
if (absMouseX != 0 || absMouseY != 0 || absMouseWidth != 0 || absMouseHeight != 0) {
|
||||
MoonBridge.sendMousePosition(absMouseX, absMouseY, absMouseWidth, absMouseHeight);
|
||||
absMouseX = absMouseY = absMouseWidth = absMouseHeight = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private InetAddress resolveServerAddress() throws IOException {
|
||||
// Try to find an address that works for this host
|
||||
InetAddress[] addrs = InetAddress.getAllByName(context.serverAddress.address);
|
||||
for (InetAddress addr : addrs) {
|
||||
try (Socket s = new Socket()) {
|
||||
s.setSoLinger(true, 0);
|
||||
s.connect(new InetSocketAddress(addr, context.serverAddress.port), 1000);
|
||||
return addr;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// If we made it here, we didn't manage to find a working address. If DNS returned any
|
||||
// address, we'll use the first available address and hope for the best.
|
||||
if (addrs.length > 0) {
|
||||
return addrs[0];
|
||||
}
|
||||
else {
|
||||
throw new IOException("No addresses found for "+context.serverAddress);
|
||||
}
|
||||
}
|
||||
|
||||
private int detectServerConnectionType() {
|
||||
ConnectivityManager connMgr = (ConnectivityManager) appContext.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
Network activeNetwork = connMgr.getActiveNetwork();
|
||||
if (activeNetwork != null) {
|
||||
NetworkCapabilities netCaps = connMgr.getNetworkCapabilities(activeNetwork);
|
||||
if (netCaps != null) {
|
||||
if (netCaps.hasTransport(NetworkCapabilities.TRANSPORT_VPN) ||
|
||||
!netCaps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)) {
|
||||
// VPNs are treated as remote connections
|
||||
return StreamConfiguration.STREAM_CFG_REMOTE;
|
||||
}
|
||||
else if (netCaps.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
|
||||
// Cellular is always treated as remote to avoid any possible
|
||||
// issues with 464XLAT or similar technologies.
|
||||
return StreamConfiguration.STREAM_CFG_REMOTE;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the server address is on-link
|
||||
LinkProperties linkProperties = connMgr.getLinkProperties(activeNetwork);
|
||||
if (linkProperties != null) {
|
||||
InetAddress serverAddress;
|
||||
try {
|
||||
serverAddress = resolveServerAddress();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
|
||||
// We can't decide without being able to resolve the server address
|
||||
return StreamConfiguration.STREAM_CFG_AUTO;
|
||||
}
|
||||
|
||||
// If the address is in the NAT64 prefix, always treat it as remote
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
IpPrefix nat64Prefix = linkProperties.getNat64Prefix();
|
||||
if (nat64Prefix != null && nat64Prefix.contains(serverAddress)) {
|
||||
return StreamConfiguration.STREAM_CFG_REMOTE;
|
||||
}
|
||||
}
|
||||
|
||||
for (RouteInfo route : linkProperties.getRoutes()) {
|
||||
// Skip non-unicast routes (which are all we get prior to Android 13)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && route.getType() != RouteInfo.RTN_UNICAST) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find the first route that matches this address
|
||||
if (route.matches(serverAddress)) {
|
||||
// If there's no gateway, this is an on-link destination
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
// We want to use hasGateway() because getGateway() doesn't adhere
|
||||
// to documented behavior of returning null for on-link addresses.
|
||||
if (!route.hasGateway()) {
|
||||
return StreamConfiguration.STREAM_CFG_LOCAL;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// getGateway() is documented to return null for on-link destinations,
|
||||
// but it actually returns the unspecified address (0.0.0.0 or ::).
|
||||
InetAddress gateway = route.getGateway();
|
||||
if (gateway == null || gateway.isAnyLocalAddress()) {
|
||||
return StreamConfiguration.STREAM_CFG_LOCAL;
|
||||
}
|
||||
}
|
||||
|
||||
// We _should_ stop after the first matching route, but for some reason
|
||||
// Android doesn't always report IPv6 routes in descending order of
|
||||
// specificity and metric. To handle that case, we enumerate all matching
|
||||
// routes, assuming that an on-link route will always be preferred.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
NetworkInfo activeNetworkInfo = connMgr.getActiveNetworkInfo();
|
||||
if (activeNetworkInfo != null) {
|
||||
switch (activeNetworkInfo.getType()) {
|
||||
case ConnectivityManager.TYPE_VPN:
|
||||
case ConnectivityManager.TYPE_MOBILE:
|
||||
case ConnectivityManager.TYPE_MOBILE_DUN:
|
||||
case ConnectivityManager.TYPE_MOBILE_HIPRI:
|
||||
case ConnectivityManager.TYPE_MOBILE_MMS:
|
||||
case ConnectivityManager.TYPE_MOBILE_SUPL:
|
||||
case ConnectivityManager.TYPE_WIMAX:
|
||||
// VPNs and cellular connections are always remote connections
|
||||
return StreamConfiguration.STREAM_CFG_REMOTE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we can't determine the connection type, let moonlight-common-c decide.
|
||||
return StreamConfiguration.STREAM_CFG_AUTO;
|
||||
}
|
||||
|
||||
private boolean startApp() throws XmlPullParserException, IOException
|
||||
{
|
||||
NvHTTP h = new NvHTTP(context.serverAddress, uniqueId, context.serverCert, cryptoProvider);
|
||||
NvHTTP h = new NvHTTP(context.serverAddress, context.httpsPort, uniqueId, context.serverCert, cryptoProvider);
|
||||
|
||||
String serverInfo = h.getServerInfo();
|
||||
String serverInfo = h.getServerInfo(true);
|
||||
|
||||
context.serverAppVersion = h.getServerVersion(serverInfo);
|
||||
if (context.serverAppVersion == null) {
|
||||
@@ -122,7 +289,7 @@ public class NvConnection {
|
||||
}
|
||||
else if ((context.streamConfig.getWidth() > 4096 || context.streamConfig.getHeight() > 4096) &&
|
||||
!context.streamConfig.getHevcSupported()) {
|
||||
context.connListener.displayMessage("Your streaming device must support H.265 to stream at resolutions above 4K.");
|
||||
context.connListener.displayMessage("Your streaming device must support HEVC to stream at resolutions above 4K.");
|
||||
return false;
|
||||
}
|
||||
else if (context.streamConfig.getHeight() >= 2160 && !h.supports4K(serverInfo)) {
|
||||
@@ -138,6 +305,18 @@ public class NvConnection {
|
||||
context.negotiatedWidth = context.streamConfig.getWidth();
|
||||
context.negotiatedHeight = context.streamConfig.getHeight();
|
||||
}
|
||||
|
||||
// We will perform some connection type detection if the caller asked for it
|
||||
if (context.streamConfig.getRemote() == StreamConfiguration.STREAM_CFG_AUTO) {
|
||||
context.negotiatedRemoteStreaming = detectServerConnectionType();
|
||||
context.negotiatedPacketSize =
|
||||
context.negotiatedRemoteStreaming == StreamConfiguration.STREAM_CFG_REMOTE ?
|
||||
1024 : context.streamConfig.getMaxPacketSize();
|
||||
}
|
||||
else {
|
||||
context.negotiatedRemoteStreaming = context.streamConfig.getRemote();
|
||||
context.negotiatedPacketSize = context.streamConfig.getMaxPacketSize();
|
||||
}
|
||||
|
||||
//
|
||||
// Video stream format will be decided during the RTSP handshake
|
||||
@@ -236,7 +415,6 @@ public class NvConnection {
|
||||
|
||||
String appName = context.streamConfig.getApp().getAppName();
|
||||
|
||||
context.serverAddress = host;
|
||||
context.connListener.stageStarting(appName);
|
||||
|
||||
try {
|
||||
@@ -274,24 +452,41 @@ public class NvConnection {
|
||||
// we must not invoke that functionality in parallel.
|
||||
synchronized (MoonBridge.class) {
|
||||
MoonBridge.setupBridge(videoDecoderRenderer, audioRenderer, connectionListener);
|
||||
int ret = MoonBridge.startConnection(context.serverAddress,
|
||||
context.serverAppVersion, context.serverGfeVersion,
|
||||
int ret = MoonBridge.startConnection(context.serverAddress.address,
|
||||
context.serverAppVersion, context.serverGfeVersion, context.rtspSessionUrl,
|
||||
context.negotiatedWidth, context.negotiatedHeight,
|
||||
context.streamConfig.getRefreshRate(), context.streamConfig.getBitrate(),
|
||||
context.streamConfig.getMaxPacketSize(),
|
||||
context.streamConfig.getRemote(), context.streamConfig.getAudioConfiguration().toInt(),
|
||||
context.negotiatedPacketSize, context.negotiatedRemoteStreaming,
|
||||
context.streamConfig.getAudioConfiguration().toInt(),
|
||||
context.streamConfig.getHevcSupported(),
|
||||
context.negotiatedHdr,
|
||||
context.streamConfig.getHevcBitratePercentageMultiplier(),
|
||||
context.streamConfig.getClientRefreshRateX100(),
|
||||
context.streamConfig.getEncryptionFlags(),
|
||||
context.riKey.getEncoded(), ib.array(),
|
||||
context.videoCapabilities);
|
||||
context.videoCapabilities,
|
||||
context.streamConfig.getColorSpace(),
|
||||
context.streamConfig.getColorRange());
|
||||
if (ret != 0) {
|
||||
// LiStartConnection() failed, so the caller is not expected
|
||||
// to stop the connection themselves. We need to release their
|
||||
// semaphore count for them.
|
||||
connectionAllowed.release();
|
||||
return;
|
||||
}
|
||||
|
||||
if (batchMouseInput) {
|
||||
// High polling rate mice can cause GeForce Experience's input queue to get backed up,
|
||||
// causing massive input latency. We counter this by limiting our mouse events to 200 Hz
|
||||
// which appears to avoid triggering the issue on all known configurations.
|
||||
mouseInputTimer = new Timer("MouseInput", true);
|
||||
mouseInputTimer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Flush the mouse position every 5 ms
|
||||
flushMousePosition();
|
||||
}
|
||||
}, MOUSE_BATCH_PERIOD_MS, MOUSE_BATCH_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -301,20 +496,65 @@ public class NvConnection {
|
||||
public void sendMouseMove(final short deltaX, final short deltaY)
|
||||
{
|
||||
if (!isMonkey) {
|
||||
MoonBridge.sendMouseMove(deltaX, deltaY);
|
||||
synchronized (mouseInputLock) {
|
||||
relMouseX += deltaX;
|
||||
relMouseY += deltaY;
|
||||
|
||||
// Reset these to ensure we don't send this as a position update
|
||||
relMouseWidth = 0;
|
||||
relMouseHeight = 0;
|
||||
}
|
||||
|
||||
if (!batchMouseInput) {
|
||||
flushMousePosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void sendMousePosition(short x, short y, short referenceWidth, short referenceHeight)
|
||||
{
|
||||
if (!isMonkey) {
|
||||
MoonBridge.sendMousePosition(x, y, referenceWidth, referenceHeight);
|
||||
synchronized (mouseInputLock) {
|
||||
absMouseX = x;
|
||||
absMouseY = y;
|
||||
absMouseWidth = referenceWidth;
|
||||
absMouseHeight = referenceHeight;
|
||||
}
|
||||
|
||||
if (!batchMouseInput) {
|
||||
flushMousePosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void sendMouseMoveAsMousePosition(short deltaX, short deltaY, short referenceWidth, short referenceHeight)
|
||||
{
|
||||
if (!isMonkey) {
|
||||
synchronized (mouseInputLock) {
|
||||
// Only accumulate the delta if the reference size is the same
|
||||
if (relMouseWidth == referenceWidth && relMouseHeight == referenceHeight) {
|
||||
relMouseX += deltaX;
|
||||
relMouseY += deltaY;
|
||||
}
|
||||
else {
|
||||
relMouseX = deltaX;
|
||||
relMouseY = deltaY;
|
||||
}
|
||||
|
||||
relMouseWidth = referenceWidth;
|
||||
relMouseHeight = referenceHeight;
|
||||
}
|
||||
|
||||
if (!batchMouseInput) {
|
||||
flushMousePosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void sendMouseButtonDown(final byte mouseButton)
|
||||
{
|
||||
if (!isMonkey) {
|
||||
flushMousePosition();
|
||||
MoonBridge.sendMouseButton(MouseButtonPacket.PRESS_EVENT, mouseButton);
|
||||
}
|
||||
}
|
||||
@@ -322,6 +562,7 @@ public class NvConnection {
|
||||
public void sendMouseButtonUp(final byte mouseButton)
|
||||
{
|
||||
if (!isMonkey) {
|
||||
flushMousePosition();
|
||||
MoonBridge.sendMouseButton(MouseButtonPacket.RELEASE_EVENT, mouseButton);
|
||||
}
|
||||
}
|
||||
@@ -357,16 +598,24 @@ public class NvConnection {
|
||||
|
||||
public void sendMouseScroll(final byte scrollClicks) {
|
||||
if (!isMonkey) {
|
||||
flushMousePosition();
|
||||
MoonBridge.sendMouseScroll(scrollClicks);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendMouseHighResScroll(final short scrollAmount) {
|
||||
if (!isMonkey) {
|
||||
flushMousePosition();
|
||||
MoonBridge.sendMouseHighResScroll(scrollAmount);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendUtf8Text(final String text) {
|
||||
if (!isMonkey) {
|
||||
MoonBridge.sendUtf8Text(text);
|
||||
}
|
||||
}
|
||||
|
||||
public static String findExternalAddressForMdns(String stunHostname, int stunPort) {
|
||||
return MoonBridge.findExternalAddressIP4(stunHostname, stunPort);
|
||||
}
|
||||
|
||||
@@ -13,4 +13,6 @@ public interface NvConnectionListener {
|
||||
void displayTransientMessage(String message);
|
||||
|
||||
void rumble(short controllerNumber, short lowFreqMotor, short highFreqMotor);
|
||||
|
||||
void setHdrMode(boolean enabled);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ public class StreamConfiguration {
|
||||
private boolean enableHdr;
|
||||
private int attachedGamepadMask;
|
||||
private int encryptionFlags;
|
||||
private int colorRange;
|
||||
private int colorSpace;
|
||||
|
||||
public static class Builder {
|
||||
private StreamConfiguration config = new StreamConfiguration();
|
||||
@@ -131,7 +133,17 @@ public class StreamConfiguration {
|
||||
config.supportsHevc = supportsHevc;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public StreamConfiguration.Builder setColorRange(int colorRange) {
|
||||
config.colorRange = colorRange;
|
||||
return this;
|
||||
}
|
||||
|
||||
public StreamConfiguration.Builder setColorSpace(int colorSpace) {
|
||||
config.colorSpace = colorSpace;
|
||||
return this;
|
||||
}
|
||||
|
||||
public StreamConfiguration build() {
|
||||
return config;
|
||||
}
|
||||
@@ -226,4 +238,12 @@ public class StreamConfiguration {
|
||||
public int getEncryptionFlags() {
|
||||
return encryptionFlags;
|
||||
}
|
||||
|
||||
public int getColorRange() {
|
||||
return colorRange;
|
||||
}
|
||||
|
||||
public int getColorSpace() {
|
||||
return colorSpace;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,11 @@ public abstract class VideoDecoderRenderer {
|
||||
// This is called once for each frame-start NALU. This means it will be called several times
|
||||
// for an IDR frame which contains several parameter sets and the I-frame data.
|
||||
public abstract int submitDecodeUnit(byte[] decodeUnitData, int decodeUnitLength, int decodeUnitType,
|
||||
int frameNumber, long receiveTimeMs, long enqueueTimeMs);
|
||||
int frameNumber, int frameType, long receiveTimeMs, long enqueueTimeMs);
|
||||
|
||||
public abstract void cleanup();
|
||||
|
||||
public abstract int getCapabilities();
|
||||
|
||||
public abstract void setHdrMode(boolean enabled);
|
||||
}
|
||||
|
||||
@@ -8,19 +8,69 @@ public class ComputerDetails {
|
||||
ONLINE, OFFLINE, UNKNOWN
|
||||
}
|
||||
|
||||
public static class AddressTuple {
|
||||
public String address;
|
||||
public int port;
|
||||
|
||||
public AddressTuple(String address, int port) {
|
||||
if (address == null) {
|
||||
throw new IllegalArgumentException("Address cannot be null");
|
||||
}
|
||||
if (port <= 0) {
|
||||
throw new IllegalArgumentException("Invalid port");
|
||||
}
|
||||
|
||||
// If this was an escaped IPv6 address, remove the brackets
|
||||
if (address.startsWith("[") && address.endsWith("]")) {
|
||||
address = address.substring(1, address.length() - 1);
|
||||
}
|
||||
|
||||
this.address = address;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return address.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof AddressTuple)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AddressTuple that = (AddressTuple) obj;
|
||||
return address.equals(that.address) && port == that.port;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
if (address.contains(":")) {
|
||||
// IPv6
|
||||
return "[" + address + "]:" + port;
|
||||
}
|
||||
else {
|
||||
// IPv4 and hostnames
|
||||
return address + ":" + port;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Persistent attributes
|
||||
public String uuid;
|
||||
public String name;
|
||||
public String localAddress;
|
||||
public String remoteAddress;
|
||||
public String manualAddress;
|
||||
public String ipv6Address;
|
||||
public AddressTuple localAddress;
|
||||
public AddressTuple remoteAddress;
|
||||
public AddressTuple manualAddress;
|
||||
public AddressTuple ipv6Address;
|
||||
public String macAddress;
|
||||
public X509Certificate serverCert;
|
||||
|
||||
// Transient attributes
|
||||
public State state;
|
||||
public String activeAddress;
|
||||
public AddressTuple activeAddress;
|
||||
public int httpsPort;
|
||||
public int externalPort;
|
||||
public PairingManager.PairState pairState;
|
||||
public int runningGameId;
|
||||
public String rawAppList;
|
||||
@@ -35,6 +85,27 @@ public class ComputerDetails {
|
||||
update(details);
|
||||
}
|
||||
|
||||
public int guessExternalPort() {
|
||||
if (externalPort != 0) {
|
||||
return externalPort;
|
||||
}
|
||||
else if (remoteAddress != null) {
|
||||
return remoteAddress.port;
|
||||
}
|
||||
else if (activeAddress != null) {
|
||||
return activeAddress.port;
|
||||
}
|
||||
else if (ipv6Address != null) {
|
||||
return ipv6Address.port;
|
||||
}
|
||||
else if (localAddress != null) {
|
||||
return localAddress.port;
|
||||
}
|
||||
else {
|
||||
return NvHTTP.DEFAULT_HTTP_PORT;
|
||||
}
|
||||
}
|
||||
|
||||
public void update(ComputerDetails details) {
|
||||
this.state = details.state;
|
||||
this.name = details.name;
|
||||
@@ -43,12 +114,18 @@ public class ComputerDetails {
|
||||
this.activeAddress = details.activeAddress;
|
||||
}
|
||||
// We can get IPv4 loopback addresses with GS IPv6 Forwarder
|
||||
if (details.localAddress != null && !details.localAddress.startsWith("127.")) {
|
||||
if (details.localAddress != null && !details.localAddress.address.startsWith("127.")) {
|
||||
this.localAddress = details.localAddress;
|
||||
}
|
||||
if (details.remoteAddress != null) {
|
||||
this.remoteAddress = details.remoteAddress;
|
||||
}
|
||||
else if (this.remoteAddress != null && details.externalPort != 0) {
|
||||
// If we have a remote address already (perhaps via STUN) but our updated details
|
||||
// don't have a new one (because GFE doesn't send one), propagate the external
|
||||
// port to the current remote address. We may have tried to guess it previously.
|
||||
this.remoteAddress.port = details.externalPort;
|
||||
}
|
||||
if (details.manualAddress != null) {
|
||||
this.manualAddress = details.manualAddress;
|
||||
}
|
||||
@@ -61,6 +138,8 @@ public class ComputerDetails {
|
||||
if (details.serverCert != null) {
|
||||
this.serverCert = details.serverCert;
|
||||
}
|
||||
this.externalPort = details.externalPort;
|
||||
this.httpsPort = details.httpsPort;
|
||||
this.pairState = details.pairState;
|
||||
this.runningGameId = details.runningGameId;
|
||||
this.rawAppList = details.rawAppList;
|
||||
@@ -80,6 +159,7 @@ public class ComputerDetails {
|
||||
str.append("MAC Address: ").append(macAddress).append("\n");
|
||||
str.append("Pair State: ").append(pairState).append("\n");
|
||||
str.append("Running Game ID: ").append(runningGameId).append("\n");
|
||||
str.append("HTTPS Port: ").append(httpsPort).append("\n");
|
||||
return str.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ public class NvApp {
|
||||
public String toString() {
|
||||
StringBuilder str = new StringBuilder();
|
||||
str.append("Name: ").append(appName).append("\n");
|
||||
str.append("HDR: ").append(hdrSupported ? "Yes" : "No").append("\n");
|
||||
str.append("HDR Supported: ").append(hdrSupported ? "Yes" : "Unknown").append("\n");
|
||||
str.append("ID: ").append(appId).append("\n");
|
||||
return str.toString();
|
||||
}
|
||||
|
||||
@@ -9,10 +9,8 @@ import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.net.InetAddress;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.Proxy;
|
||||
import java.net.Socket;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
@@ -53,6 +51,7 @@ import com.limelight.nvstream.ConnectionContext;
|
||||
import com.limelight.nvstream.http.PairingManager.PairState;
|
||||
|
||||
import okhttp3.ConnectionPool;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
@@ -63,19 +62,22 @@ public class NvHTTP {
|
||||
private String uniqueId;
|
||||
private PairingManager pm;
|
||||
|
||||
public static final int HTTPS_PORT = 47984;
|
||||
public static final int HTTP_PORT = 47989;
|
||||
public static final int CONNECTION_TIMEOUT = 3000;
|
||||
public static final int READ_TIMEOUT = 5000;
|
||||
private static final int DEFAULT_HTTPS_PORT = 47984;
|
||||
public static final int DEFAULT_HTTP_PORT = 47989;
|
||||
public static final int SHORT_CONNECTION_TIMEOUT = 3000;
|
||||
public static final int LONG_CONNECTION_TIMEOUT = 5000;
|
||||
public static final int READ_TIMEOUT = 7000;
|
||||
|
||||
// Print URL and content to logcat on debug builds
|
||||
private static boolean verbose = BuildConfig.DEBUG;
|
||||
|
||||
public String baseUrlHttps;
|
||||
public String baseUrlHttp;
|
||||
private HttpUrl baseUrlHttp;
|
||||
|
||||
private int httpsPort;
|
||||
|
||||
private OkHttpClient httpClient;
|
||||
private OkHttpClient httpClientWithReadTimeout;
|
||||
private OkHttpClient httpClientLongConnectTimeout;
|
||||
private OkHttpClient httpClientLongConnectNoReadTimeout;
|
||||
private OkHttpClient httpClientShortConnectTimeout;
|
||||
|
||||
private X509TrustManager defaultTrustManager;
|
||||
private X509TrustManager trustManager;
|
||||
@@ -168,19 +170,34 @@ public class NvHTTP {
|
||||
}
|
||||
};
|
||||
|
||||
httpClient = new OkHttpClient.Builder()
|
||||
httpClientLongConnectTimeout = new OkHttpClient.Builder()
|
||||
.connectionPool(new ConnectionPool(0, 1, TimeUnit.MILLISECONDS))
|
||||
.hostnameVerifier(hv)
|
||||
.readTimeout(0, TimeUnit.MILLISECONDS)
|
||||
.connectTimeout(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
|
||||
.build();
|
||||
|
||||
httpClientWithReadTimeout = httpClient.newBuilder()
|
||||
.readTimeout(READ_TIMEOUT, TimeUnit.MILLISECONDS)
|
||||
.connectTimeout(LONG_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
|
||||
.proxy(Proxy.NO_PROXY)
|
||||
.build();
|
||||
|
||||
httpClientShortConnectTimeout = httpClientLongConnectTimeout.newBuilder()
|
||||
.connectTimeout(SHORT_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
|
||||
.build();
|
||||
|
||||
httpClientLongConnectNoReadTimeout = httpClientLongConnectTimeout.newBuilder()
|
||||
.readTimeout(0, TimeUnit.MILLISECONDS)
|
||||
.build();
|
||||
}
|
||||
|
||||
public HttpUrl getHttpsUrl(boolean likelyOnline) throws IOException {
|
||||
if (httpsPort == 0) {
|
||||
// Fetch the HTTPS port if we don't have it already
|
||||
httpsPort = getHttpsPort(openHttpConnectionToString(likelyOnline ? httpClientLongConnectTimeout : httpClientShortConnectTimeout,
|
||||
baseUrlHttp, "serverinfo"));
|
||||
}
|
||||
|
||||
return new HttpUrl.Builder().scheme("https").host(baseUrlHttp.host()).port(httpsPort).build();
|
||||
}
|
||||
|
||||
public NvHTTP(String address, String uniqueId, X509Certificate serverCert, LimelightCryptoProvider cryptoProvider) throws IOException {
|
||||
public NvHTTP(ComputerDetails.AddressTuple address, int httpsPort, String uniqueId, X509Certificate serverCert, LimelightCryptoProvider cryptoProvider) throws IOException {
|
||||
// Use the same UID for all Moonlight clients so we can quit games
|
||||
// started by other Moonlight clients.
|
||||
this.uniqueId = "0123456789ABCDEF";
|
||||
@@ -189,23 +206,23 @@ public class NvHTTP {
|
||||
|
||||
initializeHttpState(cryptoProvider);
|
||||
|
||||
this.httpsPort = httpsPort;
|
||||
|
||||
try {
|
||||
// The URI constructor takes care of escaping IPv6 literals
|
||||
this.baseUrlHttps = new URI("https", null, address, HTTPS_PORT, null, null, null).toString();
|
||||
this.baseUrlHttp = new URI("http", null, address, HTTP_PORT, null, null, null).toString();
|
||||
} catch (URISyntaxException e) {
|
||||
// Encapsulate URISyntaxException into IOException for callers to handle more easily
|
||||
this.baseUrlHttp = new HttpUrl.Builder()
|
||||
.scheme("http")
|
||||
.host(address.address)
|
||||
.port(address.port)
|
||||
.build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Encapsulate IllegalArgumentException into IOException for callers to handle more easily
|
||||
throw new IOException(e);
|
||||
}
|
||||
|
||||
this.pm = new PairingManager(this, cryptoProvider);
|
||||
}
|
||||
|
||||
String buildUniqueIdUuidString() {
|
||||
return "uniqueid="+uniqueId+"&uuid="+UUID.randomUUID();
|
||||
}
|
||||
|
||||
static String getXmlString(Reader r, String tagname) throws XmlPullParserException, IOException {
|
||||
|
||||
static String getXmlString(Reader r, String tagname, boolean throwIfMissing) throws XmlPullParserException, IOException {
|
||||
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
|
||||
factory.setNamespaceAware(true);
|
||||
XmlPullParser xpp = factory.newPullParser();
|
||||
@@ -234,11 +251,19 @@ public class NvHTTP {
|
||||
eventType = xpp.next();
|
||||
}
|
||||
|
||||
if (throwIfMissing) {
|
||||
// We throw an XmlPullParserException here for ease of handling in all the various callers.
|
||||
// We could also throw an IOException, but some callers expect those in cases where the
|
||||
// host may not be reachable. We want to distinguish unreachable hosts vs. hosts that
|
||||
// are returning garbage XML to us, so we use XmlPullParserException instead.
|
||||
throw new XmlPullParserException("Missing mandatory field in host response: "+tagname);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static String getXmlString(String str, String tagname) throws XmlPullParserException, IOException {
|
||||
return getXmlString(new StringReader(str), tagname);
|
||||
static String getXmlString(String str, String tagname, boolean throwIfMissing) throws XmlPullParserException, IOException {
|
||||
return getXmlString(new StringReader(str), tagname, throwIfMissing);
|
||||
}
|
||||
|
||||
private static void verifyResponseStatus(XmlPullParser xpp) throws GfeHttpResponseException {
|
||||
@@ -259,8 +284,11 @@ public class NvHTTP {
|
||||
}
|
||||
}
|
||||
|
||||
public String getServerInfo() throws IOException, XmlPullParserException {
|
||||
public String getServerInfo(boolean likelyOnline) throws IOException, XmlPullParserException {
|
||||
String resp;
|
||||
|
||||
// If we believe the PC is online, give it a little extra time to respond
|
||||
OkHttpClient client = likelyOnline ? httpClientLongConnectTimeout : httpClientShortConnectTimeout;
|
||||
|
||||
//
|
||||
// TODO: Shield Hub uses HTTP for this and is able to get an accurate PairStatus with HTTP.
|
||||
@@ -272,7 +300,7 @@ public class NvHTTP {
|
||||
if (serverCert != null) {
|
||||
try {
|
||||
try {
|
||||
resp = openHttpConnectionToString(baseUrlHttps + "/serverinfo?"+buildUniqueIdUuidString(), true);
|
||||
resp = openHttpConnectionToString(client, getHttpsUrl(likelyOnline), "serverinfo");
|
||||
} catch (SSLHandshakeException e) {
|
||||
// Detect if we failed due to a server cert mismatch
|
||||
if (e.getCause() instanceof CertificateException) {
|
||||
@@ -292,7 +320,7 @@ public class NvHTTP {
|
||||
catch (GfeHttpResponseException e) {
|
||||
if (e.getErrorCode() == 401) {
|
||||
// Cert validation error - fall back to HTTP
|
||||
return openHttpConnectionToString(baseUrlHttp + "/serverinfo", true);
|
||||
return openHttpConnectionToString(client, baseUrlHttp, "serverinfo");
|
||||
}
|
||||
|
||||
// If it's not a cert validation error, throw it
|
||||
@@ -303,29 +331,42 @@ public class NvHTTP {
|
||||
}
|
||||
else {
|
||||
// No pinned cert, so use HTTP
|
||||
return openHttpConnectionToString(baseUrlHttp + "/serverinfo", true);
|
||||
return openHttpConnectionToString(client, baseUrlHttp, "serverinfo");
|
||||
}
|
||||
}
|
||||
|
||||
private static ComputerDetails.AddressTuple makeTuple(String address, int port) {
|
||||
if (address == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ComputerDetails.AddressTuple(address, port);
|
||||
}
|
||||
|
||||
public ComputerDetails getComputerDetails() throws IOException, XmlPullParserException {
|
||||
public ComputerDetails getComputerDetails(boolean likelyOnline) throws IOException, XmlPullParserException {
|
||||
ComputerDetails details = new ComputerDetails();
|
||||
String serverInfo = getServerInfo();
|
||||
String serverInfo = getServerInfo(likelyOnline);
|
||||
|
||||
details.name = getXmlString(serverInfo, "hostname");
|
||||
details.name = getXmlString(serverInfo, "hostname", false);
|
||||
if (details.name == null || details.name.isEmpty()) {
|
||||
details.name = "UNKNOWN";
|
||||
}
|
||||
|
||||
details.uuid = getXmlString(serverInfo, "uniqueid");
|
||||
details.macAddress = getXmlString(serverInfo, "mac");
|
||||
details.localAddress = getXmlString(serverInfo, "LocalIP");
|
||||
// UUID is mandatory to determine which machine is responding
|
||||
details.uuid = getXmlString(serverInfo, "uniqueid", true);
|
||||
|
||||
// This may be null, but that's okay
|
||||
details.remoteAddress = getXmlString(serverInfo, "ExternalIP");
|
||||
details.httpsPort = getHttpsPort(serverInfo);
|
||||
|
||||
details.macAddress = getXmlString(serverInfo, "mac", false);
|
||||
|
||||
// FIXME: Do we want to use the current port?
|
||||
details.localAddress = makeTuple(getXmlString(serverInfo, "LocalIP", false), baseUrlHttp.port());
|
||||
|
||||
// This is missing on on recent GFE versions, but it's present on Sunshine
|
||||
details.externalPort = getExternalPort(serverInfo);
|
||||
details.remoteAddress = makeTuple(getXmlString(serverInfo, "ExternalIP", false), details.externalPort);
|
||||
|
||||
// This has some extra logic to always report unpaired if the pinned cert isn't there
|
||||
details.pairState = getPairState(serverInfo);
|
||||
|
||||
details.runningGameId = getCurrentGame(serverInfo);
|
||||
|
||||
// We could reach it so it's online
|
||||
@@ -357,20 +398,27 @@ public class NvHTTP {
|
||||
}
|
||||
}
|
||||
|
||||
private HttpUrl getCompleteUrl(HttpUrl baseUrl, String path, String query) {
|
||||
return baseUrl.newBuilder()
|
||||
.addPathSegment(path)
|
||||
.query(query)
|
||||
.addQueryParameter("uniqueid", uniqueId)
|
||||
.addQueryParameter("uuid", UUID.randomUUID().toString())
|
||||
.build();
|
||||
}
|
||||
|
||||
private ResponseBody openHttpConnection(OkHttpClient client, HttpUrl baseUrl, String path) throws IOException {
|
||||
return openHttpConnection(client, baseUrl, path, null);
|
||||
}
|
||||
|
||||
// Read timeout should be enabled for any HTTP query that requires no outside action
|
||||
// on the GFE server. Examples of queries that DO require outside action are launch, resume, and quit.
|
||||
// The initial pair query does require outside action (user entering a PIN) but subsequent pairing
|
||||
// queries do not.
|
||||
private ResponseBody openHttpConnection(String url, boolean enableReadTimeout) throws IOException {
|
||||
Request request = new Request.Builder().url(url).get().build();
|
||||
Response response;
|
||||
|
||||
if (enableReadTimeout) {
|
||||
response = performAndroidTlsHack(httpClientWithReadTimeout).newCall(request).execute();
|
||||
}
|
||||
else {
|
||||
response = performAndroidTlsHack(httpClient).newCall(request).execute();
|
||||
}
|
||||
private ResponseBody openHttpConnection(OkHttpClient client, HttpUrl baseUrl, String path, String query) throws IOException {
|
||||
HttpUrl completeUrl = getCompleteUrl(baseUrl, path, query);
|
||||
Request request = new Request.Builder().url(completeUrl).get().build();
|
||||
Response response = performAndroidTlsHack(client).newCall(request).execute();
|
||||
|
||||
ResponseBody body = response.body();
|
||||
|
||||
@@ -384,30 +432,31 @@ public class NvHTTP {
|
||||
}
|
||||
|
||||
if (response.code() == 404) {
|
||||
throw new FileNotFoundException(url);
|
||||
throw new FileNotFoundException(completeUrl.toString());
|
||||
}
|
||||
else {
|
||||
throw new GfeHttpResponseException(response.code(), response.message());
|
||||
}
|
||||
}
|
||||
|
||||
String openHttpConnectionToString(String url, boolean enableReadTimeout) throws IOException {
|
||||
try {
|
||||
if (verbose) {
|
||||
LimeLog.info("Requesting URL: "+url);
|
||||
}
|
||||
|
||||
ResponseBody resp = openHttpConnection(url, enableReadTimeout);
|
||||
private String openHttpConnectionToString(OkHttpClient client, HttpUrl baseUrl, String path) throws IOException {
|
||||
return openHttpConnectionToString(client, baseUrl, path, null);
|
||||
}
|
||||
|
||||
private String openHttpConnectionToString(OkHttpClient client, HttpUrl baseUrl, String path, String query) throws IOException {
|
||||
try {
|
||||
ResponseBody resp = openHttpConnection(client, baseUrl, path, query);
|
||||
String respString = resp.string();
|
||||
resp.close();
|
||||
|
||||
if (verbose) {
|
||||
LimeLog.info(url+" -> "+respString);
|
||||
if (verbose && !path.equals("serverinfo")) {
|
||||
LimeLog.info(getCompleteUrl(baseUrl, path, query)+" -> "+respString);
|
||||
}
|
||||
|
||||
return respString;
|
||||
} catch (IOException e) {
|
||||
if (verbose) {
|
||||
if (verbose && !path.equals("serverinfo")) {
|
||||
LimeLog.warning(getCompleteUrl(baseUrl, path, query)+" -> "+e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
@@ -416,23 +465,23 @@ public class NvHTTP {
|
||||
}
|
||||
|
||||
public String getServerVersion(String serverInfo) throws XmlPullParserException, IOException {
|
||||
return getXmlString(serverInfo, "appversion");
|
||||
// appversion is present in all supported GFE versions
|
||||
return getXmlString(serverInfo, "appversion", true);
|
||||
}
|
||||
|
||||
public PairingManager.PairState getPairState() throws IOException, XmlPullParserException {
|
||||
return getPairState(getServerInfo());
|
||||
return getPairState(getServerInfo(true));
|
||||
}
|
||||
|
||||
public PairingManager.PairState getPairState(String serverInfo) throws IOException, XmlPullParserException {
|
||||
if (!NvHTTP.getXmlString(serverInfo, "PairStatus").equals("1")) {
|
||||
return PairState.NOT_PAIRED;
|
||||
}
|
||||
|
||||
return PairState.PAIRED;
|
||||
// appversion is present in all supported GFE versions
|
||||
return NvHTTP.getXmlString(serverInfo, "PairStatus", true).equals("1") ?
|
||||
PairState.PAIRED : PairState.NOT_PAIRED;
|
||||
}
|
||||
|
||||
public long getMaxLumaPixelsH264(String serverInfo) throws XmlPullParserException, IOException {
|
||||
String str = getXmlString(serverInfo, "MaxLumaPixelsH264");
|
||||
// MaxLumaPixelsH264 wasn't present on old GFE versions
|
||||
String str = getXmlString(serverInfo, "MaxLumaPixelsH264", false);
|
||||
if (str != null) {
|
||||
return Long.parseLong(str);
|
||||
} else {
|
||||
@@ -441,7 +490,8 @@ public class NvHTTP {
|
||||
}
|
||||
|
||||
public long getMaxLumaPixelsHEVC(String serverInfo) throws XmlPullParserException, IOException {
|
||||
String str = getXmlString(serverInfo, "MaxLumaPixelsHEVC");
|
||||
// MaxLumaPixelsHEVC wasn't present on old GFE versions
|
||||
String str = getXmlString(serverInfo, "MaxLumaPixelsHEVC", false);
|
||||
if (str != null) {
|
||||
return Long.parseLong(str);
|
||||
} else {
|
||||
@@ -458,7 +508,8 @@ public class NvHTTP {
|
||||
// Bit 10: HEVC Main10 4:4:4
|
||||
// Bit 11: ???
|
||||
public long getServerCodecModeSupport(String serverInfo) throws XmlPullParserException, IOException {
|
||||
String str = getXmlString(serverInfo, "ServerCodecModeSupport");
|
||||
// ServerCodecModeSupport wasn't present on old GFE versions
|
||||
String str = getXmlString(serverInfo, "ServerCodecModeSupport", false);
|
||||
if (str != null) {
|
||||
return Long.parseLong(str);
|
||||
} else {
|
||||
@@ -467,16 +518,18 @@ public class NvHTTP {
|
||||
}
|
||||
|
||||
public String getGpuType(String serverInfo) throws XmlPullParserException, IOException {
|
||||
return getXmlString(serverInfo, "gputype");
|
||||
// ServerCodecModeSupport wasn't present on old GFE versions
|
||||
return getXmlString(serverInfo, "gputype", false);
|
||||
}
|
||||
|
||||
public String getGfeVersion(String serverInfo) throws XmlPullParserException, IOException {
|
||||
return getXmlString(serverInfo, "GfeVersion");
|
||||
// ServerCodecModeSupport wasn't present on old GFE versions
|
||||
return getXmlString(serverInfo, "GfeVersion", false);
|
||||
}
|
||||
|
||||
public boolean supports4K(String serverInfo) throws XmlPullParserException, IOException {
|
||||
// Only allow 4K on GFE 3.x
|
||||
String gfeVersionStr = getXmlString(serverInfo, "GfeVersion");
|
||||
// Only allow 4K on GFE 3.x. GfeVersion wasn't present on very old versions of GFE.
|
||||
String gfeVersionStr = getXmlString(serverInfo, "GfeVersion", false);
|
||||
if (gfeVersionStr == null || gfeVersionStr.startsWith("2.")) {
|
||||
return false;
|
||||
}
|
||||
@@ -488,16 +541,40 @@ public class NvHTTP {
|
||||
// GFE 2.8 started keeping currentgame set to the last game played. As a result, it no longer
|
||||
// has the semantics that its name would indicate. To contain the effects of this change as much
|
||||
// as possible, we'll force the current game to zero if the server isn't in a streaming session.
|
||||
String serverState = getXmlString(serverInfo, "state");
|
||||
if (serverState != null && serverState.endsWith("_SERVER_BUSY")) {
|
||||
String game = getXmlString(serverInfo, "currentgame");
|
||||
return Integer.parseInt(game);
|
||||
if (getXmlString(serverInfo, "state", true).endsWith("_SERVER_BUSY")) {
|
||||
return Integer.parseInt(getXmlString(serverInfo, "currentgame", true));
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public int getHttpsPort(String serverInfo) {
|
||||
try {
|
||||
return Integer.parseInt(getXmlString(serverInfo, "HttpsPort", true));
|
||||
} catch (XmlPullParserException e) {
|
||||
e.printStackTrace();
|
||||
return DEFAULT_HTTPS_PORT;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return DEFAULT_HTTPS_PORT;
|
||||
}
|
||||
}
|
||||
|
||||
public int getExternalPort(String serverInfo) {
|
||||
// This is an extension which is not present in GFE. It is present for Sunshine to be able
|
||||
// to support dynamic HTTP WAN ports without requiring the user to manually enter the port.
|
||||
try {
|
||||
return Integer.parseInt(getXmlString(serverInfo, "ExternalPort", true));
|
||||
} catch (XmlPullParserException e) {
|
||||
// Expected on non-Sunshine servers
|
||||
return baseUrlHttp.port();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return baseUrlHttp.port();
|
||||
}
|
||||
}
|
||||
|
||||
public NvApp getAppById(int appId) throws IOException, XmlPullParserException {
|
||||
LinkedList<NvApp> appList = getAppList();
|
||||
for (NvApp appFromList : appList) {
|
||||
@@ -588,8 +665,8 @@ public class NvHTTP {
|
||||
return appList;
|
||||
}
|
||||
|
||||
public String getAppListRaw() throws MalformedURLException, IOException {
|
||||
return openHttpConnectionToString(baseUrlHttps + "/applist?"+buildUniqueIdUuidString(), true);
|
||||
public String getAppListRaw() throws IOException {
|
||||
return openHttpConnectionToString(httpClientLongConnectTimeout, getHttpsUrl(true), "applist");
|
||||
}
|
||||
|
||||
public LinkedList<NvApp> getAppList() throws GfeHttpResponseException, IOException, XmlPullParserException {
|
||||
@@ -598,20 +675,28 @@ public class NvHTTP {
|
||||
return getAppListByReader(new StringReader(getAppListRaw()));
|
||||
}
|
||||
else {
|
||||
ResponseBody resp = openHttpConnection(baseUrlHttps + "/applist?" + buildUniqueIdUuidString(), true);
|
||||
LinkedList<NvApp> appList = getAppListByReader(new InputStreamReader(resp.byteStream()));
|
||||
resp.close();
|
||||
return appList;
|
||||
try (final ResponseBody resp = openHttpConnection(httpClientLongConnectTimeout, getHttpsUrl(true), "applist")) {
|
||||
return getAppListByReader(new InputStreamReader(resp.byteStream()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
String executePairingCommand(String additionalArguments, boolean enableReadTimeout) throws GfeHttpResponseException, IOException {
|
||||
return openHttpConnectionToString(enableReadTimeout ? httpClientLongConnectTimeout : httpClientLongConnectNoReadTimeout,
|
||||
baseUrlHttp, "pair", "devicename=roth&updateState=1&" + additionalArguments);
|
||||
}
|
||||
|
||||
String executePairingChallenge() throws GfeHttpResponseException, IOException {
|
||||
return openHttpConnectionToString(httpClientLongConnectTimeout, getHttpsUrl(true),
|
||||
"pair", "devicename=roth&updateState=1&phrase=pairchallenge");
|
||||
}
|
||||
|
||||
public void unpair() throws IOException {
|
||||
openHttpConnectionToString(baseUrlHttp + "/unpair?"+buildUniqueIdUuidString(), true);
|
||||
openHttpConnectionToString(httpClientLongConnectTimeout, baseUrlHttp, "unpair");
|
||||
}
|
||||
|
||||
public InputStream getBoxArt(NvApp app) throws IOException {
|
||||
ResponseBody resp = openHttpConnection(baseUrlHttps + "/appasset?"+ buildUniqueIdUuidString() +
|
||||
"&appid=" + app.getAppId() + "&AssetType=2&AssetIdx=0", true);
|
||||
ResponseBody resp = openHttpConnection(httpClientLongConnectTimeout, getHttpsUrl(true), "appasset", "appid=" + app.getAppId() + "&AssetType=2&AssetIdx=0");
|
||||
return resp.byteStream();
|
||||
}
|
||||
|
||||
@@ -666,9 +751,8 @@ public class NvHTTP {
|
||||
enableSops = false;
|
||||
}
|
||||
|
||||
String xmlStr = openHttpConnectionToString(baseUrlHttps +
|
||||
"/launch?" + buildUniqueIdUuidString() +
|
||||
"&appid=" + appId +
|
||||
String xmlStr = openHttpConnectionToString(httpClientLongConnectNoReadTimeout, getHttpsUrl(true), "launch",
|
||||
"appid=" + appId +
|
||||
"&mode=" + context.negotiatedWidth + "x" + context.negotiatedHeight + "x" + fps +
|
||||
"&additionalStates=1&sops=" + (enableSops ? 1 : 0) +
|
||||
"&rikey="+bytesToHex(context.riKey.getEncoded()) +
|
||||
@@ -677,32 +761,41 @@ public class NvHTTP {
|
||||
"&localAudioPlayMode=" + (context.streamConfig.getPlayLocalAudio() ? 1 : 0) +
|
||||
"&surroundAudioInfo=" + context.streamConfig.getAudioConfiguration().getSurroundAudioInfo() +
|
||||
(context.streamConfig.getAttachedGamepadMask() != 0 ? "&remoteControllersBitmap=" + context.streamConfig.getAttachedGamepadMask() : "") +
|
||||
(context.streamConfig.getAttachedGamepadMask() != 0 ? "&gcmap=" + context.streamConfig.getAttachedGamepadMask() : ""),
|
||||
false);
|
||||
String gameSession = getXmlString(xmlStr, "gamesession");
|
||||
return gameSession != null && !gameSession.equals("0");
|
||||
(context.streamConfig.getAttachedGamepadMask() != 0 ? "&gcmap=" + context.streamConfig.getAttachedGamepadMask() : ""));
|
||||
if (!getXmlString(xmlStr, "gamesession", true).equals("0")) {
|
||||
// sessionUrl0 will be missing for older GFE versions
|
||||
context.rtspSessionUrl = getXmlString(xmlStr, "sessionUrl0", false);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean resumeApp(ConnectionContext context) throws IOException, XmlPullParserException {
|
||||
String xmlStr = openHttpConnectionToString(baseUrlHttps + "/resume?" + buildUniqueIdUuidString() +
|
||||
"&rikey="+bytesToHex(context.riKey.getEncoded()) +
|
||||
String xmlStr = openHttpConnectionToString(httpClientLongConnectNoReadTimeout, getHttpsUrl(true), "resume",
|
||||
"rikey="+bytesToHex(context.riKey.getEncoded()) +
|
||||
"&rikeyid="+context.riKeyId +
|
||||
"&surroundAudioInfo=" + context.streamConfig.getAudioConfiguration().getSurroundAudioInfo(),
|
||||
false);
|
||||
String resume = getXmlString(xmlStr, "resume");
|
||||
return Integer.parseInt(resume) != 0;
|
||||
"&surroundAudioInfo=" + context.streamConfig.getAudioConfiguration().getSurroundAudioInfo());
|
||||
if (!getXmlString(xmlStr, "resume", true).equals("0")) {
|
||||
// sessionUrl0 will be missing for older GFE versions
|
||||
context.rtspSessionUrl = getXmlString(xmlStr, "sessionUrl0", false);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean quitApp() throws IOException, XmlPullParserException {
|
||||
String xmlStr = openHttpConnectionToString(baseUrlHttps + "/cancel?" + buildUniqueIdUuidString(), false);
|
||||
String cancel = getXmlString(xmlStr, "cancel");
|
||||
if (Integer.parseInt(cancel) == 0) {
|
||||
String xmlStr = openHttpConnectionToString(httpClientLongConnectNoReadTimeout, getHttpsUrl(true), "cancel");
|
||||
if (getXmlString(xmlStr, "cancel", true).equals("0")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Newer GFE versions will just return success even if quitting fails
|
||||
// if we're not the original requestor.
|
||||
if (getCurrentGame(getServerInfo()) != 0) {
|
||||
if (getCurrentGame(getServerInfo(true)) != 0) {
|
||||
// Generate a synthetic GfeResponseException letting the caller know
|
||||
// that they can't kill someone else's stream.
|
||||
throw new GfeHttpResponseException(599, "");
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.limelight.nvstream.http;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import org.bouncycastle.crypto.BlockCipher;
|
||||
import org.bouncycastle.crypto.engines.AESLightEngine;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
@@ -21,7 +21,6 @@ public class PairingManager {
|
||||
|
||||
private PrivateKey pk;
|
||||
private X509Certificate cert;
|
||||
private SecretKey aesKey;
|
||||
private byte[] pemCertBytes;
|
||||
|
||||
private X509Certificate serverCert;
|
||||
@@ -68,7 +67,8 @@ public class PairingManager {
|
||||
|
||||
private X509Certificate extractPlainCert(String text) throws XmlPullParserException, IOException
|
||||
{
|
||||
String certText = NvHTTP.getXmlString(text, "plaincert");
|
||||
// Plaincert may be null if another client is already trying to pair
|
||||
String certText = NvHTTP.getXmlString(text, "plaincert", false);
|
||||
if (certText != null) {
|
||||
byte[] certBytes = hexToBytes(certText);
|
||||
|
||||
@@ -124,43 +124,35 @@ public class PairingManager {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] decryptAes(byte[] encryptedData, SecretKey secretKey) {
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
|
||||
|
||||
int blockRoundedSize = ((encryptedData.length + 15) / 16) * 16;
|
||||
byte[] blockRoundedEncrypted = Arrays.copyOf(encryptedData, blockRoundedSize);
|
||||
byte[] fullDecrypted = new byte[blockRoundedSize];
|
||||
|
||||
cipher.init(Cipher.DECRYPT_MODE, secretKey);
|
||||
cipher.doFinal(blockRoundedEncrypted, 0,
|
||||
blockRoundedSize, fullDecrypted);
|
||||
return fullDecrypted;
|
||||
} catch (GeneralSecurityException e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
private static byte[] performBlockCipher(BlockCipher blockCipher, byte[] input) {
|
||||
int blockSize = blockCipher.getBlockSize();
|
||||
int blockRoundedSize = (input.length + (blockSize - 1)) & ~(blockSize - 1);
|
||||
|
||||
byte[] blockRoundedInputData = Arrays.copyOf(input, blockRoundedSize);
|
||||
byte[] blockRoundedOutputData = new byte[blockRoundedSize];
|
||||
|
||||
for (int offset = 0; offset < blockRoundedSize; offset += blockSize) {
|
||||
blockCipher.processBlock(blockRoundedInputData, offset, blockRoundedOutputData, offset);
|
||||
}
|
||||
|
||||
return blockRoundedOutputData;
|
||||
}
|
||||
|
||||
private static byte[] encryptAes(byte[] data, SecretKey secretKey) {
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
|
||||
|
||||
int blockRoundedSize = ((data.length + 15) / 16) * 16;
|
||||
byte[] blockRoundedData = Arrays.copyOf(data, blockRoundedSize);
|
||||
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
|
||||
return cipher.doFinal(blockRoundedData);
|
||||
} catch (GeneralSecurityException e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
private static byte[] decryptAes(byte[] encryptedData, byte[] aesKey) {
|
||||
BlockCipher aesEngine = new AESLightEngine();
|
||||
aesEngine.init(false, new KeyParameter(aesKey));
|
||||
return performBlockCipher(aesEngine, encryptedData);
|
||||
}
|
||||
|
||||
private static SecretKey generateAesKey(PairingHashAlgorithm hashAlgo, byte[] keyData) {
|
||||
byte[] aesTruncated = Arrays.copyOf(hashAlgo.hashData(keyData), 16);
|
||||
return new SecretKeySpec(aesTruncated, "AES");
|
||||
private static byte[] encryptAes(byte[] plaintextData, byte[] aesKey) {
|
||||
BlockCipher aesEngine = new AESLightEngine();
|
||||
aesEngine.init(true, new KeyParameter(aesKey));
|
||||
return performBlockCipher(aesEngine, plaintextData);
|
||||
}
|
||||
|
||||
private static byte[] generateAesKey(PairingHashAlgorithm hashAlgo, byte[] keyData) {
|
||||
return Arrays.copyOf(hashAlgo.hashData(keyData), 16);
|
||||
}
|
||||
|
||||
private static byte[] concatBytes(byte[] a, byte[] b) {
|
||||
@@ -199,16 +191,14 @@ public class PairingManager {
|
||||
byte[] salt = generateRandomBytes(16);
|
||||
|
||||
// Combine the salt and pin, then create an AES key from them
|
||||
byte[] saltAndPin = saltPin(salt, pin);
|
||||
aesKey = generateAesKey(hashAlgo, saltAndPin);
|
||||
byte[] aesKey = generateAesKey(hashAlgo, saltPin(salt, pin));
|
||||
|
||||
// Send the salt and get the server cert. This doesn't have a read timeout
|
||||
// because the user must enter the PIN before the server responds
|
||||
String getCert = http.openHttpConnectionToString(http.baseUrlHttp +
|
||||
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&phrase=getservercert&salt="+
|
||||
String getCert = http.executePairingCommand("phrase=getservercert&salt="+
|
||||
bytesToHex(salt)+"&clientcert="+bytesToHex(pemCertBytes),
|
||||
false);
|
||||
if (!NvHTTP.getXmlString(getCert, "paired").equals("1")) {
|
||||
if (!NvHTTP.getXmlString(getCert, "paired", true).equals("1")) {
|
||||
return PairState.FAILED;
|
||||
}
|
||||
|
||||
@@ -217,7 +207,7 @@ public class PairingManager {
|
||||
if (serverCert == null) {
|
||||
// Attempting to pair while another device is pairing will cause GFE
|
||||
// to give an empty cert in the response.
|
||||
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
|
||||
http.unpair();
|
||||
return PairState.ALREADY_IN_PROGRESS;
|
||||
}
|
||||
|
||||
@@ -229,16 +219,14 @@ public class PairingManager {
|
||||
byte[] encryptedChallenge = encryptAes(randomChallenge, aesKey);
|
||||
|
||||
// Send the encrypted challenge to the server
|
||||
String challengeResp = http.openHttpConnectionToString(http.baseUrlHttp +
|
||||
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&clientchallenge="+bytesToHex(encryptedChallenge),
|
||||
true);
|
||||
if (!NvHTTP.getXmlString(challengeResp, "paired").equals("1")) {
|
||||
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
|
||||
String challengeResp = http.executePairingCommand("clientchallenge="+bytesToHex(encryptedChallenge), true);
|
||||
if (!NvHTTP.getXmlString(challengeResp, "paired", true).equals("1")) {
|
||||
http.unpair();
|
||||
return PairState.FAILED;
|
||||
}
|
||||
|
||||
// Decode the server's response and subsequent challenge
|
||||
byte[] encServerChallengeResponse = hexToBytes(NvHTTP.getXmlString(challengeResp, "challengeresponse"));
|
||||
byte[] encServerChallengeResponse = hexToBytes(NvHTTP.getXmlString(challengeResp, "challengeresponse", true));
|
||||
byte[] decServerChallengeResponse = decryptAes(encServerChallengeResponse, aesKey);
|
||||
|
||||
byte[] serverResponse = Arrays.copyOfRange(decServerChallengeResponse, 0, hashAlgo.getHashLength());
|
||||
@@ -248,23 +236,21 @@ public class PairingManager {
|
||||
byte[] clientSecret = generateRandomBytes(16);
|
||||
byte[] challengeRespHash = hashAlgo.hashData(concatBytes(concatBytes(serverChallenge, cert.getSignature()), clientSecret));
|
||||
byte[] challengeRespEncrypted = encryptAes(challengeRespHash, aesKey);
|
||||
String secretResp = http.openHttpConnectionToString(http.baseUrlHttp +
|
||||
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&serverchallengeresp="+bytesToHex(challengeRespEncrypted),
|
||||
true);
|
||||
if (!NvHTTP.getXmlString(secretResp, "paired").equals("1")) {
|
||||
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
|
||||
String secretResp = http.executePairingCommand("serverchallengeresp="+bytesToHex(challengeRespEncrypted), true);
|
||||
if (!NvHTTP.getXmlString(secretResp, "paired", true).equals("1")) {
|
||||
http.unpair();
|
||||
return PairState.FAILED;
|
||||
}
|
||||
|
||||
// Get the server's signed secret
|
||||
byte[] serverSecretResp = hexToBytes(NvHTTP.getXmlString(secretResp, "pairingsecret"));
|
||||
byte[] serverSecretResp = hexToBytes(NvHTTP.getXmlString(secretResp, "pairingsecret", true));
|
||||
byte[] serverSecret = Arrays.copyOfRange(serverSecretResp, 0, 16);
|
||||
byte[] serverSignature = Arrays.copyOfRange(serverSecretResp, 16, 272);
|
||||
|
||||
// Ensure the authenticity of the data
|
||||
if (!verifySignature(serverSecret, serverSignature, serverCert)) {
|
||||
// Cancel the pairing process
|
||||
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
|
||||
http.unpair();
|
||||
|
||||
// Looks like a MITM
|
||||
return PairState.FAILED;
|
||||
@@ -274,7 +260,7 @@ public class PairingManager {
|
||||
byte[] serverChallengeRespHash = hashAlgo.hashData(concatBytes(concatBytes(randomChallenge, serverCert.getSignature()), serverSecret));
|
||||
if (!Arrays.equals(serverChallengeRespHash, serverResponse)) {
|
||||
// Cancel the pairing process
|
||||
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
|
||||
http.unpair();
|
||||
|
||||
// Probably got the wrong PIN
|
||||
return PairState.PIN_WRONG;
|
||||
@@ -282,19 +268,16 @@ public class PairingManager {
|
||||
|
||||
// Send the server our signed secret
|
||||
byte[] clientPairingSecret = concatBytes(clientSecret, signData(clientSecret, pk));
|
||||
String clientSecretResp = http.openHttpConnectionToString(http.baseUrlHttp +
|
||||
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&clientpairingsecret="+bytesToHex(clientPairingSecret),
|
||||
true);
|
||||
if (!NvHTTP.getXmlString(clientSecretResp, "paired").equals("1")) {
|
||||
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
|
||||
String clientSecretResp = http.executePairingCommand("clientpairingsecret="+bytesToHex(clientPairingSecret), true);
|
||||
if (!NvHTTP.getXmlString(clientSecretResp, "paired", true).equals("1")) {
|
||||
http.unpair();
|
||||
return PairState.FAILED;
|
||||
}
|
||||
|
||||
// Do the initial challenge (seems neccessary for us to show as paired)
|
||||
String pairChallenge = http.openHttpConnectionToString(http.baseUrlHttps +
|
||||
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&phrase=pairchallenge", true);
|
||||
if (!NvHTTP.getXmlString(pairChallenge, "paired").equals("1")) {
|
||||
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
|
||||
// Do the initial challenge (seems necessary for us to show as paired)
|
||||
String pairChallenge = http.executePairingChallenge();
|
||||
if (!NvHTTP.getXmlString(pairChallenge, "paired", true).equals("1")) {
|
||||
http.unpair();
|
||||
return PairState.FAILED;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,4 +7,5 @@ public class KeyboardPacket {
|
||||
public static final byte MODIFIER_SHIFT = 0x01;
|
||||
public static final byte MODIFIER_CTRL = 0x02;
|
||||
public static final byte MODIFIER_ALT = 0x04;
|
||||
}
|
||||
public static final byte MODIFIER_META = 0x08;
|
||||
}
|
||||
@@ -27,6 +27,16 @@ public class MoonBridge {
|
||||
public static final int BUFFER_TYPE_PPS = 2;
|
||||
public static final int BUFFER_TYPE_VPS = 3;
|
||||
|
||||
public static final int FRAME_TYPE_PFRAME = 0;
|
||||
public static final int FRAME_TYPE_IDR = 1;
|
||||
|
||||
public static final int COLORSPACE_REC_601 = 0;
|
||||
public static final int COLORSPACE_REC_709 = 1;
|
||||
public static final int COLORSPACE_REC_2020 = 2;
|
||||
|
||||
public static final int COLOR_RANGE_LIMITED = 0;
|
||||
public static final int COLOR_RANGE_FULL = 1;
|
||||
|
||||
public static final int CAPABILITY_DIRECT_SUBMIT = 1;
|
||||
public static final int CAPABILITY_REFERENCE_FRAME_INVALIDATION_AVC = 2;
|
||||
public static final int CAPABILITY_REFERENCE_FRAME_INVALIDATION_HEVC = 4;
|
||||
@@ -42,6 +52,7 @@ public class MoonBridge {
|
||||
public static final int ML_ERROR_NO_VIDEO_FRAME = -101;
|
||||
public static final int ML_ERROR_UNEXPECTED_EARLY_TERMINATION = -102;
|
||||
public static final int ML_ERROR_PROTECTED_CONTENT = -103;
|
||||
public static final int ML_ERROR_FRAME_CONVERSION = -104;
|
||||
|
||||
public static final int ML_PORT_INDEX_TCP_47984 = 0;
|
||||
public static final int ML_PORT_INDEX_TCP_47989 = 1;
|
||||
@@ -153,12 +164,12 @@ public class MoonBridge {
|
||||
}
|
||||
}
|
||||
|
||||
public static int bridgeDrSubmitDecodeUnit(byte[] decodeUnitData, int decodeUnitLength,
|
||||
int decodeUnitType, int frameNumber,
|
||||
public static int bridgeDrSubmitDecodeUnit(byte[] decodeUnitData, int decodeUnitLength, int decodeUnitType,
|
||||
int frameNumber, int frameType,
|
||||
long receiveTimeMs, long enqueueTimeMs) {
|
||||
if (videoRenderer != null) {
|
||||
return videoRenderer.submitDecodeUnit(decodeUnitData, decodeUnitLength,
|
||||
decodeUnitType, frameNumber, receiveTimeMs, enqueueTimeMs);
|
||||
decodeUnitType, frameNumber, frameType, receiveTimeMs, enqueueTimeMs);
|
||||
}
|
||||
else {
|
||||
return DR_OK;
|
||||
@@ -240,6 +251,12 @@ public class MoonBridge {
|
||||
}
|
||||
}
|
||||
|
||||
public static void bridgeClSetHdrMode(boolean enabled) {
|
||||
if (connectionListener != null) {
|
||||
connectionListener.setHdrMode(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setupBridge(VideoDecoderRenderer videoRenderer, AudioRenderer audioRenderer, NvConnectionListener connectionListener) {
|
||||
MoonBridge.videoRenderer = videoRenderer;
|
||||
MoonBridge.audioRenderer = audioRenderer;
|
||||
@@ -253,6 +270,7 @@ public class MoonBridge {
|
||||
}
|
||||
|
||||
public static native int startConnection(String address, String appVersion, String gfeVersion,
|
||||
String rtspSessionUrl,
|
||||
int width, int height, int fps,
|
||||
int bitrate, int packetSize, int streamingRemotely,
|
||||
int audioConfiguration, boolean supportsHevc,
|
||||
@@ -261,7 +279,8 @@ public class MoonBridge {
|
||||
int clientRefreshRateX100,
|
||||
int encryptionFlags,
|
||||
byte[] riAesKey, byte[] riAesIv,
|
||||
int videoCapabilities);
|
||||
int videoCapabilities,
|
||||
int colorSpace, int colorRange);
|
||||
|
||||
public static native void stopConnection();
|
||||
|
||||
@@ -271,6 +290,8 @@ public class MoonBridge {
|
||||
|
||||
public static native void sendMousePosition(short x, short y, short referenceWidth, short referenceHeight);
|
||||
|
||||
public static native void sendMouseMoveAsMousePosition(short deltaX, short deltaY, short referenceWidth, short referenceHeight);
|
||||
|
||||
public static native void sendMouseButton(byte buttonEvent, byte mouseButton);
|
||||
|
||||
public static native void sendMultiControllerInput(short controllerNumber,
|
||||
@@ -290,6 +311,8 @@ public class MoonBridge {
|
||||
|
||||
public static native void sendMouseHighResScroll(short scrollAmount);
|
||||
|
||||
public static native void sendUtf8Text(String text);
|
||||
|
||||
public static native String getStageName(int stage);
|
||||
|
||||
public static native String findExternalAddressIP4(String stunHostName, int stunPort);
|
||||
|
||||
@@ -6,12 +6,14 @@ import java.net.InetAddress;
|
||||
public class MdnsComputer {
|
||||
private InetAddress localAddr;
|
||||
private Inet6Address v6Addr;
|
||||
private int port;
|
||||
private String name;
|
||||
|
||||
public MdnsComputer(String name, InetAddress localAddress, Inet6Address v6Addr) {
|
||||
public MdnsComputer(String name, InetAddress localAddress, Inet6Address v6Addr, int port) {
|
||||
this.name = name;
|
||||
this.localAddr = localAddress;
|
||||
this.v6Addr = v6Addr;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
@@ -26,6 +28,10 @@ public class MdnsComputer {
|
||||
return v6Addr;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return name.hashCode();
|
||||
@@ -36,7 +42,7 @@ public class MdnsComputer {
|
||||
if (o instanceof MdnsComputer) {
|
||||
MdnsComputer other = (MdnsComputer)o;
|
||||
|
||||
if (!other.name.equals(name)) {
|
||||
if (!other.name.equals(name) || other.port != port) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -260,7 +260,7 @@ public class MdnsDiscoveryAgent implements ServiceListener {
|
||||
// Add a computer object for each IPv4 address reported by the PC
|
||||
for (Inet4Address v4Addr : v4Addrs) {
|
||||
synchronized (computers) {
|
||||
MdnsComputer computer = new MdnsComputer(info.getName(), v4Addr, v6GlobalAddr);
|
||||
MdnsComputer computer = new MdnsComputer(info.getName(), v4Addr, v6GlobalAddr, info.getPort());
|
||||
if (computers.put(computer.getLocalAddress(), computer) == null) {
|
||||
// This was a new entry
|
||||
listener.notifyComputerAdded(computer);
|
||||
@@ -273,7 +273,7 @@ public class MdnsDiscoveryAgent implements ServiceListener {
|
||||
Inet6Address v6LocalAddr = getLocalAddress(v6Addrs);
|
||||
|
||||
if (v6LocalAddr != null || v6GlobalAddr != null) {
|
||||
MdnsComputer computer = new MdnsComputer(info.getName(), v6LocalAddr, v6GlobalAddr);
|
||||
MdnsComputer computer = new MdnsComputer(info.getName(), v6LocalAddr, v6GlobalAddr, info.getPort());
|
||||
if (computers.put(v6LocalAddr != null ?
|
||||
computer.getLocalAddress() : computer.getIpv6Address(), computer) == null) {
|
||||
// This was a new entry
|
||||
|
||||
@@ -10,39 +10,75 @@ import com.limelight.LimeLog;
|
||||
import com.limelight.nvstream.http.ComputerDetails;
|
||||
|
||||
public class WakeOnLanSender {
|
||||
private static final int[] PORTS_TO_TRY = new int[] {
|
||||
// These ports will always be tried as-is.
|
||||
private static final int[] STATIC_PORTS_TO_TRY = new int[] {
|
||||
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)
|
||||
};
|
||||
|
||||
// These ports will be offset by the base port number (47989) to support alternate ports.
|
||||
private static final int[] DYNAMIC_PORTS_TO_TRY = new int[] {
|
||||
47998, 47999, 48000, 48002, 48010, // Ports opened by GFE
|
||||
};
|
||||
|
||||
private static void sendPacketsForAddress(InetAddress address, int httpPort, DatagramSocket sock, byte[] payload) throws IOException {
|
||||
IOException lastException = null;
|
||||
boolean sentWolPacket = false;
|
||||
|
||||
// Try the static ports
|
||||
for (int port : STATIC_PORTS_TO_TRY) {
|
||||
try {
|
||||
DatagramPacket dp = new DatagramPacket(payload, payload.length);
|
||||
dp.setAddress(address);
|
||||
dp.setPort(port);
|
||||
sock.send(dp);
|
||||
sentWolPacket = true;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
lastException = e;
|
||||
}
|
||||
}
|
||||
|
||||
// Try the dynamic ports
|
||||
for (int port : DYNAMIC_PORTS_TO_TRY) {
|
||||
try {
|
||||
DatagramPacket dp = new DatagramPacket(payload, payload.length);
|
||||
dp.setAddress(address);
|
||||
dp.setPort((port - 47989) + httpPort);
|
||||
sock.send(dp);
|
||||
sentWolPacket = true;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
lastException = e;
|
||||
}
|
||||
}
|
||||
|
||||
if (!sentWolPacket) {
|
||||
throw lastException;
|
||||
}
|
||||
}
|
||||
|
||||
public static void sendWolPacket(ComputerDetails computer) throws IOException {
|
||||
DatagramSocket sock = new DatagramSocket(0);
|
||||
byte[] payload = createWolPayload(computer);
|
||||
IOException lastException = null;
|
||||
boolean sentWolPacket = false;
|
||||
|
||||
try {
|
||||
// Try all resolved remote and local addresses and IPv4 broadcast address.
|
||||
try (final DatagramSocket sock = new DatagramSocket(0)) {
|
||||
// Try all resolved remote and local addresses and broadcast addresses.
|
||||
// The broadcast address is required to avoid stale ARP cache entries
|
||||
// making the sleeping machine unreachable.
|
||||
for (String unresolvedAddress : new String[] {
|
||||
computer.localAddress, computer.remoteAddress, computer.manualAddress, computer.ipv6Address, "255.255.255.255"
|
||||
for (ComputerDetails.AddressTuple address : new ComputerDetails.AddressTuple[] {
|
||||
computer.localAddress, computer.remoteAddress,
|
||||
computer.manualAddress, computer.ipv6Address,
|
||||
}) {
|
||||
if (unresolvedAddress == null) {
|
||||
if (address == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
for (InetAddress resolvedAddress : InetAddress.getAllByName(unresolvedAddress)) {
|
||||
// Try all the ports for each resolved address
|
||||
for (int port : PORTS_TO_TRY) {
|
||||
DatagramPacket dp = new DatagramPacket(payload, payload.length);
|
||||
dp.setAddress(resolvedAddress);
|
||||
dp.setPort(port);
|
||||
sock.send(dp);
|
||||
sentWolPacket = true;
|
||||
}
|
||||
sendPacketsForAddress(InetAddress.getByName("255.255.255.255"), address.port, sock, payload);
|
||||
for (InetAddress resolvedAddress : InetAddress.getAllByName(address.address)) {
|
||||
sendPacketsForAddress(resolvedAddress, address.port, sock, payload);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// We may have addresses that don't resolve on this subnet,
|
||||
@@ -52,8 +88,6 @@ public class WakeOnLanSender {
|
||||
lastException = e;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
sock.close();
|
||||
}
|
||||
|
||||
// Propagate the DNS resolution exception if we didn't
|
||||
@@ -65,18 +99,20 @@ public class WakeOnLanSender {
|
||||
|
||||
private static byte[] macStringToBytes(String macAddress) {
|
||||
byte[] macBytes = new byte[6];
|
||||
@SuppressWarnings("resource")
|
||||
Scanner scan = new Scanner(macAddress).useDelimiter(":");
|
||||
for (int i = 0; i < macBytes.length && scan.hasNext(); i++) {
|
||||
try {
|
||||
macBytes[i] = (byte) Integer.parseInt(scan.next(), 16);
|
||||
} catch (NumberFormatException e) {
|
||||
LimeLog.warning("Malformed MAC address: "+macAddress+" (index: "+i+")");
|
||||
break;
|
||||
|
||||
try (@SuppressWarnings("resource")
|
||||
final Scanner scan = new Scanner(macAddress).useDelimiter(":")
|
||||
) {
|
||||
for (int i = 0; i < macBytes.length && scan.hasNext(); i++) {
|
||||
try {
|
||||
macBytes[i] = (byte) Integer.parseInt(scan.next(), 16);
|
||||
} catch (NumberFormatException e) {
|
||||
LimeLog.warning("Malformed MAC address: " + macAddress + " (index: " + i + ")");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return macBytes;
|
||||
}
|
||||
scan.close();
|
||||
return macBytes;
|
||||
}
|
||||
|
||||
private static byte[] createWolPayload(ComputerDetails computer) {
|
||||
|
||||
@@ -6,6 +6,8 @@ import java.net.InetAddress;
|
||||
import java.net.InterfaceAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
@@ -88,16 +90,39 @@ public class AddComputerManually extends Activity {
|
||||
|
||||
// Couldn't find a matching interface
|
||||
return true;
|
||||
} catch (SocketException e) {
|
||||
} catch (Exception e) {
|
||||
// Catch all exceptions because some broken Android devices
|
||||
// will throw an NPE from inside getNetworkInterfaces().
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
} catch (UnknownHostException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void doAddPc(String host) {
|
||||
private URI parseRawUserInputToUri(String rawUserInput) {
|
||||
try {
|
||||
// Try adding a scheme and parsing the remaining input.
|
||||
// This handles input like 127.0.0.1:47989, [::1], [::1]:47989, and 127.0.0.1.
|
||||
URI uri = new URI("moonlight://" + rawUserInput);
|
||||
if (uri.getHost() != null && !uri.getHost().isEmpty()) {
|
||||
return uri;
|
||||
}
|
||||
} catch (URISyntaxException ignored) {}
|
||||
|
||||
try {
|
||||
// Attempt to escape the input as an IPv6 literal.
|
||||
// This handles input like ::1.
|
||||
URI uri = new URI("moonlight://[" + rawUserInput + "]");
|
||||
if (uri.getHost() != null && !uri.getHost().isEmpty()) {
|
||||
return uri;
|
||||
}
|
||||
} catch (URISyntaxException ignored) {}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void doAddPc(String rawUserInput) throws InterruptedException {
|
||||
boolean wrongSiteLocal = false;
|
||||
boolean invalidInput = false;
|
||||
boolean success;
|
||||
int portTestResult;
|
||||
|
||||
@@ -106,18 +131,42 @@ public class AddComputerManually extends Activity {
|
||||
|
||||
try {
|
||||
ComputerDetails details = new ComputerDetails();
|
||||
details.manualAddress = host;
|
||||
success = managerBinder.addComputerBlocking(details);
|
||||
|
||||
// Check if we parsed a host address successfully
|
||||
URI uri = parseRawUserInputToUri(rawUserInput);
|
||||
if (uri != null && uri.getHost() != null && !uri.getHost().isEmpty()) {
|
||||
String host = uri.getHost();
|
||||
int port = uri.getPort();
|
||||
|
||||
// If a port was not specified, use the default
|
||||
if (port == -1) {
|
||||
port = NvHTTP.DEFAULT_HTTP_PORT;
|
||||
}
|
||||
|
||||
details.manualAddress = new ComputerDetails.AddressTuple(host, port);
|
||||
success = managerBinder.addComputerBlocking(details);
|
||||
if (!success){
|
||||
wrongSiteLocal = isWrongSubnetSiteLocalAddress(host);
|
||||
}
|
||||
} else {
|
||||
// Invalid user input
|
||||
success = false;
|
||||
invalidInput = true;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// Propagate the InterruptedException to the caller for proper handling
|
||||
dialog.dismiss();
|
||||
throw e;
|
||||
} catch (IllegalArgumentException e) {
|
||||
// This can be thrown from OkHttp if the host fails to canonicalize to a valid name.
|
||||
// https://github.com/square/okhttp/blob/okhttp_27/okhttp/src/main/java/com/squareup/okhttp/HttpUrl.java#L705
|
||||
e.printStackTrace();
|
||||
success = false;
|
||||
invalidInput = true;
|
||||
}
|
||||
if (!success){
|
||||
wrongSiteLocal = isWrongSubnetSiteLocalAddress(host);
|
||||
}
|
||||
if (!success && !wrongSiteLocal) {
|
||||
|
||||
// Keep the SpinnerDialog open while testing connectivity
|
||||
if (!success && !wrongSiteLocal && !invalidInput) {
|
||||
// Run the test before dismissing the spinner because it can take a few seconds.
|
||||
portTestResult = MoonBridge.testClientConnectivity(ServerHelper.CONNECTION_TEST_SERVER, 443,
|
||||
MoonBridge.ML_PORT_FLAG_TCP_47984 | MoonBridge.ML_PORT_FLAG_TCP_47989);
|
||||
@@ -128,7 +177,10 @@ public class AddComputerManually extends Activity {
|
||||
|
||||
dialog.dismiss();
|
||||
|
||||
if (wrongSiteLocal) {
|
||||
if (invalidInput) {
|
||||
Dialog.displayDialog(this, getResources().getString(R.string.conn_error_title), getResources().getString(R.string.addpc_unknown_host), false);
|
||||
}
|
||||
else if (wrongSiteLocal) {
|
||||
Dialog.displayDialog(this, getResources().getString(R.string.conn_error_title), getResources().getString(R.string.addpc_wrong_sitelocal), false);
|
||||
}
|
||||
else if (!success) {
|
||||
@@ -162,15 +214,12 @@ public class AddComputerManually extends Activity {
|
||||
@Override
|
||||
public void run() {
|
||||
while (!isInterrupted()) {
|
||||
String computer;
|
||||
|
||||
try {
|
||||
computer = computersToAdd.take();
|
||||
String computer = computersToAdd.take();
|
||||
doAddPc(computer);
|
||||
} catch (InterruptedException e) {
|
||||
return;
|
||||
}
|
||||
|
||||
doAddPc(computer);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -184,7 +233,14 @@ public class AddComputerManually extends Activity {
|
||||
|
||||
try {
|
||||
addThread.join();
|
||||
} catch (InterruptedException ignored) {}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
|
||||
// InterruptedException clears the thread's interrupt status. Since we can't
|
||||
// handle that here, we will re-interrupt the thread to set the interrupt
|
||||
// status back to true.
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
addThread = null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.limelight.preferences;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.preference.ListPreference;
|
||||
import android.provider.Settings;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
public class LanguagePreference extends ListPreference {
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public LanguagePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public LanguagePreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public LanguagePreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public LanguagePreference(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onClick() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
try {
|
||||
// Launch the Android native app locale settings page
|
||||
Intent intent = new Intent(Settings.ACTION_APP_LOCALE_SETTINGS);
|
||||
intent.addCategory(Intent.CATEGORY_DEFAULT);
|
||||
intent.setData(Uri.parse("package:" + getContext().getPackageName()));
|
||||
getContext().startActivity(intent, null);
|
||||
return;
|
||||
} catch (ActivityNotFoundException e) {
|
||||
// App locale settings should be present on all Android 13 devices,
|
||||
// but if not, we'll launch the old language chooser.
|
||||
}
|
||||
}
|
||||
|
||||
// If we don't have native app locale settings, launch the normal dialog
|
||||
super.onClick();
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.view.Display;
|
||||
|
||||
import com.limelight.nvstream.jni.MoonBridge;
|
||||
|
||||
@@ -30,7 +31,7 @@ public class PreferenceConfiguration {
|
||||
private static final String VIDEO_FORMAT_PREF_STRING = "video_format";
|
||||
private static final String ONSCREEN_CONTROLLER_PREF_STRING = "checkbox_show_onscreen_controls";
|
||||
private static final String ONLY_L3_R3_PREF_STRING = "checkbox_only_show_L3R3";
|
||||
private static final String DISABLE_FRAME_DROP_PREF_STRING = "checkbox_disable_frame_drop";
|
||||
private static final String LEGACY_DISABLE_FRAME_DROP_PREF_STRING = "checkbox_disable_frame_drop";
|
||||
private static final String ENABLE_HDR_PREF_STRING = "checkbox_enable_hdr";
|
||||
private static final String ENABLE_PIP_PREF_STRING = "checkbox_enable_pip";
|
||||
private static final String ENABLE_PERF_OVERLAY_STRING = "checkbox_enable_perf_overlay";
|
||||
@@ -43,6 +44,11 @@ public class PreferenceConfiguration {
|
||||
private static final String FLIP_FACE_BUTTONS_PREF_STRING = "checkbox_flip_face_buttons";
|
||||
private static final String TOUCHSCREEN_TRACKPAD_PREF_STRING = "checkbox_touchscreen_trackpad";
|
||||
private static final String LATENCY_TOAST_PREF_STRING = "checkbox_enable_post_stream_toast";
|
||||
private static final String FRAME_PACING_PREF_STRING = "frame_pacing";
|
||||
private static final String ABSOLUTE_MOUSE_MODE_PREF_STRING = "checkbox_absolute_mouse_mode";
|
||||
private static final String ENABLE_AUDIO_FX_PREF_STRING = "checkbox_enable_audiofx";
|
||||
private static final String REDUCE_REFRESH_RATE_PREF_STRING = "checkbox_reduce_refresh_rate";
|
||||
private static final String FULL_RANGE_PREF_STRING = "checkbox_full_range";
|
||||
|
||||
static final String DEFAULT_RESOLUTION = "1280x720";
|
||||
static final String DEFAULT_FPS = "60";
|
||||
@@ -50,7 +56,7 @@ public class PreferenceConfiguration {
|
||||
private static final boolean DEFAULT_SOPS = true;
|
||||
private static final boolean DEFAULT_DISABLE_TOASTS = false;
|
||||
private static final boolean DEFAULT_HOST_AUDIO = false;
|
||||
private static final int DEFAULT_DEADZONE = 15;
|
||||
private static final int DEFAULT_DEADZONE = 7;
|
||||
private static final int DEFAULT_OPACITY = 90;
|
||||
public static final String DEFAULT_LANGUAGE = "default";
|
||||
private static final boolean DEFAULT_MULTI_CONTROLLER = true;
|
||||
@@ -58,7 +64,6 @@ public class PreferenceConfiguration {
|
||||
private static final String DEFAULT_VIDEO_FORMAT = "auto";
|
||||
private static final boolean ONSCREEN_CONTROLLER_DEFAULT = false;
|
||||
private static final boolean ONLY_L3_R3_DEFAULT = false;
|
||||
private static final boolean DEFAULT_DISABLE_FRAME_DROP = false;
|
||||
private static final boolean DEFAULT_ENABLE_HDR = false;
|
||||
private static final boolean DEFAULT_ENABLE_PIP = false;
|
||||
private static final boolean DEFAULT_ENABLE_PERF_OVERLAY = false;
|
||||
@@ -72,11 +77,21 @@ public class PreferenceConfiguration {
|
||||
private static final boolean DEFAULT_TOUCHSCREEN_TRACKPAD = true;
|
||||
private static final String DEFAULT_AUDIO_CONFIG = "2"; // Stereo
|
||||
private static final boolean DEFAULT_LATENCY_TOAST = false;
|
||||
private static final String DEFAULT_FRAME_PACING = "latency";
|
||||
private static final boolean DEFAULT_ABSOLUTE_MOUSE_MODE = false;
|
||||
private static final boolean DEFAULT_ENABLE_AUDIO_FX = false;
|
||||
private static final boolean DEFAULT_REDUCE_REFRESH_RATE = false;
|
||||
private static final boolean DEFAULT_FULL_RANGE = false;
|
||||
|
||||
public static final int FORCE_H265_ON = -1;
|
||||
public static final int AUTOSELECT_H265 = 0;
|
||||
public static final int FORCE_H265_OFF = 1;
|
||||
|
||||
public static final int FRAME_PACING_MIN_LATENCY = 0;
|
||||
public static final int FRAME_PACING_BALANCED = 1;
|
||||
public static final int FRAME_PACING_CAP_FPS = 2;
|
||||
public static final int FRAME_PACING_MAX_SMOOTHNESS = 3;
|
||||
|
||||
public static final String RES_360P = "640x360";
|
||||
public static final String RES_480P = "854x480";
|
||||
public static final String RES_720P = "1280x720";
|
||||
@@ -95,7 +110,6 @@ public class PreferenceConfiguration {
|
||||
public boolean smallIconMode, multiController, usbDriver, flipFaceButtons;
|
||||
public boolean onscreenController;
|
||||
public boolean onlyL3R3;
|
||||
public boolean disableFrameDrop;
|
||||
public boolean enableHdr;
|
||||
public boolean enablePip;
|
||||
public boolean enablePerfOverlay;
|
||||
@@ -108,6 +122,11 @@ public class PreferenceConfiguration {
|
||||
public boolean vibrateFallbackToDevice;
|
||||
public boolean touchscreenTrackpad;
|
||||
public MoonBridge.AudioConfiguration audioConfiguration;
|
||||
public int framePacing;
|
||||
public boolean absoluteMouseMode;
|
||||
public boolean enableAudioFx;
|
||||
public boolean reduceRefreshRate;
|
||||
public boolean fullRange;
|
||||
|
||||
public static boolean isNativeResolution(int width, int height) {
|
||||
// It's not a native resolution if it matches an existing resolution option
|
||||
@@ -133,6 +152,31 @@ public class PreferenceConfiguration {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we have a screen that has semi-square dimensions, we may want to change our behavior
|
||||
// to allow any orientation and vertical+horizontal resolutions.
|
||||
public static boolean isSquarishScreen(int width, int height) {
|
||||
float longDim = Math.max(width, height);
|
||||
float shortDim = Math.min(width, height);
|
||||
|
||||
// We just put the arbitrary cutoff for a square-ish screen at 1.3
|
||||
return longDim / shortDim < 1.3f;
|
||||
}
|
||||
|
||||
public static boolean isSquarishScreen(Display display) {
|
||||
int width, height;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
width = display.getMode().getPhysicalWidth();
|
||||
height = display.getMode().getPhysicalHeight();
|
||||
}
|
||||
else {
|
||||
width = display.getWidth();
|
||||
height = display.getHeight();
|
||||
}
|
||||
|
||||
return isSquarishScreen(width, height);
|
||||
}
|
||||
|
||||
private static String convertFromLegacyResolutionString(String resString) {
|
||||
if (resString.equalsIgnoreCase("360p")) {
|
||||
return RES_360P;
|
||||
@@ -264,6 +308,37 @@ public class PreferenceConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
private static int getFramePacingValue(Context context) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
||||
// Migrate legacy never drop frames option to the new location
|
||||
if (prefs.contains(LEGACY_DISABLE_FRAME_DROP_PREF_STRING)) {
|
||||
boolean legacyNeverDropFrames = prefs.getBoolean(LEGACY_DISABLE_FRAME_DROP_PREF_STRING, false);
|
||||
prefs.edit()
|
||||
.remove(LEGACY_DISABLE_FRAME_DROP_PREF_STRING)
|
||||
.putString(FRAME_PACING_PREF_STRING, legacyNeverDropFrames ? "balanced" : "latency")
|
||||
.apply();
|
||||
}
|
||||
|
||||
String str = prefs.getString(FRAME_PACING_PREF_STRING, DEFAULT_FRAME_PACING);
|
||||
if (str.equals("latency")) {
|
||||
return FRAME_PACING_MIN_LATENCY;
|
||||
}
|
||||
else if (str.equals("balanced")) {
|
||||
return FRAME_PACING_BALANCED;
|
||||
}
|
||||
else if (str.equals("cap-fps")) {
|
||||
return FRAME_PACING_CAP_FPS;
|
||||
}
|
||||
else if (str.equals("smoothness")) {
|
||||
return FRAME_PACING_MAX_SMOOTHNESS;
|
||||
}
|
||||
else {
|
||||
// Should never get here
|
||||
return FRAME_PACING_MIN_LATENCY;
|
||||
}
|
||||
}
|
||||
|
||||
public static void resetStreamingSettings(Context context) {
|
||||
// We consider resolution, FPS, bitrate, HDR, and video format as "streaming settings" here
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
@@ -276,9 +351,23 @@ public class PreferenceConfiguration {
|
||||
.remove(VIDEO_FORMAT_PREF_STRING)
|
||||
.remove(ENABLE_HDR_PREF_STRING)
|
||||
.remove(UNLOCK_FPS_STRING)
|
||||
.remove(FULL_RANGE_PREF_STRING)
|
||||
.apply();
|
||||
}
|
||||
|
||||
public static void completeLanguagePreferenceMigration(Context context) {
|
||||
// Put our language option back to default which tells us that we've already migrated it
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
prefs.edit().putString(LANGUAGE_PREF_STRING, DEFAULT_LANGUAGE).apply();
|
||||
}
|
||||
|
||||
public static boolean isShieldAtvFirmwareWithBrokenHdr() {
|
||||
// This particular Shield TV firmware crashes when using HDR
|
||||
// https://www.nvidia.com/en-us/geforce/forums/notifications/comment/155192/
|
||||
return Build.MANUFACTURER.equalsIgnoreCase("NVIDIA") &&
|
||||
Build.FINGERPRINT.contains("PPR1.180610.011/4079208_2235.1395");
|
||||
}
|
||||
|
||||
public static PreferenceConfiguration readPreferences(Context context) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
PreferenceConfiguration config = new PreferenceConfiguration();
|
||||
@@ -387,6 +476,7 @@ public class PreferenceConfiguration {
|
||||
}
|
||||
|
||||
config.videoFormat = getVideoFormatValue(context);
|
||||
config.framePacing = getFramePacingValue(context);
|
||||
|
||||
config.deadzonePercentage = prefs.getInt(DEADZONE_PREF_STRING, DEFAULT_DEADZONE);
|
||||
|
||||
@@ -404,8 +494,7 @@ public class PreferenceConfiguration {
|
||||
config.usbDriver = prefs.getBoolean(USB_DRIVER_PREF_SRING, DEFAULT_USB_DRIVER);
|
||||
config.onscreenController = prefs.getBoolean(ONSCREEN_CONTROLLER_PREF_STRING, ONSCREEN_CONTROLLER_DEFAULT);
|
||||
config.onlyL3R3 = prefs.getBoolean(ONLY_L3_R3_PREF_STRING, ONLY_L3_R3_DEFAULT);
|
||||
config.disableFrameDrop = prefs.getBoolean(DISABLE_FRAME_DROP_PREF_STRING, DEFAULT_DISABLE_FRAME_DROP);
|
||||
config.enableHdr = prefs.getBoolean(ENABLE_HDR_PREF_STRING, DEFAULT_ENABLE_HDR);
|
||||
config.enableHdr = prefs.getBoolean(ENABLE_HDR_PREF_STRING, DEFAULT_ENABLE_HDR) && !isShieldAtvFirmwareWithBrokenHdr();
|
||||
config.enablePip = prefs.getBoolean(ENABLE_PIP_PREF_STRING, DEFAULT_ENABLE_PIP);
|
||||
config.enablePerfOverlay = prefs.getBoolean(ENABLE_PERF_OVERLAY_STRING, DEFAULT_ENABLE_PERF_OVERLAY);
|
||||
config.bindAllUsb = prefs.getBoolean(BIND_ALL_USB_STRING, DEFAULT_BIND_ALL_USB);
|
||||
@@ -417,6 +506,10 @@ public class PreferenceConfiguration {
|
||||
config.flipFaceButtons = prefs.getBoolean(FLIP_FACE_BUTTONS_PREF_STRING, DEFAULT_FLIP_FACE_BUTTONS);
|
||||
config.touchscreenTrackpad = prefs.getBoolean(TOUCHSCREEN_TRACKPAD_PREF_STRING, DEFAULT_TOUCHSCREEN_TRACKPAD);
|
||||
config.enableLatencyToast = prefs.getBoolean(LATENCY_TOAST_PREF_STRING, DEFAULT_LATENCY_TOAST);
|
||||
config.absoluteMouseMode = prefs.getBoolean(ABSOLUTE_MOUSE_MODE_PREF_STRING, DEFAULT_ABSOLUTE_MOUSE_MODE);
|
||||
config.enableAudioFx = prefs.getBoolean(ENABLE_AUDIO_FX_PREF_STRING, DEFAULT_ENABLE_AUDIO_FX);
|
||||
config.reduceRefreshRate = prefs.getBoolean(REDUCE_REFRESH_RATE_PREF_STRING, DEFAULT_REDUCE_REFRESH_RATE);
|
||||
config.fullRange = prefs.getBoolean(FULL_RANGE_PREF_STRING, DEFAULT_FULL_RANGE);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -4,12 +4,14 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.media.MediaCodecInfo;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.app.Activity;
|
||||
import android.os.Handler;
|
||||
import android.os.Vibrator;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceCategory;
|
||||
@@ -19,9 +21,11 @@ import android.preference.PreferenceScreen;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Range;
|
||||
import android.view.Display;
|
||||
import android.view.DisplayCutout;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
import com.limelight.PcView;
|
||||
@@ -35,11 +39,19 @@ import java.util.Arrays;
|
||||
|
||||
public class StreamSettings extends Activity {
|
||||
private PreferenceConfiguration previousPrefs;
|
||||
private int previousDisplayPixelCount;
|
||||
|
||||
// HACK for Android 9
|
||||
static DisplayCutout displayCutoutP;
|
||||
|
||||
void reloadSettings() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
Display.Mode mode = getWindowManager().getDefaultDisplay().getMode();
|
||||
previousDisplayPixelCount = mode.getPhysicalWidth() * mode.getPhysicalHeight();
|
||||
}
|
||||
getFragmentManager().beginTransaction().replace(
|
||||
R.id.stream_settings, new SettingsFragment()
|
||||
).commit();
|
||||
).commitAllowingStateLoss();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -51,22 +63,61 @@ public class StreamSettings extends Activity {
|
||||
UiHelper.setLocale(this);
|
||||
|
||||
setContentView(R.layout.activity_stream_settings);
|
||||
reloadSettings();
|
||||
|
||||
UiHelper.notifyNewRootView(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
|
||||
// We have to use this hack on Android 9 because we don't have Display.getCutout()
|
||||
// which was added in Android 10.
|
||||
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.P) {
|
||||
// Insets can be null when the activity is recreated on screen rotation
|
||||
// https://stackoverflow.com/questions/61241255/windowinsets-getdisplaycutout-is-null-everywhere-except-within-onattachedtowindo
|
||||
WindowInsets insets = getWindow().getDecorView().getRootWindowInsets();
|
||||
if (insets != null) {
|
||||
displayCutoutP = insets.getDisplayCutout();
|
||||
}
|
||||
}
|
||||
|
||||
reloadSettings();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
Display.Mode mode = getWindowManager().getDefaultDisplay().getMode();
|
||||
|
||||
// If the display's physical pixel count has changed, we consider that it's a new display
|
||||
// and we should reload our settings (which include display-dependent values).
|
||||
//
|
||||
// NB: We aren't using displayId here because that stays the same (DEFAULT_DISPLAY) when
|
||||
// switching between screens on a foldable device.
|
||||
if (mode.getPhysicalWidth() * mode.getPhysicalHeight() != previousDisplayPixelCount) {
|
||||
reloadSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
// NOTE: This will NOT be called on Android 13+ with android:enableOnBackInvokedCallback="true"
|
||||
public void onBackPressed() {
|
||||
finish();
|
||||
|
||||
// Check for changes that require a UI reload to take effect
|
||||
PreferenceConfiguration newPrefs = PreferenceConfiguration.readPreferences(this);
|
||||
if (!newPrefs.language.equals(previousPrefs.language)) {
|
||||
// Restart the PC view to apply UI changes
|
||||
Intent intent = new Intent(this, PcView.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent, null);
|
||||
// Language changes are handled via configuration changes in Android 13+,
|
||||
// so manual activity relaunching is no longer required.
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||
PreferenceConfiguration newPrefs = PreferenceConfiguration.readPreferences(this);
|
||||
if (!newPrefs.language.equals(previousPrefs.language)) {
|
||||
// Restart the PC view to apply UI changes
|
||||
Intent intent = new Intent(this, PcView.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,10 +130,29 @@ public class StreamSettings extends Activity {
|
||||
pref.setValue(value);
|
||||
}
|
||||
|
||||
private void addNativeResolutionEntry(int nativeWidth, int nativeHeight) {
|
||||
private void addNativeResolutionEntry(int nativeWidth, int nativeHeight, boolean insetsRemoved, boolean portrait) {
|
||||
ListPreference pref = (ListPreference) findPreference(PreferenceConfiguration.RESOLUTION_PREF_STRING);
|
||||
|
||||
String newName = getResources().getString(R.string.resolution_prefix_native) + " ("+nativeWidth+"x"+nativeHeight+")";
|
||||
String newName;
|
||||
|
||||
if (insetsRemoved) {
|
||||
newName = getResources().getString(R.string.resolution_prefix_native_fullscreen);
|
||||
}
|
||||
else {
|
||||
newName = getResources().getString(R.string.resolution_prefix_native);
|
||||
}
|
||||
|
||||
if (PreferenceConfiguration.isSquarishScreen(nativeWidth, nativeHeight)) {
|
||||
if (portrait) {
|
||||
newName += " " + getResources().getString(R.string.resolution_prefix_native_portrait);
|
||||
}
|
||||
else {
|
||||
newName += " " + getResources().getString(R.string.resolution_prefix_native_landscape);
|
||||
}
|
||||
}
|
||||
|
||||
newName += " ("+nativeWidth+"x"+nativeHeight+")";
|
||||
|
||||
String newValue = nativeWidth+"x"+nativeHeight;
|
||||
|
||||
CharSequence[] values = pref.getEntryValues();
|
||||
@@ -110,6 +180,13 @@ public class StreamSettings extends Activity {
|
||||
}
|
||||
}
|
||||
|
||||
private void addNativeResolutionEntries(int nativeWidth, int nativeHeight, boolean insetsRemoved) {
|
||||
if (PreferenceConfiguration.isSquarishScreen(nativeWidth, nativeHeight)) {
|
||||
addNativeResolutionEntry(nativeHeight, nativeWidth, insetsRemoved, true);
|
||||
}
|
||||
addNativeResolutionEntry(nativeWidth, nativeHeight, insetsRemoved, false);
|
||||
}
|
||||
|
||||
private void removeValue(String preferenceKey, String value, Runnable onMatched) {
|
||||
int matchingCount = 0;
|
||||
|
||||
@@ -190,6 +267,15 @@ public class StreamSettings extends Activity {
|
||||
}
|
||||
}
|
||||
|
||||
// Hide remote desktop mouse mode on pre-Oreo (which doesn't have pointer capture)
|
||||
// and NVIDIA SHIELD devices (which support raw mouse input in pointer capture mode)
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O ||
|
||||
getActivity().getPackageManager().hasSystemFeature("com.nvidia.feature.shield")) {
|
||||
PreferenceCategory category =
|
||||
(PreferenceCategory) findPreference("category_input_settings");
|
||||
category.removePreference(findPreference("checkbox_absolute_mouse_mode"));
|
||||
}
|
||||
|
||||
// Remove PiP mode on devices pre-Oreo, where the feature is not available (some low RAM devices),
|
||||
// and on Fire OS where it violates the Amazon App Store guidelines for some reason.
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O ||
|
||||
@@ -200,6 +286,13 @@ public class StreamSettings extends Activity {
|
||||
category.removePreference(findPreference("checkbox_enable_pip"));
|
||||
}
|
||||
|
||||
// Fire TV apps are not allowed to use WebViews or browsers, so hide the Help category
|
||||
/*if (getActivity().getPackageManager().hasSystemFeature("amazon.hardware.fire_tv")) {
|
||||
PreferenceCategory category =
|
||||
(PreferenceCategory) findPreference("category_help");
|
||||
screen.removePreference(category);
|
||||
}*/
|
||||
|
||||
// Remove the vibration options if the device can't vibrate
|
||||
if (!((Vibrator)getActivity().getSystemService(Context.VIBRATOR_SERVICE)).hasVibrator()) {
|
||||
PreferenceCategory category =
|
||||
@@ -221,6 +314,38 @@ public class StreamSettings extends Activity {
|
||||
|
||||
int maxSupportedResW = 0;
|
||||
|
||||
// Add a native resolution with any insets included for users that don't want content
|
||||
// behind the notch of their display
|
||||
boolean hasInsets = false;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
DisplayCutout cutout;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
// Use the much nicer Display.getCutout() API on Android 10+
|
||||
cutout = display.getCutout();
|
||||
}
|
||||
else {
|
||||
// Android 9 only
|
||||
cutout = displayCutoutP;
|
||||
}
|
||||
|
||||
if (cutout != null) {
|
||||
int widthInsets = cutout.getSafeInsetLeft() + cutout.getSafeInsetRight();
|
||||
int heightInsets = cutout.getSafeInsetBottom() + cutout.getSafeInsetTop();
|
||||
|
||||
if (widthInsets != 0 || heightInsets != 0) {
|
||||
DisplayMetrics metrics = new DisplayMetrics();
|
||||
display.getRealMetrics(metrics);
|
||||
|
||||
int width = Math.max(metrics.widthPixels - widthInsets, metrics.heightPixels - heightInsets);
|
||||
int height = Math.min(metrics.widthPixels - widthInsets, metrics.heightPixels - heightInsets);
|
||||
|
||||
addNativeResolutionEntries(width, height, false);
|
||||
hasInsets = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Always allow resolutions that are smaller or equal to the active
|
||||
// display resolution because decoders can report total non-sense to us.
|
||||
// For example, a p201 device reports:
|
||||
@@ -240,7 +365,7 @@ public class StreamSettings extends Activity {
|
||||
// unless they report greater than 4K resolutions.
|
||||
if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEVISION) ||
|
||||
(width > 3840 || height > 2160)) {
|
||||
addNativeResolutionEntry(width, height);
|
||||
addNativeResolutionEntries(width, height, hasInsets);
|
||||
}
|
||||
|
||||
if ((width >= 3840 || height >= 2160) && maxSupportedResW < 3840) {
|
||||
@@ -350,7 +475,7 @@ public class StreamSettings extends Activity {
|
||||
getActivity().getWindowManager().getDefaultDisplay().getRealMetrics(metrics);
|
||||
int width = Math.max(metrics.widthPixels, metrics.heightPixels);
|
||||
int height = Math.min(metrics.widthPixels, metrics.heightPixels);
|
||||
addNativeResolutionEntry(width, height);
|
||||
addNativeResolutionEntries(width, height, false);
|
||||
}
|
||||
else {
|
||||
// On Android 4.1, we have to resort to reflection to invoke hidden APIs
|
||||
@@ -361,7 +486,7 @@ public class StreamSettings extends Activity {
|
||||
Method getRawWidthFunc = Display.class.getMethod("getRawWidth");
|
||||
int width = (Integer) getRawWidthFunc.invoke(display);
|
||||
int height = (Integer) getRawHeightFunc.invoke(display);
|
||||
addNativeResolutionEntry(Math.max(width, height), Math.min(width, height));
|
||||
addNativeResolutionEntries(Math.max(width, height), Math.min(width, height), false);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@@ -465,6 +590,15 @@ public class StreamSettings extends Activity {
|
||||
(PreferenceCategory) findPreference("category_advanced_settings");
|
||||
category.removePreference(findPreference("checkbox_enable_hdr"));
|
||||
}
|
||||
else if (PreferenceConfiguration.isShieldAtvFirmwareWithBrokenHdr()) {
|
||||
LimeLog.info("Disabling HDR toggle on old broken SHIELD TV firmware");
|
||||
PreferenceCategory category =
|
||||
(PreferenceCategory) findPreference("category_advanced_settings");
|
||||
CheckBoxPreference hdrPref = (CheckBoxPreference) category.findPreference("checkbox_enable_hdr");
|
||||
hdrPref.setEnabled(false);
|
||||
hdrPref.setChecked(false);
|
||||
hdrPref.setSummary("Update the firmware on your NVIDIA SHIELD Android TV to enable HDR");
|
||||
}
|
||||
}
|
||||
|
||||
// Add a listener to the FPS and resolution preference
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.limelight.preferences;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.preference.Preference;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import com.limelight.utils.HelpLauncher;
|
||||
|
||||
public class WebLauncherPreference extends Preference {
|
||||
private String url;
|
||||
|
||||
public WebLauncherPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
initialize(attrs);
|
||||
}
|
||||
|
||||
public WebLauncherPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initialize(attrs);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public WebLauncherPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
initialize(attrs);
|
||||
}
|
||||
|
||||
private void initialize(AttributeSet attrs) {
|
||||
if (attrs == null) {
|
||||
throw new IllegalStateException("WebLauncherPreference must have attributes!");
|
||||
}
|
||||
|
||||
url = attrs.getAttributeValue(null, "url");
|
||||
if (url == null) {
|
||||
throw new IllegalStateException("WebLauncherPreference must have 'url' attribute!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick() {
|
||||
HelpLauncher.launchUrl(getContext(), url);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
package com.limelight.ui;
|
||||
|
||||
public interface GameGestures {
|
||||
void showKeyboard();
|
||||
void toggleKeyboard();
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import android.net.Uri;
|
||||
import com.limelight.HelpActivity;
|
||||
|
||||
public class HelpLauncher {
|
||||
private static void launchUrl(Context context, String url) {
|
||||
public static void launchUrl(Context context, String url) {
|
||||
// Try to launch the default browser
|
||||
try {
|
||||
Intent i = new Intent(Intent.ACTION_VIEW);
|
||||
|
||||
@@ -27,7 +27,10 @@ import java.security.cert.CertificateEncodingException;
|
||||
public class ServerHelper {
|
||||
public static final String CONNECTION_TEST_SERVER = "android.conntest.moonlight-stream.org";
|
||||
|
||||
public static String getCurrentAddressFromComputer(ComputerDetails computer) {
|
||||
public static ComputerDetails.AddressTuple getCurrentAddressFromComputer(ComputerDetails computer) throws IOException {
|
||||
if (computer.activeAddress == null) {
|
||||
throw new IOException("No active address for "+computer.name);
|
||||
}
|
||||
return computer.activeAddress;
|
||||
}
|
||||
|
||||
@@ -53,7 +56,9 @@ public class ServerHelper {
|
||||
public static Intent createStartIntent(Activity parent, NvApp app, ComputerDetails computer,
|
||||
ComputerManagerService.ComputerManagerBinder managerBinder) {
|
||||
Intent intent = new Intent(parent, Game.class);
|
||||
intent.putExtra(Game.EXTRA_HOST, getCurrentAddressFromComputer(computer));
|
||||
intent.putExtra(Game.EXTRA_HOST, computer.activeAddress.address);
|
||||
intent.putExtra(Game.EXTRA_PORT, computer.activeAddress.port);
|
||||
intent.putExtra(Game.EXTRA_HTTPS_PORT, computer.httpsPort);
|
||||
intent.putExtra(Game.EXTRA_APP_NAME, app.getAppName());
|
||||
intent.putExtra(Game.EXTRA_APP_ID, app.getAppId());
|
||||
intent.putExtra(Game.EXTRA_APP_HDR, app.isHdrSupported());
|
||||
@@ -72,8 +77,7 @@ public class ServerHelper {
|
||||
|
||||
public static void doStart(Activity parent, NvApp app, ComputerDetails computer,
|
||||
ComputerManagerService.ComputerManagerBinder managerBinder) {
|
||||
if (computer.state == ComputerDetails.State.OFFLINE ||
|
||||
ServerHelper.getCurrentAddressFromComputer(computer) == null) {
|
||||
if (computer.state == ComputerDetails.State.OFFLINE || computer.activeAddress == null) {
|
||||
Toast.makeText(parent, parent.getResources().getString(R.string.pair_pc_offline), Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
@@ -124,7 +128,7 @@ public class ServerHelper {
|
||||
NvHTTP httpConn;
|
||||
String message;
|
||||
try {
|
||||
httpConn = new NvHTTP(ServerHelper.getCurrentAddressFromComputer(computer),
|
||||
httpConn = new NvHTTP(ServerHelper.getCurrentAddressFromComputer(computer), computer.httpsPort,
|
||||
managerBinder.getUniqueId(), computer.serverCert, PlatformBinding.getCryptoProvider(parent));
|
||||
if (httpConn.quitApp()) {
|
||||
message = parent.getResources().getString(R.string.applist_quit_success) + " " + app.getAppName();
|
||||
|
||||
@@ -39,7 +39,7 @@ public class ShortcutHelper {
|
||||
@TargetApi(Build.VERSION_CODES.N_MR1)
|
||||
private void reapShortcutsForDynamicAdd() {
|
||||
List<ShortcutInfo> dynamicShortcuts = sm.getDynamicShortcuts();
|
||||
while (dynamicShortcuts.size() >= sm.getMaxShortcutCountPerActivity()) {
|
||||
while (!dynamicShortcuts.isEmpty() && dynamicShortcuts.size() >= sm.getMaxShortcutCountPerActivity()) {
|
||||
ShortcutInfo maxRankShortcut = dynamicShortcuts.get(0);
|
||||
for (ShortcutInfo scut : dynamicShortcuts) {
|
||||
if (maxRankShortcut.getRank() < scut.getRank()) {
|
||||
@@ -118,8 +118,16 @@ public class ShortcutHelper {
|
||||
// To avoid a random carousel of shortcuts popping in and out based on polling status,
|
||||
// we only add shortcuts if it's not at the limit or the user made a conscious action
|
||||
// to interact with this PC.
|
||||
if (forceAdd || sm.getDynamicShortcuts().size() < sm.getMaxShortcutCountPerActivity()) {
|
||||
|
||||
if (forceAdd) {
|
||||
// This should free an entry for us to add one below
|
||||
reapShortcutsForDynamicAdd();
|
||||
}
|
||||
|
||||
// We still need to check the maximum shortcut count even after reaping,
|
||||
// because there's a possibility that it could be zero.
|
||||
if (sm.getDynamicShortcuts().size() < sm.getMaxShortcutCountPerActivity()) {
|
||||
// Add a shortcut if there is room
|
||||
sm.addDynamicShortcuts(Collections.singletonList(sinfo));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ package com.limelight.utils;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.GameManager;
|
||||
import android.app.GameState;
|
||||
import android.app.LocaleManager;
|
||||
import android.app.UiModeManager;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
@@ -9,10 +12,12 @@ import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Insets;
|
||||
import android.os.Build;
|
||||
import android.os.LocaleList;
|
||||
import android.view.View;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.limelight.Game;
|
||||
import com.limelight.R;
|
||||
import com.limelight.nvstream.http.ComputerDetails;
|
||||
import com.limelight.preferences.PreferenceConfiguration;
|
||||
@@ -24,25 +29,66 @@ public class UiHelper {
|
||||
private static final int TV_VERTICAL_PADDING_DP = 15;
|
||||
private static final int TV_HORIZONTAL_PADDING_DP = 15;
|
||||
|
||||
private static void setGameModeStatus(Context context, boolean streaming, boolean loading, boolean interruptible) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
GameManager gameManager = context.getSystemService(GameManager.class);
|
||||
|
||||
if (streaming) {
|
||||
gameManager.setGameState(new GameState(loading, interruptible ? GameState.MODE_GAMEPLAY_INTERRUPTIBLE : GameState.MODE_GAMEPLAY_UNINTERRUPTIBLE));
|
||||
}
|
||||
else {
|
||||
gameManager.setGameState(new GameState(loading, GameState.MODE_NONE));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void notifyStreamConnecting(Context context) {
|
||||
setGameModeStatus(context, true, true, true);
|
||||
}
|
||||
|
||||
public static void notifyStreamConnected(Context context) {
|
||||
setGameModeStatus(context, true, false, false);
|
||||
}
|
||||
|
||||
public static void notifyStreamEnteringPiP(Context context) {
|
||||
setGameModeStatus(context, true, false, true);
|
||||
}
|
||||
|
||||
public static void notifyStreamExitingPiP(Context context) {
|
||||
setGameModeStatus(context, true, false, false);
|
||||
}
|
||||
|
||||
public static void notifyStreamEnded(Context context) {
|
||||
setGameModeStatus(context, false, false, false);
|
||||
}
|
||||
|
||||
public static void setLocale(Activity activity)
|
||||
{
|
||||
String locale = PreferenceConfiguration.readPreferences(activity).language;
|
||||
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
|
||||
Configuration config = new Configuration(activity.getResources().getConfiguration());
|
||||
|
||||
// Some locales include both language and country which must be separated
|
||||
// before calling the Locale constructor.
|
||||
if (locale.contains("-"))
|
||||
{
|
||||
config.locale = new Locale(locale.substring(0, locale.indexOf('-')),
|
||||
locale.substring(locale.indexOf('-') + 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
config.locale = new Locale(locale);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
// On Android 13, migrate this non-default language setting into the OS native API
|
||||
LocaleManager localeManager = activity.getSystemService(LocaleManager.class);
|
||||
localeManager.setApplicationLocales(LocaleList.forLanguageTags(locale));
|
||||
PreferenceConfiguration.completeLanguagePreferenceMigration(activity);
|
||||
}
|
||||
else {
|
||||
Configuration config = new Configuration(activity.getResources().getConfiguration());
|
||||
|
||||
activity.getResources().updateConfiguration(config, activity.getResources().getDisplayMetrics());
|
||||
// Some locales include both language and country which must be separated
|
||||
// before calling the Locale constructor.
|
||||
if (locale.contains("-"))
|
||||
{
|
||||
config.locale = new Locale(locale.substring(0, locale.indexOf('-')),
|
||||
locale.substring(locale.indexOf('-') + 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
config.locale = new Locale(locale);
|
||||
}
|
||||
|
||||
activity.getResources().updateConfiguration(config, activity.getResources().getDisplayMetrics());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +114,9 @@ public class UiHelper {
|
||||
View rootView = activity.findViewById(android.R.id.content);
|
||||
UiModeManager modeMgr = (UiModeManager) activity.getSystemService(Context.UI_MODE_SERVICE);
|
||||
|
||||
// Set GameState.MODE_NONE initially for all activities
|
||||
setGameModeStatus(activity, false, false, false);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
// Allow this non-streaming activity to layout under notches.
|
||||
//
|
||||
|
||||
@@ -20,8 +20,8 @@ LOCAL_SRC_FILES := moonlight-common-c/src/AudioStream.c \
|
||||
moonlight-common-c/src/Platform.c \
|
||||
moonlight-common-c/src/PlatformCrypto.c \
|
||||
moonlight-common-c/src/PlatformSockets.c \
|
||||
moonlight-common-c/src/RtpFecQueue.c \
|
||||
moonlight-common-c/src/RtpReorderQueue.c \
|
||||
moonlight-common-c/src/RtpAudioQueue.c \
|
||||
moonlight-common-c/src/RtpVideoQueue.c \
|
||||
moonlight-common-c/src/RtspConnection.c \
|
||||
moonlight-common-c/src/RtspParser.c \
|
||||
moonlight-common-c/src/SdpGenerator.c \
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
PATH=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH
|
||||
PATH=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH
|
||||
OUTPUT_DIR=~/openssl
|
||||
|
||||
BASE_ARGS="no-shared no-ssl3 no-stdio no-engine no-hw"
|
||||
|
||||
@@ -32,6 +32,7 @@ static jmethodID BridgeClConnectionStartedMethod;
|
||||
static jmethodID BridgeClConnectionTerminatedMethod;
|
||||
static jmethodID BridgeClRumbleMethod;
|
||||
static jmethodID BridgeClConnectionStatusUpdateMethod;
|
||||
static jmethodID BridgeClSetHdrModeMethod;
|
||||
static jbyteArray DecodedFrameBuffer;
|
||||
static jshortArray DecodedAudioBuffer;
|
||||
|
||||
@@ -79,7 +80,7 @@ Java_com_limelight_nvstream_jni_MoonBridge_init(JNIEnv *env, jclass clazz) {
|
||||
BridgeDrStartMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeDrStart", "()V");
|
||||
BridgeDrStopMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeDrStop", "()V");
|
||||
BridgeDrCleanupMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeDrCleanup", "()V");
|
||||
BridgeDrSubmitDecodeUnitMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeDrSubmitDecodeUnit", "([BIIIJJ)I");
|
||||
BridgeDrSubmitDecodeUnitMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeDrSubmitDecodeUnit", "([BIIIIJJ)I");
|
||||
BridgeArInitMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeArInit", "(III)I");
|
||||
BridgeArStartMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeArStart", "()V");
|
||||
BridgeArStopMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeArStop", "()V");
|
||||
@@ -92,6 +93,7 @@ Java_com_limelight_nvstream_jni_MoonBridge_init(JNIEnv *env, jclass clazz) {
|
||||
BridgeClConnectionTerminatedMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClConnectionTerminated", "(I)V");
|
||||
BridgeClRumbleMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClRumble", "(SSS)V");
|
||||
BridgeClConnectionStatusUpdateMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClConnectionStatusUpdate", "(I)V");
|
||||
BridgeClSetHdrModeMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClSetHdrMode", "(Z)V");
|
||||
}
|
||||
|
||||
int BridgeDrSetup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags) {
|
||||
@@ -157,8 +159,8 @@ int BridgeDrSubmitDecodeUnit(PDECODE_UNIT decodeUnit) {
|
||||
|
||||
ret = (*env)->CallStaticIntMethod(env, GlobalBridgeClass, BridgeDrSubmitDecodeUnitMethod,
|
||||
DecodedFrameBuffer, currentEntry->length, currentEntry->bufferType,
|
||||
decodeUnit->frameNumber, (jlong)decodeUnit->receiveTimeMs,
|
||||
(jlong)decodeUnit->enqueueTimeMs);
|
||||
decodeUnit->frameNumber, decodeUnit->frameType,
|
||||
(jlong)decodeUnit->receiveTimeMs, (jlong)decodeUnit->enqueueTimeMs);
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
// We will crash here
|
||||
(*JVM)->DetachCurrentThread(JVM);
|
||||
@@ -178,7 +180,7 @@ int BridgeDrSubmitDecodeUnit(PDECODE_UNIT decodeUnit) {
|
||||
|
||||
ret = (*env)->CallStaticIntMethod(env, GlobalBridgeClass, BridgeDrSubmitDecodeUnitMethod,
|
||||
DecodedFrameBuffer, offset, BUFFER_TYPE_PICDATA,
|
||||
decodeUnit->frameNumber,
|
||||
decodeUnit->frameNumber, decodeUnit->frameType,
|
||||
(jlong)decodeUnit->receiveTimeMs, (jlong)decodeUnit->enqueueTimeMs);
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
// We will crash here
|
||||
@@ -326,6 +328,16 @@ void BridgeClConnectionStatusUpdate(int connectionStatus) {
|
||||
}
|
||||
}
|
||||
|
||||
void BridgeClSetHdrMode(bool enabled) {
|
||||
JNIEnv* env = GetThreadEnv();
|
||||
|
||||
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeClSetHdrModeMethod, enabled);
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
// We will crash here
|
||||
(*JVM)->DetachCurrentThread(JVM);
|
||||
}
|
||||
}
|
||||
|
||||
void BridgeClLogMessage(const char* format, ...) {
|
||||
va_list va;
|
||||
va_start(va, format);
|
||||
@@ -359,11 +371,13 @@ static CONNECTION_LISTENER_CALLBACKS BridgeConnListenerCallbacks = {
|
||||
.logMessage = BridgeClLogMessage,
|
||||
.rumble = BridgeClRumble,
|
||||
.connectionStatusUpdate = BridgeClConnectionStatusUpdate,
|
||||
.setHdrMode = BridgeClSetHdrMode,
|
||||
};
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_limelight_nvstream_jni_MoonBridge_startConnection(JNIEnv *env, jclass clazz,
|
||||
jstring address, jstring appVersion, jstring gfeVersion,
|
||||
jstring rtspSessionUrl,
|
||||
jint width, jint height, jint fps,
|
||||
jint bitrate, jint packetSize, jint streamingRemotely,
|
||||
jint audioConfiguration, jboolean supportsHevc,
|
||||
@@ -372,11 +386,13 @@ Java_com_limelight_nvstream_jni_MoonBridge_startConnection(JNIEnv *env, jclass c
|
||||
jint clientRefreshRateX100,
|
||||
jint encryptionFlags,
|
||||
jbyteArray riAesKey, jbyteArray riAesIv,
|
||||
jint videoCapabilities) {
|
||||
jint videoCapabilities,
|
||||
jint colorSpace, jint colorRange) {
|
||||
SERVER_INFORMATION serverInfo = {
|
||||
.address = (*env)->GetStringUTFChars(env, address, 0),
|
||||
.serverInfoAppVersion = (*env)->GetStringUTFChars(env, appVersion, 0),
|
||||
.serverInfoGfeVersion = gfeVersion ? (*env)->GetStringUTFChars(env, gfeVersion, 0) : NULL,
|
||||
.rtspSessionUrl = rtspSessionUrl ? (*env)->GetStringUTFChars(env, rtspSessionUrl, 0) : NULL,
|
||||
};
|
||||
STREAM_CONFIGURATION streamConfig = {
|
||||
.width = width,
|
||||
@@ -391,6 +407,8 @@ Java_com_limelight_nvstream_jni_MoonBridge_startConnection(JNIEnv *env, jclass c
|
||||
.hevcBitratePercentageMultiplier = hevcBitratePercentageMultiplier,
|
||||
.clientRefreshRateX100 = clientRefreshRateX100,
|
||||
.encryptionFlags = encryptionFlags,
|
||||
.colorSpace = colorSpace,
|
||||
.colorRange = colorRange
|
||||
};
|
||||
|
||||
jbyte* riAesKeyBuf = (*env)->GetByteArrayElements(env, riAesKey, NULL);
|
||||
@@ -416,6 +434,9 @@ Java_com_limelight_nvstream_jni_MoonBridge_startConnection(JNIEnv *env, jclass c
|
||||
if (gfeVersion != NULL) {
|
||||
(*env)->ReleaseStringUTFChars(env, gfeVersion, serverInfo.serverInfoGfeVersion);
|
||||
}
|
||||
if (rtspSessionUrl != NULL) {
|
||||
(*env)->ReleaseStringUTFChars(env, rtspSessionUrl, serverInfo.rtspSessionUrl);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
Submodule app/src/main/jni/moonlight-core/moonlight-common-c updated: 4723f8ba7c...8169a31ecc
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,592 @@
|
||||
/*
|
||||
* WARNING: do not edit!
|
||||
* Generated by Makefile from include/openssl/cmp.h.in
|
||||
*
|
||||
* Copyright 2007-2021 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright Nokia 2007-2019
|
||||
* Copyright Siemens AG 2015-2019
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution or at
|
||||
* https://www.openssl.org/source/license.html
|
||||
*/
|
||||
|
||||
|
||||
|
||||
#ifndef OPENSSL_CMP_H
|
||||
# define OPENSSL_CMP_H
|
||||
|
||||
# include <openssl/opensslconf.h>
|
||||
# ifndef OPENSSL_NO_CMP
|
||||
|
||||
# include <openssl/crmf.h>
|
||||
# include <openssl/cmperr.h>
|
||||
# include <openssl/cmp_util.h>
|
||||
# include <openssl/http.h>
|
||||
|
||||
/* explicit #includes not strictly needed since implied by the above: */
|
||||
# include <openssl/types.h>
|
||||
# include <openssl/safestack.h>
|
||||
# include <openssl/x509.h>
|
||||
# include <openssl/x509v3.h>
|
||||
|
||||
# ifdef __cplusplus
|
||||
extern "C" {
|
||||
# endif
|
||||
|
||||
# define OSSL_CMP_PVNO 2
|
||||
|
||||
/*-
|
||||
* PKIFailureInfo ::= BIT STRING {
|
||||
* -- since we can fail in more than one way!
|
||||
* -- More codes may be added in the future if/when required.
|
||||
* badAlg (0),
|
||||
* -- unrecognized or unsupported Algorithm Identifier
|
||||
* badMessageCheck (1),
|
||||
* -- integrity check failed (e.g., signature did not verify)
|
||||
* badRequest (2),
|
||||
* -- transaction not permitted or supported
|
||||
* badTime (3),
|
||||
* -- messageTime was not sufficiently close to the system time,
|
||||
* -- as defined by local policy
|
||||
* badCertId (4),
|
||||
* -- no certificate could be found matching the provided criteria
|
||||
* badDataFormat (5),
|
||||
* -- the data submitted has the wrong format
|
||||
* wrongAuthority (6),
|
||||
* -- the authority indicated in the request is different from the
|
||||
* -- one creating the response token
|
||||
* incorrectData (7),
|
||||
* -- the requester's data is incorrect (for notary services)
|
||||
* missingTimeStamp (8),
|
||||
* -- when the timestamp is missing but should be there
|
||||
* -- (by policy)
|
||||
* badPOP (9),
|
||||
* -- the proof-of-possession failed
|
||||
* certRevoked (10),
|
||||
* -- the certificate has already been revoked
|
||||
* certConfirmed (11),
|
||||
* -- the certificate has already been confirmed
|
||||
* wrongIntegrity (12),
|
||||
* -- invalid integrity, password based instead of signature or
|
||||
* -- vice versa
|
||||
* badRecipientNonce (13),
|
||||
* -- invalid recipient nonce, either missing or wrong value
|
||||
* timeNotAvailable (14),
|
||||
* -- the TSA's time source is not available
|
||||
* unacceptedPolicy (15),
|
||||
* -- the requested TSA policy is not supported by the TSA.
|
||||
* unacceptedExtension (16),
|
||||
* -- the requested extension is not supported by the TSA.
|
||||
* addInfoNotAvailable (17),
|
||||
* -- the additional information requested could not be
|
||||
* -- understood or is not available
|
||||
* badSenderNonce (18),
|
||||
* -- invalid sender nonce, either missing or wrong size
|
||||
* badCertTemplate (19),
|
||||
* -- invalid cert. template or missing mandatory information
|
||||
* signerNotTrusted (20),
|
||||
* -- signer of the message unknown or not trusted
|
||||
* transactionIdInUse (21),
|
||||
* -- the transaction identifier is already in use
|
||||
* unsupportedVersion (22),
|
||||
* -- the version of the message is not supported
|
||||
* notAuthorized (23),
|
||||
* -- the sender was not authorized to make the preceding
|
||||
* -- request or perform the preceding action
|
||||
* systemUnavail (24),
|
||||
* -- the request cannot be handled due to system unavailability
|
||||
* systemFailure (25),
|
||||
* -- the request cannot be handled due to system failure
|
||||
* duplicateCertReq (26)
|
||||
* -- certificate cannot be issued because a duplicate
|
||||
* -- certificate already exists
|
||||
* }
|
||||
*/
|
||||
# define OSSL_CMP_PKIFAILUREINFO_badAlg 0
|
||||
# define OSSL_CMP_PKIFAILUREINFO_badMessageCheck 1
|
||||
# define OSSL_CMP_PKIFAILUREINFO_badRequest 2
|
||||
# define OSSL_CMP_PKIFAILUREINFO_badTime 3
|
||||
# define OSSL_CMP_PKIFAILUREINFO_badCertId 4
|
||||
# define OSSL_CMP_PKIFAILUREINFO_badDataFormat 5
|
||||
# define OSSL_CMP_PKIFAILUREINFO_wrongAuthority 6
|
||||
# define OSSL_CMP_PKIFAILUREINFO_incorrectData 7
|
||||
# define OSSL_CMP_PKIFAILUREINFO_missingTimeStamp 8
|
||||
# define OSSL_CMP_PKIFAILUREINFO_badPOP 9
|
||||
# define OSSL_CMP_PKIFAILUREINFO_certRevoked 10
|
||||
# define OSSL_CMP_PKIFAILUREINFO_certConfirmed 11
|
||||
# define OSSL_CMP_PKIFAILUREINFO_wrongIntegrity 12
|
||||
# define OSSL_CMP_PKIFAILUREINFO_badRecipientNonce 13
|
||||
# define OSSL_CMP_PKIFAILUREINFO_timeNotAvailable 14
|
||||
# define OSSL_CMP_PKIFAILUREINFO_unacceptedPolicy 15
|
||||
# define OSSL_CMP_PKIFAILUREINFO_unacceptedExtension 16
|
||||
# define OSSL_CMP_PKIFAILUREINFO_addInfoNotAvailable 17
|
||||
# define OSSL_CMP_PKIFAILUREINFO_badSenderNonce 18
|
||||
# define OSSL_CMP_PKIFAILUREINFO_badCertTemplate 19
|
||||
# define OSSL_CMP_PKIFAILUREINFO_signerNotTrusted 20
|
||||
# define OSSL_CMP_PKIFAILUREINFO_transactionIdInUse 21
|
||||
# define OSSL_CMP_PKIFAILUREINFO_unsupportedVersion 22
|
||||
# define OSSL_CMP_PKIFAILUREINFO_notAuthorized 23
|
||||
# define OSSL_CMP_PKIFAILUREINFO_systemUnavail 24
|
||||
# define OSSL_CMP_PKIFAILUREINFO_systemFailure 25
|
||||
# define OSSL_CMP_PKIFAILUREINFO_duplicateCertReq 26
|
||||
# define OSSL_CMP_PKIFAILUREINFO_MAX 26
|
||||
# define OSSL_CMP_PKIFAILUREINFO_MAX_BIT_PATTERN \
|
||||
((1 << (OSSL_CMP_PKIFAILUREINFO_MAX + 1)) - 1)
|
||||
# if OSSL_CMP_PKIFAILUREINFO_MAX_BIT_PATTERN > INT_MAX
|
||||
# error CMP_PKIFAILUREINFO_MAX bit pattern does not fit in type int
|
||||
# endif
|
||||
|
||||
typedef ASN1_BIT_STRING OSSL_CMP_PKIFAILUREINFO;
|
||||
|
||||
# define OSSL_CMP_CTX_FAILINFO_badAlg (1 << 0)
|
||||
# define OSSL_CMP_CTX_FAILINFO_badMessageCheck (1 << 1)
|
||||
# define OSSL_CMP_CTX_FAILINFO_badRequest (1 << 2)
|
||||
# define OSSL_CMP_CTX_FAILINFO_badTime (1 << 3)
|
||||
# define OSSL_CMP_CTX_FAILINFO_badCertId (1 << 4)
|
||||
# define OSSL_CMP_CTX_FAILINFO_badDataFormat (1 << 5)
|
||||
# define OSSL_CMP_CTX_FAILINFO_wrongAuthority (1 << 6)
|
||||
# define OSSL_CMP_CTX_FAILINFO_incorrectData (1 << 7)
|
||||
# define OSSL_CMP_CTX_FAILINFO_missingTimeStamp (1 << 8)
|
||||
# define OSSL_CMP_CTX_FAILINFO_badPOP (1 << 9)
|
||||
# define OSSL_CMP_CTX_FAILINFO_certRevoked (1 << 10)
|
||||
# define OSSL_CMP_CTX_FAILINFO_certConfirmed (1 << 11)
|
||||
# define OSSL_CMP_CTX_FAILINFO_wrongIntegrity (1 << 12)
|
||||
# define OSSL_CMP_CTX_FAILINFO_badRecipientNonce (1 << 13)
|
||||
# define OSSL_CMP_CTX_FAILINFO_timeNotAvailable (1 << 14)
|
||||
# define OSSL_CMP_CTX_FAILINFO_unacceptedPolicy (1 << 15)
|
||||
# define OSSL_CMP_CTX_FAILINFO_unacceptedExtension (1 << 16)
|
||||
# define OSSL_CMP_CTX_FAILINFO_addInfoNotAvailable (1 << 17)
|
||||
# define OSSL_CMP_CTX_FAILINFO_badSenderNonce (1 << 18)
|
||||
# define OSSL_CMP_CTX_FAILINFO_badCertTemplate (1 << 19)
|
||||
# define OSSL_CMP_CTX_FAILINFO_signerNotTrusted (1 << 20)
|
||||
# define OSSL_CMP_CTX_FAILINFO_transactionIdInUse (1 << 21)
|
||||
# define OSSL_CMP_CTX_FAILINFO_unsupportedVersion (1 << 22)
|
||||
# define OSSL_CMP_CTX_FAILINFO_notAuthorized (1 << 23)
|
||||
# define OSSL_CMP_CTX_FAILINFO_systemUnavail (1 << 24)
|
||||
# define OSSL_CMP_CTX_FAILINFO_systemFailure (1 << 25)
|
||||
# define OSSL_CMP_CTX_FAILINFO_duplicateCertReq (1 << 26)
|
||||
|
||||
/*-
|
||||
* PKIStatus ::= INTEGER {
|
||||
* accepted (0),
|
||||
* -- you got exactly what you asked for
|
||||
* grantedWithMods (1),
|
||||
* -- you got something like what you asked for; the
|
||||
* -- requester is responsible for ascertaining the differences
|
||||
* rejection (2),
|
||||
* -- you don't get it, more information elsewhere in the message
|
||||
* waiting (3),
|
||||
* -- the request body part has not yet been processed; expect to
|
||||
* -- hear more later (note: proper handling of this status
|
||||
* -- response MAY use the polling req/rep PKIMessages specified
|
||||
* -- in Section 5.3.22; alternatively, polling in the underlying
|
||||
* -- transport layer MAY have some utility in this regard)
|
||||
* revocationWarning (4),
|
||||
* -- this message contains a warning that a revocation is
|
||||
* -- imminent
|
||||
* revocationNotification (5),
|
||||
* -- notification that a revocation has occurred
|
||||
* keyUpdateWarning (6)
|
||||
* -- update already done for the oldCertId specified in
|
||||
* -- CertReqMsg
|
||||
* }
|
||||
*/
|
||||
# define OSSL_CMP_PKISTATUS_accepted 0
|
||||
# define OSSL_CMP_PKISTATUS_grantedWithMods 1
|
||||
# define OSSL_CMP_PKISTATUS_rejection 2
|
||||
# define OSSL_CMP_PKISTATUS_waiting 3
|
||||
# define OSSL_CMP_PKISTATUS_revocationWarning 4
|
||||
# define OSSL_CMP_PKISTATUS_revocationNotification 5
|
||||
# define OSSL_CMP_PKISTATUS_keyUpdateWarning 6
|
||||
|
||||
typedef ASN1_INTEGER OSSL_CMP_PKISTATUS;
|
||||
DECLARE_ASN1_ITEM(OSSL_CMP_PKISTATUS)
|
||||
|
||||
# define OSSL_CMP_CERTORENCCERT_CERTIFICATE 0
|
||||
# define OSSL_CMP_CERTORENCCERT_ENCRYPTEDCERT 1
|
||||
|
||||
/* data type declarations */
|
||||
typedef struct ossl_cmp_ctx_st OSSL_CMP_CTX;
|
||||
typedef struct ossl_cmp_pkiheader_st OSSL_CMP_PKIHEADER;
|
||||
DECLARE_ASN1_FUNCTIONS(OSSL_CMP_PKIHEADER)
|
||||
typedef struct ossl_cmp_msg_st OSSL_CMP_MSG;
|
||||
DECLARE_ASN1_DUP_FUNCTION(OSSL_CMP_MSG)
|
||||
DECLARE_ASN1_ENCODE_FUNCTIONS(OSSL_CMP_MSG, OSSL_CMP_MSG, OSSL_CMP_MSG)
|
||||
typedef struct ossl_cmp_certstatus_st OSSL_CMP_CERTSTATUS;
|
||||
SKM_DEFINE_STACK_OF_INTERNAL(OSSL_CMP_CERTSTATUS, OSSL_CMP_CERTSTATUS, OSSL_CMP_CERTSTATUS)
|
||||
#define sk_OSSL_CMP_CERTSTATUS_num(sk) OPENSSL_sk_num(ossl_check_const_OSSL_CMP_CERTSTATUS_sk_type(sk))
|
||||
#define sk_OSSL_CMP_CERTSTATUS_value(sk, idx) ((OSSL_CMP_CERTSTATUS *)OPENSSL_sk_value(ossl_check_const_OSSL_CMP_CERTSTATUS_sk_type(sk), (idx)))
|
||||
#define sk_OSSL_CMP_CERTSTATUS_new(cmp) ((STACK_OF(OSSL_CMP_CERTSTATUS) *)OPENSSL_sk_new(ossl_check_OSSL_CMP_CERTSTATUS_compfunc_type(cmp)))
|
||||
#define sk_OSSL_CMP_CERTSTATUS_new_null() ((STACK_OF(OSSL_CMP_CERTSTATUS) *)OPENSSL_sk_new_null())
|
||||
#define sk_OSSL_CMP_CERTSTATUS_new_reserve(cmp, n) ((STACK_OF(OSSL_CMP_CERTSTATUS) *)OPENSSL_sk_new_reserve(ossl_check_OSSL_CMP_CERTSTATUS_compfunc_type(cmp), (n)))
|
||||
#define sk_OSSL_CMP_CERTSTATUS_reserve(sk, n) OPENSSL_sk_reserve(ossl_check_OSSL_CMP_CERTSTATUS_sk_type(sk), (n))
|
||||
#define sk_OSSL_CMP_CERTSTATUS_free(sk) OPENSSL_sk_free(ossl_check_OSSL_CMP_CERTSTATUS_sk_type(sk))
|
||||
#define sk_OSSL_CMP_CERTSTATUS_zero(sk) OPENSSL_sk_zero(ossl_check_OSSL_CMP_CERTSTATUS_sk_type(sk))
|
||||
#define sk_OSSL_CMP_CERTSTATUS_delete(sk, i) ((OSSL_CMP_CERTSTATUS *)OPENSSL_sk_delete(ossl_check_OSSL_CMP_CERTSTATUS_sk_type(sk), (i)))
|
||||
#define sk_OSSL_CMP_CERTSTATUS_delete_ptr(sk, ptr) ((OSSL_CMP_CERTSTATUS *)OPENSSL_sk_delete_ptr(ossl_check_OSSL_CMP_CERTSTATUS_sk_type(sk), ossl_check_OSSL_CMP_CERTSTATUS_type(ptr)))
|
||||
#define sk_OSSL_CMP_CERTSTATUS_push(sk, ptr) OPENSSL_sk_push(ossl_check_OSSL_CMP_CERTSTATUS_sk_type(sk), ossl_check_OSSL_CMP_CERTSTATUS_type(ptr))
|
||||
#define sk_OSSL_CMP_CERTSTATUS_unshift(sk, ptr) OPENSSL_sk_unshift(ossl_check_OSSL_CMP_CERTSTATUS_sk_type(sk), ossl_check_OSSL_CMP_CERTSTATUS_type(ptr))
|
||||
#define sk_OSSL_CMP_CERTSTATUS_pop(sk) ((OSSL_CMP_CERTSTATUS *)OPENSSL_sk_pop(ossl_check_OSSL_CMP_CERTSTATUS_sk_type(sk)))
|
||||
#define sk_OSSL_CMP_CERTSTATUS_shift(sk) ((OSSL_CMP_CERTSTATUS *)OPENSSL_sk_shift(ossl_check_OSSL_CMP_CERTSTATUS_sk_type(sk)))
|
||||
#define sk_OSSL_CMP_CERTSTATUS_pop_free(sk, freefunc) OPENSSL_sk_pop_free(ossl_check_OSSL_CMP_CERTSTATUS_sk_type(sk),ossl_check_OSSL_CMP_CERTSTATUS_freefunc_type(freefunc))
|
||||
#define sk_OSSL_CMP_CERTSTATUS_insert(sk, ptr, idx) OPENSSL_sk_insert(ossl_check_OSSL_CMP_CERTSTATUS_sk_type(sk), ossl_check_OSSL_CMP_CERTSTATUS_type(ptr), (idx))
|
||||
#define sk_OSSL_CMP_CERTSTATUS_set(sk, idx, ptr) ((OSSL_CMP_CERTSTATUS *)OPENSSL_sk_set(ossl_check_OSSL_CMP_CERTSTATUS_sk_type(sk), (idx), ossl_check_OSSL_CMP_CERTSTATUS_type(ptr)))
|
||||
#define sk_OSSL_CMP_CERTSTATUS_find(sk, ptr) OPENSSL_sk_find(ossl_check_OSSL_CMP_CERTSTATUS_sk_type(sk), ossl_check_OSSL_CMP_CERTSTATUS_type(ptr))
|
||||
#define sk_OSSL_CMP_CERTSTATUS_find_ex(sk, ptr) OPENSSL_sk_find_ex(ossl_check_OSSL_CMP_CERTSTATUS_sk_type(sk), ossl_check_OSSL_CMP_CERTSTATUS_type(ptr))
|
||||
#define sk_OSSL_CMP_CERTSTATUS_find_all(sk, ptr, pnum) OPENSSL_sk_find_all(ossl_check_OSSL_CMP_CERTSTATUS_sk_type(sk), ossl_check_OSSL_CMP_CERTSTATUS_type(ptr), pnum)
|
||||
#define sk_OSSL_CMP_CERTSTATUS_sort(sk) OPENSSL_sk_sort(ossl_check_OSSL_CMP_CERTSTATUS_sk_type(sk))
|
||||
#define sk_OSSL_CMP_CERTSTATUS_is_sorted(sk) OPENSSL_sk_is_sorted(ossl_check_const_OSSL_CMP_CERTSTATUS_sk_type(sk))
|
||||
#define sk_OSSL_CMP_CERTSTATUS_dup(sk) ((STACK_OF(OSSL_CMP_CERTSTATUS) *)OPENSSL_sk_dup(ossl_check_const_OSSL_CMP_CERTSTATUS_sk_type(sk)))
|
||||
#define sk_OSSL_CMP_CERTSTATUS_deep_copy(sk, copyfunc, freefunc) ((STACK_OF(OSSL_CMP_CERTSTATUS) *)OPENSSL_sk_deep_copy(ossl_check_const_OSSL_CMP_CERTSTATUS_sk_type(sk), ossl_check_OSSL_CMP_CERTSTATUS_copyfunc_type(copyfunc), ossl_check_OSSL_CMP_CERTSTATUS_freefunc_type(freefunc)))
|
||||
#define sk_OSSL_CMP_CERTSTATUS_set_cmp_func(sk, cmp) ((sk_OSSL_CMP_CERTSTATUS_compfunc)OPENSSL_sk_set_cmp_func(ossl_check_OSSL_CMP_CERTSTATUS_sk_type(sk), ossl_check_OSSL_CMP_CERTSTATUS_compfunc_type(cmp)))
|
||||
|
||||
typedef struct ossl_cmp_itav_st OSSL_CMP_ITAV;
|
||||
DECLARE_ASN1_DUP_FUNCTION(OSSL_CMP_ITAV)
|
||||
SKM_DEFINE_STACK_OF_INTERNAL(OSSL_CMP_ITAV, OSSL_CMP_ITAV, OSSL_CMP_ITAV)
|
||||
#define sk_OSSL_CMP_ITAV_num(sk) OPENSSL_sk_num(ossl_check_const_OSSL_CMP_ITAV_sk_type(sk))
|
||||
#define sk_OSSL_CMP_ITAV_value(sk, idx) ((OSSL_CMP_ITAV *)OPENSSL_sk_value(ossl_check_const_OSSL_CMP_ITAV_sk_type(sk), (idx)))
|
||||
#define sk_OSSL_CMP_ITAV_new(cmp) ((STACK_OF(OSSL_CMP_ITAV) *)OPENSSL_sk_new(ossl_check_OSSL_CMP_ITAV_compfunc_type(cmp)))
|
||||
#define sk_OSSL_CMP_ITAV_new_null() ((STACK_OF(OSSL_CMP_ITAV) *)OPENSSL_sk_new_null())
|
||||
#define sk_OSSL_CMP_ITAV_new_reserve(cmp, n) ((STACK_OF(OSSL_CMP_ITAV) *)OPENSSL_sk_new_reserve(ossl_check_OSSL_CMP_ITAV_compfunc_type(cmp), (n)))
|
||||
#define sk_OSSL_CMP_ITAV_reserve(sk, n) OPENSSL_sk_reserve(ossl_check_OSSL_CMP_ITAV_sk_type(sk), (n))
|
||||
#define sk_OSSL_CMP_ITAV_free(sk) OPENSSL_sk_free(ossl_check_OSSL_CMP_ITAV_sk_type(sk))
|
||||
#define sk_OSSL_CMP_ITAV_zero(sk) OPENSSL_sk_zero(ossl_check_OSSL_CMP_ITAV_sk_type(sk))
|
||||
#define sk_OSSL_CMP_ITAV_delete(sk, i) ((OSSL_CMP_ITAV *)OPENSSL_sk_delete(ossl_check_OSSL_CMP_ITAV_sk_type(sk), (i)))
|
||||
#define sk_OSSL_CMP_ITAV_delete_ptr(sk, ptr) ((OSSL_CMP_ITAV *)OPENSSL_sk_delete_ptr(ossl_check_OSSL_CMP_ITAV_sk_type(sk), ossl_check_OSSL_CMP_ITAV_type(ptr)))
|
||||
#define sk_OSSL_CMP_ITAV_push(sk, ptr) OPENSSL_sk_push(ossl_check_OSSL_CMP_ITAV_sk_type(sk), ossl_check_OSSL_CMP_ITAV_type(ptr))
|
||||
#define sk_OSSL_CMP_ITAV_unshift(sk, ptr) OPENSSL_sk_unshift(ossl_check_OSSL_CMP_ITAV_sk_type(sk), ossl_check_OSSL_CMP_ITAV_type(ptr))
|
||||
#define sk_OSSL_CMP_ITAV_pop(sk) ((OSSL_CMP_ITAV *)OPENSSL_sk_pop(ossl_check_OSSL_CMP_ITAV_sk_type(sk)))
|
||||
#define sk_OSSL_CMP_ITAV_shift(sk) ((OSSL_CMP_ITAV *)OPENSSL_sk_shift(ossl_check_OSSL_CMP_ITAV_sk_type(sk)))
|
||||
#define sk_OSSL_CMP_ITAV_pop_free(sk, freefunc) OPENSSL_sk_pop_free(ossl_check_OSSL_CMP_ITAV_sk_type(sk),ossl_check_OSSL_CMP_ITAV_freefunc_type(freefunc))
|
||||
#define sk_OSSL_CMP_ITAV_insert(sk, ptr, idx) OPENSSL_sk_insert(ossl_check_OSSL_CMP_ITAV_sk_type(sk), ossl_check_OSSL_CMP_ITAV_type(ptr), (idx))
|
||||
#define sk_OSSL_CMP_ITAV_set(sk, idx, ptr) ((OSSL_CMP_ITAV *)OPENSSL_sk_set(ossl_check_OSSL_CMP_ITAV_sk_type(sk), (idx), ossl_check_OSSL_CMP_ITAV_type(ptr)))
|
||||
#define sk_OSSL_CMP_ITAV_find(sk, ptr) OPENSSL_sk_find(ossl_check_OSSL_CMP_ITAV_sk_type(sk), ossl_check_OSSL_CMP_ITAV_type(ptr))
|
||||
#define sk_OSSL_CMP_ITAV_find_ex(sk, ptr) OPENSSL_sk_find_ex(ossl_check_OSSL_CMP_ITAV_sk_type(sk), ossl_check_OSSL_CMP_ITAV_type(ptr))
|
||||
#define sk_OSSL_CMP_ITAV_find_all(sk, ptr, pnum) OPENSSL_sk_find_all(ossl_check_OSSL_CMP_ITAV_sk_type(sk), ossl_check_OSSL_CMP_ITAV_type(ptr), pnum)
|
||||
#define sk_OSSL_CMP_ITAV_sort(sk) OPENSSL_sk_sort(ossl_check_OSSL_CMP_ITAV_sk_type(sk))
|
||||
#define sk_OSSL_CMP_ITAV_is_sorted(sk) OPENSSL_sk_is_sorted(ossl_check_const_OSSL_CMP_ITAV_sk_type(sk))
|
||||
#define sk_OSSL_CMP_ITAV_dup(sk) ((STACK_OF(OSSL_CMP_ITAV) *)OPENSSL_sk_dup(ossl_check_const_OSSL_CMP_ITAV_sk_type(sk)))
|
||||
#define sk_OSSL_CMP_ITAV_deep_copy(sk, copyfunc, freefunc) ((STACK_OF(OSSL_CMP_ITAV) *)OPENSSL_sk_deep_copy(ossl_check_const_OSSL_CMP_ITAV_sk_type(sk), ossl_check_OSSL_CMP_ITAV_copyfunc_type(copyfunc), ossl_check_OSSL_CMP_ITAV_freefunc_type(freefunc)))
|
||||
#define sk_OSSL_CMP_ITAV_set_cmp_func(sk, cmp) ((sk_OSSL_CMP_ITAV_compfunc)OPENSSL_sk_set_cmp_func(ossl_check_OSSL_CMP_ITAV_sk_type(sk), ossl_check_OSSL_CMP_ITAV_compfunc_type(cmp)))
|
||||
|
||||
typedef struct ossl_cmp_revrepcontent_st OSSL_CMP_REVREPCONTENT;
|
||||
typedef struct ossl_cmp_pkisi_st OSSL_CMP_PKISI;
|
||||
DECLARE_ASN1_FUNCTIONS(OSSL_CMP_PKISI)
|
||||
DECLARE_ASN1_DUP_FUNCTION(OSSL_CMP_PKISI)
|
||||
SKM_DEFINE_STACK_OF_INTERNAL(OSSL_CMP_PKISI, OSSL_CMP_PKISI, OSSL_CMP_PKISI)
|
||||
#define sk_OSSL_CMP_PKISI_num(sk) OPENSSL_sk_num(ossl_check_const_OSSL_CMP_PKISI_sk_type(sk))
|
||||
#define sk_OSSL_CMP_PKISI_value(sk, idx) ((OSSL_CMP_PKISI *)OPENSSL_sk_value(ossl_check_const_OSSL_CMP_PKISI_sk_type(sk), (idx)))
|
||||
#define sk_OSSL_CMP_PKISI_new(cmp) ((STACK_OF(OSSL_CMP_PKISI) *)OPENSSL_sk_new(ossl_check_OSSL_CMP_PKISI_compfunc_type(cmp)))
|
||||
#define sk_OSSL_CMP_PKISI_new_null() ((STACK_OF(OSSL_CMP_PKISI) *)OPENSSL_sk_new_null())
|
||||
#define sk_OSSL_CMP_PKISI_new_reserve(cmp, n) ((STACK_OF(OSSL_CMP_PKISI) *)OPENSSL_sk_new_reserve(ossl_check_OSSL_CMP_PKISI_compfunc_type(cmp), (n)))
|
||||
#define sk_OSSL_CMP_PKISI_reserve(sk, n) OPENSSL_sk_reserve(ossl_check_OSSL_CMP_PKISI_sk_type(sk), (n))
|
||||
#define sk_OSSL_CMP_PKISI_free(sk) OPENSSL_sk_free(ossl_check_OSSL_CMP_PKISI_sk_type(sk))
|
||||
#define sk_OSSL_CMP_PKISI_zero(sk) OPENSSL_sk_zero(ossl_check_OSSL_CMP_PKISI_sk_type(sk))
|
||||
#define sk_OSSL_CMP_PKISI_delete(sk, i) ((OSSL_CMP_PKISI *)OPENSSL_sk_delete(ossl_check_OSSL_CMP_PKISI_sk_type(sk), (i)))
|
||||
#define sk_OSSL_CMP_PKISI_delete_ptr(sk, ptr) ((OSSL_CMP_PKISI *)OPENSSL_sk_delete_ptr(ossl_check_OSSL_CMP_PKISI_sk_type(sk), ossl_check_OSSL_CMP_PKISI_type(ptr)))
|
||||
#define sk_OSSL_CMP_PKISI_push(sk, ptr) OPENSSL_sk_push(ossl_check_OSSL_CMP_PKISI_sk_type(sk), ossl_check_OSSL_CMP_PKISI_type(ptr))
|
||||
#define sk_OSSL_CMP_PKISI_unshift(sk, ptr) OPENSSL_sk_unshift(ossl_check_OSSL_CMP_PKISI_sk_type(sk), ossl_check_OSSL_CMP_PKISI_type(ptr))
|
||||
#define sk_OSSL_CMP_PKISI_pop(sk) ((OSSL_CMP_PKISI *)OPENSSL_sk_pop(ossl_check_OSSL_CMP_PKISI_sk_type(sk)))
|
||||
#define sk_OSSL_CMP_PKISI_shift(sk) ((OSSL_CMP_PKISI *)OPENSSL_sk_shift(ossl_check_OSSL_CMP_PKISI_sk_type(sk)))
|
||||
#define sk_OSSL_CMP_PKISI_pop_free(sk, freefunc) OPENSSL_sk_pop_free(ossl_check_OSSL_CMP_PKISI_sk_type(sk),ossl_check_OSSL_CMP_PKISI_freefunc_type(freefunc))
|
||||
#define sk_OSSL_CMP_PKISI_insert(sk, ptr, idx) OPENSSL_sk_insert(ossl_check_OSSL_CMP_PKISI_sk_type(sk), ossl_check_OSSL_CMP_PKISI_type(ptr), (idx))
|
||||
#define sk_OSSL_CMP_PKISI_set(sk, idx, ptr) ((OSSL_CMP_PKISI *)OPENSSL_sk_set(ossl_check_OSSL_CMP_PKISI_sk_type(sk), (idx), ossl_check_OSSL_CMP_PKISI_type(ptr)))
|
||||
#define sk_OSSL_CMP_PKISI_find(sk, ptr) OPENSSL_sk_find(ossl_check_OSSL_CMP_PKISI_sk_type(sk), ossl_check_OSSL_CMP_PKISI_type(ptr))
|
||||
#define sk_OSSL_CMP_PKISI_find_ex(sk, ptr) OPENSSL_sk_find_ex(ossl_check_OSSL_CMP_PKISI_sk_type(sk), ossl_check_OSSL_CMP_PKISI_type(ptr))
|
||||
#define sk_OSSL_CMP_PKISI_find_all(sk, ptr, pnum) OPENSSL_sk_find_all(ossl_check_OSSL_CMP_PKISI_sk_type(sk), ossl_check_OSSL_CMP_PKISI_type(ptr), pnum)
|
||||
#define sk_OSSL_CMP_PKISI_sort(sk) OPENSSL_sk_sort(ossl_check_OSSL_CMP_PKISI_sk_type(sk))
|
||||
#define sk_OSSL_CMP_PKISI_is_sorted(sk) OPENSSL_sk_is_sorted(ossl_check_const_OSSL_CMP_PKISI_sk_type(sk))
|
||||
#define sk_OSSL_CMP_PKISI_dup(sk) ((STACK_OF(OSSL_CMP_PKISI) *)OPENSSL_sk_dup(ossl_check_const_OSSL_CMP_PKISI_sk_type(sk)))
|
||||
#define sk_OSSL_CMP_PKISI_deep_copy(sk, copyfunc, freefunc) ((STACK_OF(OSSL_CMP_PKISI) *)OPENSSL_sk_deep_copy(ossl_check_const_OSSL_CMP_PKISI_sk_type(sk), ossl_check_OSSL_CMP_PKISI_copyfunc_type(copyfunc), ossl_check_OSSL_CMP_PKISI_freefunc_type(freefunc)))
|
||||
#define sk_OSSL_CMP_PKISI_set_cmp_func(sk, cmp) ((sk_OSSL_CMP_PKISI_compfunc)OPENSSL_sk_set_cmp_func(ossl_check_OSSL_CMP_PKISI_sk_type(sk), ossl_check_OSSL_CMP_PKISI_compfunc_type(cmp)))
|
||||
|
||||
typedef struct ossl_cmp_certrepmessage_st OSSL_CMP_CERTREPMESSAGE;
|
||||
SKM_DEFINE_STACK_OF_INTERNAL(OSSL_CMP_CERTREPMESSAGE, OSSL_CMP_CERTREPMESSAGE, OSSL_CMP_CERTREPMESSAGE)
|
||||
#define sk_OSSL_CMP_CERTREPMESSAGE_num(sk) OPENSSL_sk_num(ossl_check_const_OSSL_CMP_CERTREPMESSAGE_sk_type(sk))
|
||||
#define sk_OSSL_CMP_CERTREPMESSAGE_value(sk, idx) ((OSSL_CMP_CERTREPMESSAGE *)OPENSSL_sk_value(ossl_check_const_OSSL_CMP_CERTREPMESSAGE_sk_type(sk), (idx)))
|
||||
#define sk_OSSL_CMP_CERTREPMESSAGE_new(cmp) ((STACK_OF(OSSL_CMP_CERTREPMESSAGE) *)OPENSSL_sk_new(ossl_check_OSSL_CMP_CERTREPMESSAGE_compfunc_type(cmp)))
|
||||
#define sk_OSSL_CMP_CERTREPMESSAGE_new_null() ((STACK_OF(OSSL_CMP_CERTREPMESSAGE) *)OPENSSL_sk_new_null())
|
||||
#define sk_OSSL_CMP_CERTREPMESSAGE_new_reserve(cmp, n) ((STACK_OF(OSSL_CMP_CERTREPMESSAGE) *)OPENSSL_sk_new_reserve(ossl_check_OSSL_CMP_CERTREPMESSAGE_compfunc_type(cmp), (n)))
|
||||
#define sk_OSSL_CMP_CERTREPMESSAGE_reserve(sk, n) OPENSSL_sk_reserve(ossl_check_OSSL_CMP_CERTREPMESSAGE_sk_type(sk), (n))
|
||||
#define sk_OSSL_CMP_CERTREPMESSAGE_free(sk) OPENSSL_sk_free(ossl_check_OSSL_CMP_CERTREPMESSAGE_sk_type(sk))
|
||||
#define sk_OSSL_CMP_CERTREPMESSAGE_zero(sk) OPENSSL_sk_zero(ossl_check_OSSL_CMP_CERTREPMESSAGE_sk_type(sk))
|
||||
#define sk_OSSL_CMP_CERTREPMESSAGE_delete(sk, i) ((OSSL_CMP_CERTREPMESSAGE *)OPENSSL_sk_delete(ossl_check_OSSL_CMP_CERTREPMESSAGE_sk_type(sk), (i)))
|
||||
#define sk_OSSL_CMP_CERTREPMESSAGE_delete_ptr(sk, ptr) ((OSSL_CMP_CERTREPMESSAGE *)OPENSSL_sk_delete_ptr(ossl_check_OSSL_CMP_CERTREPMESSAGE_sk_type(sk), ossl_check_OSSL_CMP_CERTREPMESSAGE_type(ptr)))
|
||||
#define sk_OSSL_CMP_CERTREPMESSAGE_push(sk, ptr) OPENSSL_sk_push(ossl_check_OSSL_CMP_CERTREPMESSAGE_sk_type(sk), ossl_check_OSSL_CMP_CERTREPMESSAGE_type(ptr))
|
||||
#define sk_OSSL_CMP_CERTREPMESSAGE_unshift(sk, ptr) OPENSSL_sk_unshift(ossl_check_OSSL_CMP_CERTREPMESSAGE_sk_type(sk), ossl_check_OSSL_CMP_CERTREPMESSAGE_type(ptr))
|
||||
#define sk_OSSL_CMP_CERTREPMESSAGE_pop(sk) ((OSSL_CMP_CERTREPMESSAGE *)OPENSSL_sk_pop(ossl_check_OSSL_CMP_CERTREPMESSAGE_sk_type(sk)))
|
||||
#define sk_OSSL_CMP_CERTREPMESSAGE_shift(sk) ((OSSL_CMP_CERTREPMESSAGE *)OPENSSL_sk_shift(ossl_check_OSSL_CMP_CERTREPMESSAGE_sk_type(sk)))
|
||||
#define sk_OSSL_CMP_CERTREPMESSAGE_pop_free(sk, freefunc) OPENSSL_sk_pop_free(ossl_check_OSSL_CMP_CERTREPMESSAGE_sk_type(sk),ossl_check_OSSL_CMP_CERTREPMESSAGE_freefunc_type(freefunc))
|
||||
#define sk_OSSL_CMP_CERTREPMESSAGE_insert(sk, ptr, idx) OPENSSL_sk_insert(ossl_check_OSSL_CMP_CERTREPMESSAGE_sk_type(sk), ossl_check_OSSL_CMP_CERTREPMESSAGE_type(ptr), (idx))
|
||||
#define sk_OSSL_CMP_CERTREPMESSAGE_set(sk, idx, ptr) ((OSSL_CMP_CERTREPMESSAGE *)OPENSSL_sk_set(ossl_check_OSSL_CMP_CERTREPMESSAGE_sk_type(sk), (idx), ossl_check_OSSL_CMP_CERTREPMESSAGE_type(ptr)))
|
||||
#define sk_OSSL_CMP_CERTREPMESSAGE_find(sk, ptr) OPENSSL_sk_find(ossl_check_OSSL_CMP_CERTREPMESSAGE_sk_type(sk), ossl_check_OSSL_CMP_CERTREPMESSAGE_type(ptr))
|
||||
#define sk_OSSL_CMP_CERTREPMESSAGE_find_ex(sk, ptr) OPENSSL_sk_find_ex(ossl_check_OSSL_CMP_CERTREPMESSAGE_sk_type(sk), ossl_check_OSSL_CMP_CERTREPMESSAGE_type(ptr))
|
||||
#define sk_OSSL_CMP_CERTREPMESSAGE_find_all(sk, ptr, pnum) OPENSSL_sk_find_all(ossl_check_OSSL_CMP_CERTREPMESSAGE_sk_type(sk), ossl_check_OSSL_CMP_CERTREPMESSAGE_type(ptr), pnum)
|
||||
#define sk_OSSL_CMP_CERTREPMESSAGE_sort(sk) OPENSSL_sk_sort(ossl_check_OSSL_CMP_CERTREPMESSAGE_sk_type(sk))
|
||||
#define sk_OSSL_CMP_CERTREPMESSAGE_is_sorted(sk) OPENSSL_sk_is_sorted(ossl_check_const_OSSL_CMP_CERTREPMESSAGE_sk_type(sk))
|
||||
#define sk_OSSL_CMP_CERTREPMESSAGE_dup(sk) ((STACK_OF(OSSL_CMP_CERTREPMESSAGE) *)OPENSSL_sk_dup(ossl_check_const_OSSL_CMP_CERTREPMESSAGE_sk_type(sk)))
|
||||
#define sk_OSSL_CMP_CERTREPMESSAGE_deep_copy(sk, copyfunc, freefunc) ((STACK_OF(OSSL_CMP_CERTREPMESSAGE) *)OPENSSL_sk_deep_copy(ossl_check_const_OSSL_CMP_CERTREPMESSAGE_sk_type(sk), ossl_check_OSSL_CMP_CERTREPMESSAGE_copyfunc_type(copyfunc), ossl_check_OSSL_CMP_CERTREPMESSAGE_freefunc_type(freefunc)))
|
||||
#define sk_OSSL_CMP_CERTREPMESSAGE_set_cmp_func(sk, cmp) ((sk_OSSL_CMP_CERTREPMESSAGE_compfunc)OPENSSL_sk_set_cmp_func(ossl_check_OSSL_CMP_CERTREPMESSAGE_sk_type(sk), ossl_check_OSSL_CMP_CERTREPMESSAGE_compfunc_type(cmp)))
|
||||
|
||||
typedef struct ossl_cmp_pollrep_st OSSL_CMP_POLLREP;
|
||||
typedef STACK_OF(OSSL_CMP_POLLREP) OSSL_CMP_POLLREPCONTENT;
|
||||
typedef struct ossl_cmp_certresponse_st OSSL_CMP_CERTRESPONSE;
|
||||
SKM_DEFINE_STACK_OF_INTERNAL(OSSL_CMP_CERTRESPONSE, OSSL_CMP_CERTRESPONSE, OSSL_CMP_CERTRESPONSE)
|
||||
#define sk_OSSL_CMP_CERTRESPONSE_num(sk) OPENSSL_sk_num(ossl_check_const_OSSL_CMP_CERTRESPONSE_sk_type(sk))
|
||||
#define sk_OSSL_CMP_CERTRESPONSE_value(sk, idx) ((OSSL_CMP_CERTRESPONSE *)OPENSSL_sk_value(ossl_check_const_OSSL_CMP_CERTRESPONSE_sk_type(sk), (idx)))
|
||||
#define sk_OSSL_CMP_CERTRESPONSE_new(cmp) ((STACK_OF(OSSL_CMP_CERTRESPONSE) *)OPENSSL_sk_new(ossl_check_OSSL_CMP_CERTRESPONSE_compfunc_type(cmp)))
|
||||
#define sk_OSSL_CMP_CERTRESPONSE_new_null() ((STACK_OF(OSSL_CMP_CERTRESPONSE) *)OPENSSL_sk_new_null())
|
||||
#define sk_OSSL_CMP_CERTRESPONSE_new_reserve(cmp, n) ((STACK_OF(OSSL_CMP_CERTRESPONSE) *)OPENSSL_sk_new_reserve(ossl_check_OSSL_CMP_CERTRESPONSE_compfunc_type(cmp), (n)))
|
||||
#define sk_OSSL_CMP_CERTRESPONSE_reserve(sk, n) OPENSSL_sk_reserve(ossl_check_OSSL_CMP_CERTRESPONSE_sk_type(sk), (n))
|
||||
#define sk_OSSL_CMP_CERTRESPONSE_free(sk) OPENSSL_sk_free(ossl_check_OSSL_CMP_CERTRESPONSE_sk_type(sk))
|
||||
#define sk_OSSL_CMP_CERTRESPONSE_zero(sk) OPENSSL_sk_zero(ossl_check_OSSL_CMP_CERTRESPONSE_sk_type(sk))
|
||||
#define sk_OSSL_CMP_CERTRESPONSE_delete(sk, i) ((OSSL_CMP_CERTRESPONSE *)OPENSSL_sk_delete(ossl_check_OSSL_CMP_CERTRESPONSE_sk_type(sk), (i)))
|
||||
#define sk_OSSL_CMP_CERTRESPONSE_delete_ptr(sk, ptr) ((OSSL_CMP_CERTRESPONSE *)OPENSSL_sk_delete_ptr(ossl_check_OSSL_CMP_CERTRESPONSE_sk_type(sk), ossl_check_OSSL_CMP_CERTRESPONSE_type(ptr)))
|
||||
#define sk_OSSL_CMP_CERTRESPONSE_push(sk, ptr) OPENSSL_sk_push(ossl_check_OSSL_CMP_CERTRESPONSE_sk_type(sk), ossl_check_OSSL_CMP_CERTRESPONSE_type(ptr))
|
||||
#define sk_OSSL_CMP_CERTRESPONSE_unshift(sk, ptr) OPENSSL_sk_unshift(ossl_check_OSSL_CMP_CERTRESPONSE_sk_type(sk), ossl_check_OSSL_CMP_CERTRESPONSE_type(ptr))
|
||||
#define sk_OSSL_CMP_CERTRESPONSE_pop(sk) ((OSSL_CMP_CERTRESPONSE *)OPENSSL_sk_pop(ossl_check_OSSL_CMP_CERTRESPONSE_sk_type(sk)))
|
||||
#define sk_OSSL_CMP_CERTRESPONSE_shift(sk) ((OSSL_CMP_CERTRESPONSE *)OPENSSL_sk_shift(ossl_check_OSSL_CMP_CERTRESPONSE_sk_type(sk)))
|
||||
#define sk_OSSL_CMP_CERTRESPONSE_pop_free(sk, freefunc) OPENSSL_sk_pop_free(ossl_check_OSSL_CMP_CERTRESPONSE_sk_type(sk),ossl_check_OSSL_CMP_CERTRESPONSE_freefunc_type(freefunc))
|
||||
#define sk_OSSL_CMP_CERTRESPONSE_insert(sk, ptr, idx) OPENSSL_sk_insert(ossl_check_OSSL_CMP_CERTRESPONSE_sk_type(sk), ossl_check_OSSL_CMP_CERTRESPONSE_type(ptr), (idx))
|
||||
#define sk_OSSL_CMP_CERTRESPONSE_set(sk, idx, ptr) ((OSSL_CMP_CERTRESPONSE *)OPENSSL_sk_set(ossl_check_OSSL_CMP_CERTRESPONSE_sk_type(sk), (idx), ossl_check_OSSL_CMP_CERTRESPONSE_type(ptr)))
|
||||
#define sk_OSSL_CMP_CERTRESPONSE_find(sk, ptr) OPENSSL_sk_find(ossl_check_OSSL_CMP_CERTRESPONSE_sk_type(sk), ossl_check_OSSL_CMP_CERTRESPONSE_type(ptr))
|
||||
#define sk_OSSL_CMP_CERTRESPONSE_find_ex(sk, ptr) OPENSSL_sk_find_ex(ossl_check_OSSL_CMP_CERTRESPONSE_sk_type(sk), ossl_check_OSSL_CMP_CERTRESPONSE_type(ptr))
|
||||
#define sk_OSSL_CMP_CERTRESPONSE_find_all(sk, ptr, pnum) OPENSSL_sk_find_all(ossl_check_OSSL_CMP_CERTRESPONSE_sk_type(sk), ossl_check_OSSL_CMP_CERTRESPONSE_type(ptr), pnum)
|
||||
#define sk_OSSL_CMP_CERTRESPONSE_sort(sk) OPENSSL_sk_sort(ossl_check_OSSL_CMP_CERTRESPONSE_sk_type(sk))
|
||||
#define sk_OSSL_CMP_CERTRESPONSE_is_sorted(sk) OPENSSL_sk_is_sorted(ossl_check_const_OSSL_CMP_CERTRESPONSE_sk_type(sk))
|
||||
#define sk_OSSL_CMP_CERTRESPONSE_dup(sk) ((STACK_OF(OSSL_CMP_CERTRESPONSE) *)OPENSSL_sk_dup(ossl_check_const_OSSL_CMP_CERTRESPONSE_sk_type(sk)))
|
||||
#define sk_OSSL_CMP_CERTRESPONSE_deep_copy(sk, copyfunc, freefunc) ((STACK_OF(OSSL_CMP_CERTRESPONSE) *)OPENSSL_sk_deep_copy(ossl_check_const_OSSL_CMP_CERTRESPONSE_sk_type(sk), ossl_check_OSSL_CMP_CERTRESPONSE_copyfunc_type(copyfunc), ossl_check_OSSL_CMP_CERTRESPONSE_freefunc_type(freefunc)))
|
||||
#define sk_OSSL_CMP_CERTRESPONSE_set_cmp_func(sk, cmp) ((sk_OSSL_CMP_CERTRESPONSE_compfunc)OPENSSL_sk_set_cmp_func(ossl_check_OSSL_CMP_CERTRESPONSE_sk_type(sk), ossl_check_OSSL_CMP_CERTRESPONSE_compfunc_type(cmp)))
|
||||
|
||||
typedef STACK_OF(ASN1_UTF8STRING) OSSL_CMP_PKIFREETEXT;
|
||||
|
||||
/*
|
||||
* function DECLARATIONS
|
||||
*/
|
||||
|
||||
/* from cmp_asn.c */
|
||||
OSSL_CMP_ITAV *OSSL_CMP_ITAV_create(ASN1_OBJECT *type, ASN1_TYPE *value);
|
||||
void OSSL_CMP_ITAV_set0(OSSL_CMP_ITAV *itav, ASN1_OBJECT *type,
|
||||
ASN1_TYPE *value);
|
||||
ASN1_OBJECT *OSSL_CMP_ITAV_get0_type(const OSSL_CMP_ITAV *itav);
|
||||
ASN1_TYPE *OSSL_CMP_ITAV_get0_value(const OSSL_CMP_ITAV *itav);
|
||||
int OSSL_CMP_ITAV_push0_stack_item(STACK_OF(OSSL_CMP_ITAV) **itav_sk_p,
|
||||
OSSL_CMP_ITAV *itav);
|
||||
void OSSL_CMP_ITAV_free(OSSL_CMP_ITAV *itav);
|
||||
void OSSL_CMP_MSG_free(OSSL_CMP_MSG *msg);
|
||||
|
||||
/* from cmp_ctx.c */
|
||||
OSSL_CMP_CTX *OSSL_CMP_CTX_new(OSSL_LIB_CTX *libctx, const char *propq);
|
||||
void OSSL_CMP_CTX_free(OSSL_CMP_CTX *ctx);
|
||||
int OSSL_CMP_CTX_reinit(OSSL_CMP_CTX *ctx);
|
||||
/* CMP general options: */
|
||||
# define OSSL_CMP_OPT_LOG_VERBOSITY 0
|
||||
/* CMP transfer options: */
|
||||
# define OSSL_CMP_OPT_KEEP_ALIVE 10
|
||||
# define OSSL_CMP_OPT_MSG_TIMEOUT 11
|
||||
# define OSSL_CMP_OPT_TOTAL_TIMEOUT 12
|
||||
/* CMP request options: */
|
||||
# define OSSL_CMP_OPT_VALIDITY_DAYS 20
|
||||
# define OSSL_CMP_OPT_SUBJECTALTNAME_NODEFAULT 21
|
||||
# define OSSL_CMP_OPT_SUBJECTALTNAME_CRITICAL 22
|
||||
# define OSSL_CMP_OPT_POLICIES_CRITICAL 23
|
||||
# define OSSL_CMP_OPT_POPO_METHOD 24
|
||||
# define OSSL_CMP_OPT_IMPLICIT_CONFIRM 25
|
||||
# define OSSL_CMP_OPT_DISABLE_CONFIRM 26
|
||||
# define OSSL_CMP_OPT_REVOCATION_REASON 27
|
||||
/* CMP protection options: */
|
||||
# define OSSL_CMP_OPT_UNPROTECTED_SEND 30
|
||||
# define OSSL_CMP_OPT_UNPROTECTED_ERRORS 31
|
||||
# define OSSL_CMP_OPT_OWF_ALGNID 32
|
||||
# define OSSL_CMP_OPT_MAC_ALGNID 33
|
||||
# define OSSL_CMP_OPT_DIGEST_ALGNID 34
|
||||
# define OSSL_CMP_OPT_IGNORE_KEYUSAGE 35
|
||||
# define OSSL_CMP_OPT_PERMIT_TA_IN_EXTRACERTS_FOR_IR 36
|
||||
int OSSL_CMP_CTX_set_option(OSSL_CMP_CTX *ctx, int opt, int val);
|
||||
int OSSL_CMP_CTX_get_option(const OSSL_CMP_CTX *ctx, int opt);
|
||||
/* CMP-specific callback for logging and outputting the error queue: */
|
||||
int OSSL_CMP_CTX_set_log_cb(OSSL_CMP_CTX *ctx, OSSL_CMP_log_cb_t cb);
|
||||
# define OSSL_CMP_CTX_set_log_verbosity(ctx, level) \
|
||||
OSSL_CMP_CTX_set_option(ctx, OSSL_CMP_OPT_LOG_VERBOSITY, level)
|
||||
void OSSL_CMP_CTX_print_errors(const OSSL_CMP_CTX *ctx);
|
||||
/* message transfer: */
|
||||
int OSSL_CMP_CTX_set1_serverPath(OSSL_CMP_CTX *ctx, const char *path);
|
||||
int OSSL_CMP_CTX_set1_server(OSSL_CMP_CTX *ctx, const char *address);
|
||||
int OSSL_CMP_CTX_set_serverPort(OSSL_CMP_CTX *ctx, int port);
|
||||
int OSSL_CMP_CTX_set1_proxy(OSSL_CMP_CTX *ctx, const char *name);
|
||||
int OSSL_CMP_CTX_set1_no_proxy(OSSL_CMP_CTX *ctx, const char *names);
|
||||
int OSSL_CMP_CTX_set_http_cb(OSSL_CMP_CTX *ctx, OSSL_HTTP_bio_cb_t cb);
|
||||
int OSSL_CMP_CTX_set_http_cb_arg(OSSL_CMP_CTX *ctx, void *arg);
|
||||
void *OSSL_CMP_CTX_get_http_cb_arg(const OSSL_CMP_CTX *ctx);
|
||||
typedef OSSL_CMP_MSG *(*OSSL_CMP_transfer_cb_t) (OSSL_CMP_CTX *ctx,
|
||||
const OSSL_CMP_MSG *req);
|
||||
int OSSL_CMP_CTX_set_transfer_cb(OSSL_CMP_CTX *ctx, OSSL_CMP_transfer_cb_t cb);
|
||||
int OSSL_CMP_CTX_set_transfer_cb_arg(OSSL_CMP_CTX *ctx, void *arg);
|
||||
void *OSSL_CMP_CTX_get_transfer_cb_arg(const OSSL_CMP_CTX *ctx);
|
||||
/* server authentication: */
|
||||
int OSSL_CMP_CTX_set1_srvCert(OSSL_CMP_CTX *ctx, X509 *cert);
|
||||
int OSSL_CMP_CTX_set1_expected_sender(OSSL_CMP_CTX *ctx, const X509_NAME *name);
|
||||
int OSSL_CMP_CTX_set0_trustedStore(OSSL_CMP_CTX *ctx, X509_STORE *store);
|
||||
X509_STORE *OSSL_CMP_CTX_get0_trustedStore(const OSSL_CMP_CTX *ctx);
|
||||
int OSSL_CMP_CTX_set1_untrusted(OSSL_CMP_CTX *ctx, STACK_OF(X509) *certs);
|
||||
STACK_OF(X509) *OSSL_CMP_CTX_get0_untrusted(const OSSL_CMP_CTX *ctx);
|
||||
/* client authentication: */
|
||||
int OSSL_CMP_CTX_set1_cert(OSSL_CMP_CTX *ctx, X509 *cert);
|
||||
int OSSL_CMP_CTX_build_cert_chain(OSSL_CMP_CTX *ctx, X509_STORE *own_trusted,
|
||||
STACK_OF(X509) *candidates);
|
||||
int OSSL_CMP_CTX_set1_pkey(OSSL_CMP_CTX *ctx, EVP_PKEY *pkey);
|
||||
int OSSL_CMP_CTX_set1_referenceValue(OSSL_CMP_CTX *ctx,
|
||||
const unsigned char *ref, int len);
|
||||
int OSSL_CMP_CTX_set1_secretValue(OSSL_CMP_CTX *ctx, const unsigned char *sec,
|
||||
const int len);
|
||||
/* CMP message header and extra certificates: */
|
||||
int OSSL_CMP_CTX_set1_recipient(OSSL_CMP_CTX *ctx, const X509_NAME *name);
|
||||
int OSSL_CMP_CTX_push0_geninfo_ITAV(OSSL_CMP_CTX *ctx, OSSL_CMP_ITAV *itav);
|
||||
int OSSL_CMP_CTX_set1_extraCertsOut(OSSL_CMP_CTX *ctx,
|
||||
STACK_OF(X509) *extraCertsOut);
|
||||
/* certificate template: */
|
||||
int OSSL_CMP_CTX_set0_newPkey(OSSL_CMP_CTX *ctx, int priv, EVP_PKEY *pkey);
|
||||
EVP_PKEY *OSSL_CMP_CTX_get0_newPkey(const OSSL_CMP_CTX *ctx, int priv);
|
||||
int OSSL_CMP_CTX_set1_issuer(OSSL_CMP_CTX *ctx, const X509_NAME *name);
|
||||
int OSSL_CMP_CTX_set1_subjectName(OSSL_CMP_CTX *ctx, const X509_NAME *name);
|
||||
int OSSL_CMP_CTX_push1_subjectAltName(OSSL_CMP_CTX *ctx,
|
||||
const GENERAL_NAME *name);
|
||||
int OSSL_CMP_CTX_set0_reqExtensions(OSSL_CMP_CTX *ctx, X509_EXTENSIONS *exts);
|
||||
int OSSL_CMP_CTX_reqExtensions_have_SAN(OSSL_CMP_CTX *ctx);
|
||||
int OSSL_CMP_CTX_push0_policy(OSSL_CMP_CTX *ctx, POLICYINFO *pinfo);
|
||||
int OSSL_CMP_CTX_set1_oldCert(OSSL_CMP_CTX *ctx, X509 *cert);
|
||||
int OSSL_CMP_CTX_set1_p10CSR(OSSL_CMP_CTX *ctx, const X509_REQ *csr);
|
||||
/* misc body contents: */
|
||||
int OSSL_CMP_CTX_push0_genm_ITAV(OSSL_CMP_CTX *ctx, OSSL_CMP_ITAV *itav);
|
||||
/* certificate confirmation: */
|
||||
typedef int (*OSSL_CMP_certConf_cb_t) (OSSL_CMP_CTX *ctx, X509 *cert,
|
||||
int fail_info, const char **txt);
|
||||
int OSSL_CMP_certConf_cb(OSSL_CMP_CTX *ctx, X509 *cert, int fail_info,
|
||||
const char **text);
|
||||
int OSSL_CMP_CTX_set_certConf_cb(OSSL_CMP_CTX *ctx, OSSL_CMP_certConf_cb_t cb);
|
||||
int OSSL_CMP_CTX_set_certConf_cb_arg(OSSL_CMP_CTX *ctx, void *arg);
|
||||
void *OSSL_CMP_CTX_get_certConf_cb_arg(const OSSL_CMP_CTX *ctx);
|
||||
/* result fetching: */
|
||||
int OSSL_CMP_CTX_get_status(const OSSL_CMP_CTX *ctx);
|
||||
OSSL_CMP_PKIFREETEXT *OSSL_CMP_CTX_get0_statusString(const OSSL_CMP_CTX *ctx);
|
||||
int OSSL_CMP_CTX_get_failInfoCode(const OSSL_CMP_CTX *ctx);
|
||||
# define OSSL_CMP_PKISI_BUFLEN 1024
|
||||
X509 *OSSL_CMP_CTX_get0_newCert(const OSSL_CMP_CTX *ctx);
|
||||
STACK_OF(X509) *OSSL_CMP_CTX_get1_newChain(const OSSL_CMP_CTX *ctx);
|
||||
STACK_OF(X509) *OSSL_CMP_CTX_get1_caPubs(const OSSL_CMP_CTX *ctx);
|
||||
STACK_OF(X509) *OSSL_CMP_CTX_get1_extraCertsIn(const OSSL_CMP_CTX *ctx);
|
||||
int OSSL_CMP_CTX_set1_transactionID(OSSL_CMP_CTX *ctx,
|
||||
const ASN1_OCTET_STRING *id);
|
||||
int OSSL_CMP_CTX_set1_senderNonce(OSSL_CMP_CTX *ctx,
|
||||
const ASN1_OCTET_STRING *nonce);
|
||||
|
||||
/* from cmp_status.c */
|
||||
char *OSSL_CMP_CTX_snprint_PKIStatus(const OSSL_CMP_CTX *ctx, char *buf,
|
||||
size_t bufsize);
|
||||
char *OSSL_CMP_snprint_PKIStatusInfo(const OSSL_CMP_PKISI *statusInfo,
|
||||
char *buf, size_t bufsize);
|
||||
OSSL_CMP_PKISI *
|
||||
OSSL_CMP_STATUSINFO_new(int status, int fail_info, const char *text);
|
||||
|
||||
/* from cmp_hdr.c */
|
||||
ASN1_OCTET_STRING *OSSL_CMP_HDR_get0_transactionID(const
|
||||
OSSL_CMP_PKIHEADER *hdr);
|
||||
ASN1_OCTET_STRING *OSSL_CMP_HDR_get0_recipNonce(const OSSL_CMP_PKIHEADER *hdr);
|
||||
|
||||
/* from cmp_msg.c */
|
||||
OSSL_CMP_PKIHEADER *OSSL_CMP_MSG_get0_header(const OSSL_CMP_MSG *msg);
|
||||
int OSSL_CMP_MSG_get_bodytype(const OSSL_CMP_MSG *msg);
|
||||
int OSSL_CMP_MSG_update_transactionID(OSSL_CMP_CTX *ctx, OSSL_CMP_MSG *msg);
|
||||
OSSL_CRMF_MSG *OSSL_CMP_CTX_setup_CRM(OSSL_CMP_CTX *ctx, int for_KUR, int rid);
|
||||
OSSL_CMP_MSG *OSSL_CMP_MSG_read(const char *file, OSSL_LIB_CTX *libctx,
|
||||
const char *propq);
|
||||
int OSSL_CMP_MSG_write(const char *file, const OSSL_CMP_MSG *msg);
|
||||
OSSL_CMP_MSG *d2i_OSSL_CMP_MSG_bio(BIO *bio, OSSL_CMP_MSG **msg);
|
||||
int i2d_OSSL_CMP_MSG_bio(BIO *bio, const OSSL_CMP_MSG *msg);
|
||||
|
||||
/* from cmp_vfy.c */
|
||||
int OSSL_CMP_validate_msg(OSSL_CMP_CTX *ctx, const OSSL_CMP_MSG *msg);
|
||||
int OSSL_CMP_validate_cert_path(const OSSL_CMP_CTX *ctx,
|
||||
X509_STORE *trusted_store, X509 *cert);
|
||||
|
||||
/* from cmp_http.c */
|
||||
OSSL_CMP_MSG *OSSL_CMP_MSG_http_perform(OSSL_CMP_CTX *ctx,
|
||||
const OSSL_CMP_MSG *req);
|
||||
|
||||
/* from cmp_server.c */
|
||||
typedef struct ossl_cmp_srv_ctx_st OSSL_CMP_SRV_CTX;
|
||||
OSSL_CMP_MSG *OSSL_CMP_SRV_process_request(OSSL_CMP_SRV_CTX *srv_ctx,
|
||||
const OSSL_CMP_MSG *req);
|
||||
OSSL_CMP_MSG * OSSL_CMP_CTX_server_perform(OSSL_CMP_CTX *client_ctx,
|
||||
const OSSL_CMP_MSG *req);
|
||||
OSSL_CMP_SRV_CTX *OSSL_CMP_SRV_CTX_new(OSSL_LIB_CTX *libctx, const char *propq);
|
||||
void OSSL_CMP_SRV_CTX_free(OSSL_CMP_SRV_CTX *srv_ctx);
|
||||
typedef OSSL_CMP_PKISI *(*OSSL_CMP_SRV_cert_request_cb_t)
|
||||
(OSSL_CMP_SRV_CTX *srv_ctx, const OSSL_CMP_MSG *req, int certReqId,
|
||||
const OSSL_CRMF_MSG *crm, const X509_REQ *p10cr,
|
||||
X509 **certOut, STACK_OF(X509) **chainOut, STACK_OF(X509) **caPubs);
|
||||
typedef OSSL_CMP_PKISI *(*OSSL_CMP_SRV_rr_cb_t)(OSSL_CMP_SRV_CTX *srv_ctx,
|
||||
const OSSL_CMP_MSG *req,
|
||||
const X509_NAME *issuer,
|
||||
const ASN1_INTEGER *serial);
|
||||
typedef int (*OSSL_CMP_SRV_genm_cb_t)(OSSL_CMP_SRV_CTX *srv_ctx,
|
||||
const OSSL_CMP_MSG *req,
|
||||
const STACK_OF(OSSL_CMP_ITAV) *in,
|
||||
STACK_OF(OSSL_CMP_ITAV) **out);
|
||||
typedef void (*OSSL_CMP_SRV_error_cb_t)(OSSL_CMP_SRV_CTX *srv_ctx,
|
||||
const OSSL_CMP_MSG *req,
|
||||
const OSSL_CMP_PKISI *statusInfo,
|
||||
const ASN1_INTEGER *errorCode,
|
||||
const OSSL_CMP_PKIFREETEXT *errDetails);
|
||||
typedef int (*OSSL_CMP_SRV_certConf_cb_t)(OSSL_CMP_SRV_CTX *srv_ctx,
|
||||
const OSSL_CMP_MSG *req,
|
||||
int certReqId,
|
||||
const ASN1_OCTET_STRING *certHash,
|
||||
const OSSL_CMP_PKISI *si);
|
||||
typedef int (*OSSL_CMP_SRV_pollReq_cb_t)(OSSL_CMP_SRV_CTX *srv_ctx,
|
||||
const OSSL_CMP_MSG *req, int certReqId,
|
||||
OSSL_CMP_MSG **certReq,
|
||||
int64_t *check_after);
|
||||
int OSSL_CMP_SRV_CTX_init(OSSL_CMP_SRV_CTX *srv_ctx, void *custom_ctx,
|
||||
OSSL_CMP_SRV_cert_request_cb_t process_cert_request,
|
||||
OSSL_CMP_SRV_rr_cb_t process_rr,
|
||||
OSSL_CMP_SRV_genm_cb_t process_genm,
|
||||
OSSL_CMP_SRV_error_cb_t process_error,
|
||||
OSSL_CMP_SRV_certConf_cb_t process_certConf,
|
||||
OSSL_CMP_SRV_pollReq_cb_t process_pollReq);
|
||||
OSSL_CMP_CTX *OSSL_CMP_SRV_CTX_get0_cmp_ctx(const OSSL_CMP_SRV_CTX *srv_ctx);
|
||||
void *OSSL_CMP_SRV_CTX_get0_custom_ctx(const OSSL_CMP_SRV_CTX *srv_ctx);
|
||||
int OSSL_CMP_SRV_CTX_set_send_unprotected_errors(OSSL_CMP_SRV_CTX *srv_ctx,
|
||||
int val);
|
||||
int OSSL_CMP_SRV_CTX_set_accept_unprotected(OSSL_CMP_SRV_CTX *srv_ctx, int val);
|
||||
int OSSL_CMP_SRV_CTX_set_accept_raverified(OSSL_CMP_SRV_CTX *srv_ctx, int val);
|
||||
int OSSL_CMP_SRV_CTX_set_grant_implicit_confirm(OSSL_CMP_SRV_CTX *srv_ctx,
|
||||
int val);
|
||||
|
||||
/* from cmp_client.c */
|
||||
X509 *OSSL_CMP_exec_certreq(OSSL_CMP_CTX *ctx, int req_type,
|
||||
const OSSL_CRMF_MSG *crm);
|
||||
# define OSSL_CMP_IR 0
|
||||
# define OSSL_CMP_CR 2
|
||||
# define OSSL_CMP_P10CR 4
|
||||
# define OSSL_CMP_KUR 7
|
||||
# define OSSL_CMP_exec_IR_ses(ctx) \
|
||||
OSSL_CMP_exec_certreq(ctx, OSSL_CMP_IR, NULL)
|
||||
# define OSSL_CMP_exec_CR_ses(ctx) \
|
||||
OSSL_CMP_exec_certreq(ctx, OSSL_CMP_CR, NULL)
|
||||
# define OSSL_CMP_exec_P10CR_ses(ctx) \
|
||||
OSSL_CMP_exec_certreq(ctx, OSSL_CMP_P10CR, NULL)
|
||||
# define OSSL_CMP_exec_KUR_ses(ctx) \
|
||||
OSSL_CMP_exec_certreq(ctx, OSSL_CMP_KUR, NULL)
|
||||
int OSSL_CMP_try_certreq(OSSL_CMP_CTX *ctx, int req_type,
|
||||
const OSSL_CRMF_MSG *crm, int *checkAfter);
|
||||
int OSSL_CMP_exec_RR_ses(OSSL_CMP_CTX *ctx);
|
||||
STACK_OF(OSSL_CMP_ITAV) *OSSL_CMP_exec_GENM_ses(OSSL_CMP_CTX *ctx);
|
||||
|
||||
# ifdef __cplusplus
|
||||
}
|
||||
# endif
|
||||
# endif /* !defined(OPENSSL_NO_CMP) */
|
||||
#endif /* !defined(OPENSSL_CMP_H) */
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* WARNING: do not edit!
|
||||
* Generated by Makefile from include/openssl/configuration.h.in
|
||||
*
|
||||
* Copyright 2016-2021 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution or at
|
||||
* https://www.openssl.org/source/license.html
|
||||
*/
|
||||
|
||||
#ifndef OPENSSL_CONFIGURATION_H
|
||||
# define OPENSSL_CONFIGURATION_H
|
||||
# pragma once
|
||||
|
||||
# ifdef __cplusplus
|
||||
extern "C" {
|
||||
# endif
|
||||
|
||||
# ifdef OPENSSL_ALGORITHM_DEFINES
|
||||
# error OPENSSL_ALGORITHM_DEFINES no longer supported
|
||||
# endif
|
||||
|
||||
/*
|
||||
* OpenSSL was configured with the following options:
|
||||
*/
|
||||
|
||||
# define OPENSSL_CONFIGURED_API 30000
|
||||
# ifndef OPENSSL_RAND_SEED_OS
|
||||
# define OPENSSL_RAND_SEED_OS
|
||||
# endif
|
||||
# ifndef OPENSSL_THREADS
|
||||
# define OPENSSL_THREADS
|
||||
# endif
|
||||
# ifndef OPENSSL_NO_ACVP_TESTS
|
||||
# define OPENSSL_NO_ACVP_TESTS
|
||||
# endif
|
||||
# ifndef OPENSSL_NO_AFALGENG
|
||||
# define OPENSSL_NO_AFALGENG
|
||||
# endif
|
||||
# ifndef OPENSSL_NO_ASAN
|
||||
# define OPENSSL_NO_ASAN
|
||||
# endif
|
||||
# ifndef OPENSSL_NO_CAPIENG
|
||||
# define OPENSSL_NO_CAPIENG
|
||||
# endif
|
||||
# ifndef OPENSSL_NO_CRYPTO_MDEBUG
|
||||
# define OPENSSL_NO_CRYPTO_MDEBUG
|
||||
# endif
|
||||
# ifndef OPENSSL_NO_CRYPTO_MDEBUG_BACKTRACE
|
||||
# define OPENSSL_NO_CRYPTO_MDEBUG_BACKTRACE
|
||||
# endif
|
||||
# ifndef OPENSSL_NO_DEPRECATED
|
||||
# define OPENSSL_NO_DEPRECATED
|
||||
# endif
|
||||
# ifndef OPENSSL_NO_DEVCRYPTOENG
|
||||
# define OPENSSL_NO_DEVCRYPTOENG
|
||||
# endif
|
||||
# ifndef OPENSSL_NO_EC_NISTP_64_GCC_128
|
||||
# define OPENSSL_NO_EC_NISTP_64_GCC_128
|
||||
# endif
|
||||
# ifndef OPENSSL_NO_EGD
|
||||
# define OPENSSL_NO_EGD
|
||||
# endif
|
||||
# ifndef OPENSSL_NO_ENGINE
|
||||
# define OPENSSL_NO_ENGINE
|
||||
# endif
|
||||
# ifndef OPENSSL_NO_EXTERNAL_TESTS
|
||||
# define OPENSSL_NO_EXTERNAL_TESTS
|
||||
# endif
|
||||
# ifndef OPENSSL_NO_FIPS_SECURITYCHECKS
|
||||
# define OPENSSL_NO_FIPS_SECURITYCHECKS
|
||||
# endif
|
||||
# ifndef OPENSSL_NO_FUZZ_AFL
|
||||
# define OPENSSL_NO_FUZZ_AFL
|
||||
# endif
|
||||
# ifndef OPENSSL_NO_FUZZ_LIBFUZZER
|
||||
# define OPENSSL_NO_FUZZ_LIBFUZZER
|
||||
# endif
|
||||
# ifndef OPENSSL_NO_KTLS
|
||||
# define OPENSSL_NO_KTLS
|
||||
# endif
|
||||
# ifndef OPENSSL_NO_LOADERENG
|
||||
# define OPENSSL_NO_LOADERENG
|
||||
# endif
|
||||
# ifndef OPENSSL_NO_MD2
|
||||
# define OPENSSL_NO_MD2
|
||||
# endif
|
||||
# ifndef OPENSSL_NO_MSAN
|
||||
# define OPENSSL_NO_MSAN
|
||||
# endif
|
||||
# ifndef OPENSSL_NO_PADLOCKENG
|
||||
# define OPENSSL_NO_PADLOCKENG
|
||||
# endif
|
||||
# ifndef OPENSSL_NO_RC5
|
||||
# define OPENSSL_NO_RC5
|
||||
# endif
|
||||
# ifndef OPENSSL_NO_SCTP
|
||||
# define OPENSSL_NO_SCTP
|
||||
# endif
|
||||
# ifndef OPENSSL_NO_SRP
|
||||
# define OPENSSL_NO_SRP
|
||||
# endif
|
||||
# ifndef OPENSSL_NO_SSL3
|
||||
# define OPENSSL_NO_SSL3
|
||||
# endif
|
||||
# ifndef OPENSSL_NO_SSL3_METHOD
|
||||
# define OPENSSL_NO_SSL3_METHOD
|
||||
# endif
|
||||
# ifndef OPENSSL_NO_TESTS
|
||||
# define OPENSSL_NO_TESTS
|
||||
# endif
|
||||
# ifndef OPENSSL_NO_TRACE
|
||||
# define OPENSSL_NO_TRACE
|
||||
# endif
|
||||
# ifndef OPENSSL_NO_UBSAN
|
||||
# define OPENSSL_NO_UBSAN
|
||||
# endif
|
||||
# ifndef OPENSSL_NO_UNIT_TEST
|
||||
# define OPENSSL_NO_UNIT_TEST
|
||||
# endif
|
||||
# ifndef OPENSSL_NO_UPLINK
|
||||
# define OPENSSL_NO_UPLINK
|
||||
# endif
|
||||
# ifndef OPENSSL_NO_WEAK_SSL_CIPHERS
|
||||
# define OPENSSL_NO_WEAK_SSL_CIPHERS
|
||||
# endif
|
||||
# ifndef OPENSSL_NO_DYNAMIC_ENGINE
|
||||
# define OPENSSL_NO_DYNAMIC_ENGINE
|
||||
# endif
|
||||
|
||||
|
||||
/* Generate 80386 code? */
|
||||
# undef I386_ONLY
|
||||
|
||||
/*
|
||||
* The following are cipher-specific, but are part of the public API.
|
||||
*/
|
||||
# if !defined(OPENSSL_SYS_UEFI)
|
||||
# undef BN_LLONG
|
||||
/* Only one for the following should be defined */
|
||||
# define SIXTY_FOUR_BIT_LONG
|
||||
# undef SIXTY_FOUR_BIT
|
||||
# undef THIRTY_TWO_BIT
|
||||
# endif
|
||||
|
||||
# define RC4_INT unsigned int
|
||||
|
||||
# ifdef __cplusplus
|
||||
}
|
||||
# endif
|
||||
|
||||
#endif /* OPENSSL_CONFIGURATION_H */
|
||||
@@ -0,0 +1,227 @@
|
||||
/*-
|
||||
* WARNING: do not edit!
|
||||
* Generated by Makefile from include/openssl/crmf.h.in
|
||||
*
|
||||
* Copyright 2007-2021 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright Nokia 2007-2019
|
||||
* Copyright Siemens AG 2015-2019
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution or at
|
||||
* https://www.openssl.org/source/license.html
|
||||
*
|
||||
* CRMF (RFC 4211) implementation by M. Peylo, M. Viljanen, and D. von Oheimb.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
#ifndef OPENSSL_CRMF_H
|
||||
# define OPENSSL_CRMF_H
|
||||
|
||||
# include <openssl/opensslconf.h>
|
||||
|
||||
# ifndef OPENSSL_NO_CRMF
|
||||
# include <openssl/opensslv.h>
|
||||
# include <openssl/safestack.h>
|
||||
# include <openssl/crmferr.h>
|
||||
# include <openssl/x509v3.h> /* for GENERAL_NAME etc. */
|
||||
|
||||
/* explicit #includes not strictly needed since implied by the above: */
|
||||
# include <openssl/types.h>
|
||||
# include <openssl/x509.h>
|
||||
|
||||
# ifdef __cplusplus
|
||||
extern "C" {
|
||||
# endif
|
||||
|
||||
# define OSSL_CRMF_POPOPRIVKEY_THISMESSAGE 0
|
||||
# define OSSL_CRMF_POPOPRIVKEY_SUBSEQUENTMESSAGE 1
|
||||
# define OSSL_CRMF_POPOPRIVKEY_DHMAC 2
|
||||
# define OSSL_CRMF_POPOPRIVKEY_AGREEMAC 3
|
||||
# define OSSL_CRMF_POPOPRIVKEY_ENCRYPTEDKEY 4
|
||||
|
||||
# define OSSL_CRMF_SUBSEQUENTMESSAGE_ENCRCERT 0
|
||||
# define OSSL_CRMF_SUBSEQUENTMESSAGE_CHALLENGERESP 1
|
||||
|
||||
typedef struct ossl_crmf_encryptedvalue_st OSSL_CRMF_ENCRYPTEDVALUE;
|
||||
DECLARE_ASN1_FUNCTIONS(OSSL_CRMF_ENCRYPTEDVALUE)
|
||||
typedef struct ossl_crmf_msg_st OSSL_CRMF_MSG;
|
||||
DECLARE_ASN1_FUNCTIONS(OSSL_CRMF_MSG)
|
||||
DECLARE_ASN1_DUP_FUNCTION(OSSL_CRMF_MSG)
|
||||
SKM_DEFINE_STACK_OF_INTERNAL(OSSL_CRMF_MSG, OSSL_CRMF_MSG, OSSL_CRMF_MSG)
|
||||
#define sk_OSSL_CRMF_MSG_num(sk) OPENSSL_sk_num(ossl_check_const_OSSL_CRMF_MSG_sk_type(sk))
|
||||
#define sk_OSSL_CRMF_MSG_value(sk, idx) ((OSSL_CRMF_MSG *)OPENSSL_sk_value(ossl_check_const_OSSL_CRMF_MSG_sk_type(sk), (idx)))
|
||||
#define sk_OSSL_CRMF_MSG_new(cmp) ((STACK_OF(OSSL_CRMF_MSG) *)OPENSSL_sk_new(ossl_check_OSSL_CRMF_MSG_compfunc_type(cmp)))
|
||||
#define sk_OSSL_CRMF_MSG_new_null() ((STACK_OF(OSSL_CRMF_MSG) *)OPENSSL_sk_new_null())
|
||||
#define sk_OSSL_CRMF_MSG_new_reserve(cmp, n) ((STACK_OF(OSSL_CRMF_MSG) *)OPENSSL_sk_new_reserve(ossl_check_OSSL_CRMF_MSG_compfunc_type(cmp), (n)))
|
||||
#define sk_OSSL_CRMF_MSG_reserve(sk, n) OPENSSL_sk_reserve(ossl_check_OSSL_CRMF_MSG_sk_type(sk), (n))
|
||||
#define sk_OSSL_CRMF_MSG_free(sk) OPENSSL_sk_free(ossl_check_OSSL_CRMF_MSG_sk_type(sk))
|
||||
#define sk_OSSL_CRMF_MSG_zero(sk) OPENSSL_sk_zero(ossl_check_OSSL_CRMF_MSG_sk_type(sk))
|
||||
#define sk_OSSL_CRMF_MSG_delete(sk, i) ((OSSL_CRMF_MSG *)OPENSSL_sk_delete(ossl_check_OSSL_CRMF_MSG_sk_type(sk), (i)))
|
||||
#define sk_OSSL_CRMF_MSG_delete_ptr(sk, ptr) ((OSSL_CRMF_MSG *)OPENSSL_sk_delete_ptr(ossl_check_OSSL_CRMF_MSG_sk_type(sk), ossl_check_OSSL_CRMF_MSG_type(ptr)))
|
||||
#define sk_OSSL_CRMF_MSG_push(sk, ptr) OPENSSL_sk_push(ossl_check_OSSL_CRMF_MSG_sk_type(sk), ossl_check_OSSL_CRMF_MSG_type(ptr))
|
||||
#define sk_OSSL_CRMF_MSG_unshift(sk, ptr) OPENSSL_sk_unshift(ossl_check_OSSL_CRMF_MSG_sk_type(sk), ossl_check_OSSL_CRMF_MSG_type(ptr))
|
||||
#define sk_OSSL_CRMF_MSG_pop(sk) ((OSSL_CRMF_MSG *)OPENSSL_sk_pop(ossl_check_OSSL_CRMF_MSG_sk_type(sk)))
|
||||
#define sk_OSSL_CRMF_MSG_shift(sk) ((OSSL_CRMF_MSG *)OPENSSL_sk_shift(ossl_check_OSSL_CRMF_MSG_sk_type(sk)))
|
||||
#define sk_OSSL_CRMF_MSG_pop_free(sk, freefunc) OPENSSL_sk_pop_free(ossl_check_OSSL_CRMF_MSG_sk_type(sk),ossl_check_OSSL_CRMF_MSG_freefunc_type(freefunc))
|
||||
#define sk_OSSL_CRMF_MSG_insert(sk, ptr, idx) OPENSSL_sk_insert(ossl_check_OSSL_CRMF_MSG_sk_type(sk), ossl_check_OSSL_CRMF_MSG_type(ptr), (idx))
|
||||
#define sk_OSSL_CRMF_MSG_set(sk, idx, ptr) ((OSSL_CRMF_MSG *)OPENSSL_sk_set(ossl_check_OSSL_CRMF_MSG_sk_type(sk), (idx), ossl_check_OSSL_CRMF_MSG_type(ptr)))
|
||||
#define sk_OSSL_CRMF_MSG_find(sk, ptr) OPENSSL_sk_find(ossl_check_OSSL_CRMF_MSG_sk_type(sk), ossl_check_OSSL_CRMF_MSG_type(ptr))
|
||||
#define sk_OSSL_CRMF_MSG_find_ex(sk, ptr) OPENSSL_sk_find_ex(ossl_check_OSSL_CRMF_MSG_sk_type(sk), ossl_check_OSSL_CRMF_MSG_type(ptr))
|
||||
#define sk_OSSL_CRMF_MSG_find_all(sk, ptr, pnum) OPENSSL_sk_find_all(ossl_check_OSSL_CRMF_MSG_sk_type(sk), ossl_check_OSSL_CRMF_MSG_type(ptr), pnum)
|
||||
#define sk_OSSL_CRMF_MSG_sort(sk) OPENSSL_sk_sort(ossl_check_OSSL_CRMF_MSG_sk_type(sk))
|
||||
#define sk_OSSL_CRMF_MSG_is_sorted(sk) OPENSSL_sk_is_sorted(ossl_check_const_OSSL_CRMF_MSG_sk_type(sk))
|
||||
#define sk_OSSL_CRMF_MSG_dup(sk) ((STACK_OF(OSSL_CRMF_MSG) *)OPENSSL_sk_dup(ossl_check_const_OSSL_CRMF_MSG_sk_type(sk)))
|
||||
#define sk_OSSL_CRMF_MSG_deep_copy(sk, copyfunc, freefunc) ((STACK_OF(OSSL_CRMF_MSG) *)OPENSSL_sk_deep_copy(ossl_check_const_OSSL_CRMF_MSG_sk_type(sk), ossl_check_OSSL_CRMF_MSG_copyfunc_type(copyfunc), ossl_check_OSSL_CRMF_MSG_freefunc_type(freefunc)))
|
||||
#define sk_OSSL_CRMF_MSG_set_cmp_func(sk, cmp) ((sk_OSSL_CRMF_MSG_compfunc)OPENSSL_sk_set_cmp_func(ossl_check_OSSL_CRMF_MSG_sk_type(sk), ossl_check_OSSL_CRMF_MSG_compfunc_type(cmp)))
|
||||
|
||||
typedef struct ossl_crmf_attributetypeandvalue_st OSSL_CRMF_ATTRIBUTETYPEANDVALUE;
|
||||
typedef struct ossl_crmf_pbmparameter_st OSSL_CRMF_PBMPARAMETER;
|
||||
DECLARE_ASN1_FUNCTIONS(OSSL_CRMF_PBMPARAMETER)
|
||||
typedef struct ossl_crmf_poposigningkey_st OSSL_CRMF_POPOSIGNINGKEY;
|
||||
typedef struct ossl_crmf_certrequest_st OSSL_CRMF_CERTREQUEST;
|
||||
typedef struct ossl_crmf_certid_st OSSL_CRMF_CERTID;
|
||||
DECLARE_ASN1_FUNCTIONS(OSSL_CRMF_CERTID)
|
||||
DECLARE_ASN1_DUP_FUNCTION(OSSL_CRMF_CERTID)
|
||||
SKM_DEFINE_STACK_OF_INTERNAL(OSSL_CRMF_CERTID, OSSL_CRMF_CERTID, OSSL_CRMF_CERTID)
|
||||
#define sk_OSSL_CRMF_CERTID_num(sk) OPENSSL_sk_num(ossl_check_const_OSSL_CRMF_CERTID_sk_type(sk))
|
||||
#define sk_OSSL_CRMF_CERTID_value(sk, idx) ((OSSL_CRMF_CERTID *)OPENSSL_sk_value(ossl_check_const_OSSL_CRMF_CERTID_sk_type(sk), (idx)))
|
||||
#define sk_OSSL_CRMF_CERTID_new(cmp) ((STACK_OF(OSSL_CRMF_CERTID) *)OPENSSL_sk_new(ossl_check_OSSL_CRMF_CERTID_compfunc_type(cmp)))
|
||||
#define sk_OSSL_CRMF_CERTID_new_null() ((STACK_OF(OSSL_CRMF_CERTID) *)OPENSSL_sk_new_null())
|
||||
#define sk_OSSL_CRMF_CERTID_new_reserve(cmp, n) ((STACK_OF(OSSL_CRMF_CERTID) *)OPENSSL_sk_new_reserve(ossl_check_OSSL_CRMF_CERTID_compfunc_type(cmp), (n)))
|
||||
#define sk_OSSL_CRMF_CERTID_reserve(sk, n) OPENSSL_sk_reserve(ossl_check_OSSL_CRMF_CERTID_sk_type(sk), (n))
|
||||
#define sk_OSSL_CRMF_CERTID_free(sk) OPENSSL_sk_free(ossl_check_OSSL_CRMF_CERTID_sk_type(sk))
|
||||
#define sk_OSSL_CRMF_CERTID_zero(sk) OPENSSL_sk_zero(ossl_check_OSSL_CRMF_CERTID_sk_type(sk))
|
||||
#define sk_OSSL_CRMF_CERTID_delete(sk, i) ((OSSL_CRMF_CERTID *)OPENSSL_sk_delete(ossl_check_OSSL_CRMF_CERTID_sk_type(sk), (i)))
|
||||
#define sk_OSSL_CRMF_CERTID_delete_ptr(sk, ptr) ((OSSL_CRMF_CERTID *)OPENSSL_sk_delete_ptr(ossl_check_OSSL_CRMF_CERTID_sk_type(sk), ossl_check_OSSL_CRMF_CERTID_type(ptr)))
|
||||
#define sk_OSSL_CRMF_CERTID_push(sk, ptr) OPENSSL_sk_push(ossl_check_OSSL_CRMF_CERTID_sk_type(sk), ossl_check_OSSL_CRMF_CERTID_type(ptr))
|
||||
#define sk_OSSL_CRMF_CERTID_unshift(sk, ptr) OPENSSL_sk_unshift(ossl_check_OSSL_CRMF_CERTID_sk_type(sk), ossl_check_OSSL_CRMF_CERTID_type(ptr))
|
||||
#define sk_OSSL_CRMF_CERTID_pop(sk) ((OSSL_CRMF_CERTID *)OPENSSL_sk_pop(ossl_check_OSSL_CRMF_CERTID_sk_type(sk)))
|
||||
#define sk_OSSL_CRMF_CERTID_shift(sk) ((OSSL_CRMF_CERTID *)OPENSSL_sk_shift(ossl_check_OSSL_CRMF_CERTID_sk_type(sk)))
|
||||
#define sk_OSSL_CRMF_CERTID_pop_free(sk, freefunc) OPENSSL_sk_pop_free(ossl_check_OSSL_CRMF_CERTID_sk_type(sk),ossl_check_OSSL_CRMF_CERTID_freefunc_type(freefunc))
|
||||
#define sk_OSSL_CRMF_CERTID_insert(sk, ptr, idx) OPENSSL_sk_insert(ossl_check_OSSL_CRMF_CERTID_sk_type(sk), ossl_check_OSSL_CRMF_CERTID_type(ptr), (idx))
|
||||
#define sk_OSSL_CRMF_CERTID_set(sk, idx, ptr) ((OSSL_CRMF_CERTID *)OPENSSL_sk_set(ossl_check_OSSL_CRMF_CERTID_sk_type(sk), (idx), ossl_check_OSSL_CRMF_CERTID_type(ptr)))
|
||||
#define sk_OSSL_CRMF_CERTID_find(sk, ptr) OPENSSL_sk_find(ossl_check_OSSL_CRMF_CERTID_sk_type(sk), ossl_check_OSSL_CRMF_CERTID_type(ptr))
|
||||
#define sk_OSSL_CRMF_CERTID_find_ex(sk, ptr) OPENSSL_sk_find_ex(ossl_check_OSSL_CRMF_CERTID_sk_type(sk), ossl_check_OSSL_CRMF_CERTID_type(ptr))
|
||||
#define sk_OSSL_CRMF_CERTID_find_all(sk, ptr, pnum) OPENSSL_sk_find_all(ossl_check_OSSL_CRMF_CERTID_sk_type(sk), ossl_check_OSSL_CRMF_CERTID_type(ptr), pnum)
|
||||
#define sk_OSSL_CRMF_CERTID_sort(sk) OPENSSL_sk_sort(ossl_check_OSSL_CRMF_CERTID_sk_type(sk))
|
||||
#define sk_OSSL_CRMF_CERTID_is_sorted(sk) OPENSSL_sk_is_sorted(ossl_check_const_OSSL_CRMF_CERTID_sk_type(sk))
|
||||
#define sk_OSSL_CRMF_CERTID_dup(sk) ((STACK_OF(OSSL_CRMF_CERTID) *)OPENSSL_sk_dup(ossl_check_const_OSSL_CRMF_CERTID_sk_type(sk)))
|
||||
#define sk_OSSL_CRMF_CERTID_deep_copy(sk, copyfunc, freefunc) ((STACK_OF(OSSL_CRMF_CERTID) *)OPENSSL_sk_deep_copy(ossl_check_const_OSSL_CRMF_CERTID_sk_type(sk), ossl_check_OSSL_CRMF_CERTID_copyfunc_type(copyfunc), ossl_check_OSSL_CRMF_CERTID_freefunc_type(freefunc)))
|
||||
#define sk_OSSL_CRMF_CERTID_set_cmp_func(sk, cmp) ((sk_OSSL_CRMF_CERTID_compfunc)OPENSSL_sk_set_cmp_func(ossl_check_OSSL_CRMF_CERTID_sk_type(sk), ossl_check_OSSL_CRMF_CERTID_compfunc_type(cmp)))
|
||||
|
||||
|
||||
typedef struct ossl_crmf_pkipublicationinfo_st OSSL_CRMF_PKIPUBLICATIONINFO;
|
||||
DECLARE_ASN1_FUNCTIONS(OSSL_CRMF_PKIPUBLICATIONINFO)
|
||||
typedef struct ossl_crmf_singlepubinfo_st OSSL_CRMF_SINGLEPUBINFO;
|
||||
DECLARE_ASN1_FUNCTIONS(OSSL_CRMF_SINGLEPUBINFO)
|
||||
typedef struct ossl_crmf_certtemplate_st OSSL_CRMF_CERTTEMPLATE;
|
||||
DECLARE_ASN1_FUNCTIONS(OSSL_CRMF_CERTTEMPLATE)
|
||||
typedef STACK_OF(OSSL_CRMF_MSG) OSSL_CRMF_MSGS;
|
||||
DECLARE_ASN1_FUNCTIONS(OSSL_CRMF_MSGS)
|
||||
|
||||
typedef struct ossl_crmf_optionalvalidity_st OSSL_CRMF_OPTIONALVALIDITY;
|
||||
|
||||
/* crmf_pbm.c */
|
||||
OSSL_CRMF_PBMPARAMETER *OSSL_CRMF_pbmp_new(OSSL_LIB_CTX *libctx, size_t slen,
|
||||
int owfnid, size_t itercnt,
|
||||
int macnid);
|
||||
int OSSL_CRMF_pbm_new(OSSL_LIB_CTX *libctx, const char *propq,
|
||||
const OSSL_CRMF_PBMPARAMETER *pbmp,
|
||||
const unsigned char *msg, size_t msglen,
|
||||
const unsigned char *sec, size_t seclen,
|
||||
unsigned char **mac, size_t *maclen);
|
||||
|
||||
/* crmf_lib.c */
|
||||
int OSSL_CRMF_MSG_set1_regCtrl_regToken(OSSL_CRMF_MSG *msg,
|
||||
const ASN1_UTF8STRING *tok);
|
||||
ASN1_UTF8STRING
|
||||
*OSSL_CRMF_MSG_get0_regCtrl_regToken(const OSSL_CRMF_MSG *msg);
|
||||
int OSSL_CRMF_MSG_set1_regCtrl_authenticator(OSSL_CRMF_MSG *msg,
|
||||
const ASN1_UTF8STRING *auth);
|
||||
ASN1_UTF8STRING
|
||||
*OSSL_CRMF_MSG_get0_regCtrl_authenticator(const OSSL_CRMF_MSG *msg);
|
||||
int
|
||||
OSSL_CRMF_MSG_PKIPublicationInfo_push0_SinglePubInfo(OSSL_CRMF_PKIPUBLICATIONINFO *pi,
|
||||
OSSL_CRMF_SINGLEPUBINFO *spi);
|
||||
# define OSSL_CRMF_PUB_METHOD_DONTCARE 0
|
||||
# define OSSL_CRMF_PUB_METHOD_X500 1
|
||||
# define OSSL_CRMF_PUB_METHOD_WEB 2
|
||||
# define OSSL_CRMF_PUB_METHOD_LDAP 3
|
||||
int OSSL_CRMF_MSG_set0_SinglePubInfo(OSSL_CRMF_SINGLEPUBINFO *spi,
|
||||
int method, GENERAL_NAME *nm);
|
||||
# define OSSL_CRMF_PUB_ACTION_DONTPUBLISH 0
|
||||
# define OSSL_CRMF_PUB_ACTION_PLEASEPUBLISH 1
|
||||
int OSSL_CRMF_MSG_set_PKIPublicationInfo_action(OSSL_CRMF_PKIPUBLICATIONINFO *pi,
|
||||
int action);
|
||||
int OSSL_CRMF_MSG_set1_regCtrl_pkiPublicationInfo(OSSL_CRMF_MSG *msg,
|
||||
const OSSL_CRMF_PKIPUBLICATIONINFO *pi);
|
||||
OSSL_CRMF_PKIPUBLICATIONINFO
|
||||
*OSSL_CRMF_MSG_get0_regCtrl_pkiPublicationInfo(const OSSL_CRMF_MSG *msg);
|
||||
int OSSL_CRMF_MSG_set1_regCtrl_protocolEncrKey(OSSL_CRMF_MSG *msg,
|
||||
const X509_PUBKEY *pubkey);
|
||||
X509_PUBKEY
|
||||
*OSSL_CRMF_MSG_get0_regCtrl_protocolEncrKey(const OSSL_CRMF_MSG *msg);
|
||||
int OSSL_CRMF_MSG_set1_regCtrl_oldCertID(OSSL_CRMF_MSG *msg,
|
||||
const OSSL_CRMF_CERTID *cid);
|
||||
OSSL_CRMF_CERTID
|
||||
*OSSL_CRMF_MSG_get0_regCtrl_oldCertID(const OSSL_CRMF_MSG *msg);
|
||||
OSSL_CRMF_CERTID *OSSL_CRMF_CERTID_gen(const X509_NAME *issuer,
|
||||
const ASN1_INTEGER *serial);
|
||||
|
||||
int OSSL_CRMF_MSG_set1_regInfo_utf8Pairs(OSSL_CRMF_MSG *msg,
|
||||
const ASN1_UTF8STRING *utf8pairs);
|
||||
ASN1_UTF8STRING
|
||||
*OSSL_CRMF_MSG_get0_regInfo_utf8Pairs(const OSSL_CRMF_MSG *msg);
|
||||
int OSSL_CRMF_MSG_set1_regInfo_certReq(OSSL_CRMF_MSG *msg,
|
||||
const OSSL_CRMF_CERTREQUEST *cr);
|
||||
OSSL_CRMF_CERTREQUEST
|
||||
*OSSL_CRMF_MSG_get0_regInfo_certReq(const OSSL_CRMF_MSG *msg);
|
||||
|
||||
int OSSL_CRMF_MSG_set0_validity(OSSL_CRMF_MSG *crm,
|
||||
ASN1_TIME *notBefore, ASN1_TIME *notAfter);
|
||||
int OSSL_CRMF_MSG_set_certReqId(OSSL_CRMF_MSG *crm, int rid);
|
||||
int OSSL_CRMF_MSG_get_certReqId(const OSSL_CRMF_MSG *crm);
|
||||
int OSSL_CRMF_MSG_set0_extensions(OSSL_CRMF_MSG *crm, X509_EXTENSIONS *exts);
|
||||
|
||||
int OSSL_CRMF_MSG_push0_extension(OSSL_CRMF_MSG *crm, X509_EXTENSION *ext);
|
||||
# define OSSL_CRMF_POPO_NONE -1
|
||||
# define OSSL_CRMF_POPO_RAVERIFIED 0
|
||||
# define OSSL_CRMF_POPO_SIGNATURE 1
|
||||
# define OSSL_CRMF_POPO_KEYENC 2
|
||||
# define OSSL_CRMF_POPO_KEYAGREE 3
|
||||
int OSSL_CRMF_MSG_create_popo(int meth, OSSL_CRMF_MSG *crm,
|
||||
EVP_PKEY *pkey, const EVP_MD *digest,
|
||||
OSSL_LIB_CTX *libctx, const char *propq);
|
||||
int OSSL_CRMF_MSGS_verify_popo(const OSSL_CRMF_MSGS *reqs,
|
||||
int rid, int acceptRAVerified,
|
||||
OSSL_LIB_CTX *libctx, const char *propq);
|
||||
OSSL_CRMF_CERTTEMPLATE *OSSL_CRMF_MSG_get0_tmpl(const OSSL_CRMF_MSG *crm);
|
||||
const ASN1_INTEGER
|
||||
*OSSL_CRMF_CERTTEMPLATE_get0_serialNumber(const OSSL_CRMF_CERTTEMPLATE *tmpl);
|
||||
const X509_NAME
|
||||
*OSSL_CRMF_CERTTEMPLATE_get0_subject(const OSSL_CRMF_CERTTEMPLATE *tmpl);
|
||||
const X509_NAME
|
||||
*OSSL_CRMF_CERTTEMPLATE_get0_issuer(const OSSL_CRMF_CERTTEMPLATE *tmpl);
|
||||
X509_EXTENSIONS
|
||||
*OSSL_CRMF_CERTTEMPLATE_get0_extensions(const OSSL_CRMF_CERTTEMPLATE *tmpl);
|
||||
const X509_NAME
|
||||
*OSSL_CRMF_CERTID_get0_issuer(const OSSL_CRMF_CERTID *cid);
|
||||
const ASN1_INTEGER
|
||||
*OSSL_CRMF_CERTID_get0_serialNumber(const OSSL_CRMF_CERTID *cid);
|
||||
int OSSL_CRMF_CERTTEMPLATE_fill(OSSL_CRMF_CERTTEMPLATE *tmpl,
|
||||
EVP_PKEY *pubkey,
|
||||
const X509_NAME *subject,
|
||||
const X509_NAME *issuer,
|
||||
const ASN1_INTEGER *serial);
|
||||
X509
|
||||
*OSSL_CRMF_ENCRYPTEDVALUE_get1_encCert(const OSSL_CRMF_ENCRYPTEDVALUE *ecert,
|
||||
OSSL_LIB_CTX *libctx, const char *propq,
|
||||
EVP_PKEY *pkey);
|
||||
|
||||
# ifdef __cplusplus
|
||||
}
|
||||
# endif
|
||||
# endif /* !defined(OPENSSL_NO_CRMF) */
|
||||
#endif /* !defined(OPENSSL_CRMF_H) */
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 1995-2020 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 1995-2021 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -279,7 +279,8 @@ typedef unsigned __int64 uint64_t;
|
||||
# define ossl_inline inline
|
||||
# endif
|
||||
|
||||
# if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
|
||||
# if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && \
|
||||
!defined(__cplusplus)
|
||||
# define ossl_noreturn _Noreturn
|
||||
# elif defined(__GNUC__) && __GNUC__ >= 2
|
||||
# define ossl_noreturn __attribute__((noreturn))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 2002-2021 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
@@ -793,12 +793,15 @@ int EC_GROUP_get_pentanomial_basis(const EC_GROUP *, unsigned int *k1,
|
||||
EC_GROUP *d2i_ECPKParameters(EC_GROUP **, const unsigned char **in, long len);
|
||||
int i2d_ECPKParameters(const EC_GROUP *, unsigned char **out);
|
||||
|
||||
# define d2i_ECPKParameters_bio(bp,x) ASN1_d2i_bio_of(EC_GROUP,NULL,d2i_ECPKParameters,bp,x)
|
||||
# define i2d_ECPKParameters_bio(bp,x) ASN1_i2d_bio_of_const(EC_GROUP,i2d_ECPKParameters,bp,x)
|
||||
# define d2i_ECPKParameters_fp(fp,x) (EC_GROUP *)ASN1_d2i_fp(NULL, \
|
||||
(char *(*)())d2i_ECPKParameters,(fp),(unsigned char **)(x))
|
||||
# define i2d_ECPKParameters_fp(fp,x) ASN1_i2d_fp(i2d_ECPKParameters,(fp), \
|
||||
(unsigned char *)(x))
|
||||
# define d2i_ECPKParameters_bio(bp,x) \
|
||||
ASN1_d2i_bio_of(EC_GROUP, NULL, d2i_ECPKParameters, bp, x)
|
||||
# define i2d_ECPKParameters_bio(bp,x) \
|
||||
ASN1_i2d_bio_of_const(EC_GROUP, i2d_ECPKParameters, bp, x)
|
||||
# define d2i_ECPKParameters_fp(fp,x) \
|
||||
(EC_GROUP *)ASN1_d2i_fp(NULL, (d2i_of_void *)d2i_ECPKParameters, (fp), \
|
||||
(void **)(x))
|
||||
# define i2d_ECPKParameters_fp(fp,x) \
|
||||
ASN1_i2d_fp((i2d_of_void *)i2d_ECPKParameters, (fp), (void *)(x))
|
||||
|
||||
int ECPKParameters_print(BIO *bp, const EC_GROUP *x, int off);
|
||||
# ifndef OPENSSL_NO_STDIO
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2018 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 2000-2022 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
@@ -722,6 +722,7 @@ typedef int (*dynamic_bind_engine) (ENGINE *e, const char *id,
|
||||
CRYPTO_set_mem_functions(fns->mem_fns.malloc_fn, \
|
||||
fns->mem_fns.realloc_fn, \
|
||||
fns->mem_fns.free_fn); \
|
||||
OPENSSL_init_crypto(OPENSSL_INIT_NO_ATEXIT, NULL); \
|
||||
skip_cbs: \
|
||||
if (!fn(e, id)) return 0; \
|
||||
return 1; }
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* WARNING: do not edit!
|
||||
* Generated by Makefile from include/openssl/ess.h.in
|
||||
*
|
||||
* Copyright 2019-2021 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution or at
|
||||
* https://www.openssl.org/source/license.html
|
||||
*/
|
||||
|
||||
|
||||
|
||||
#ifndef OPENSSL_ESS_H
|
||||
# define OPENSSL_ESS_H
|
||||
# pragma once
|
||||
|
||||
# include <openssl/opensslconf.h>
|
||||
|
||||
# include <openssl/safestack.h>
|
||||
# include <openssl/x509.h>
|
||||
# include <openssl/esserr.h>
|
||||
|
||||
# ifdef __cplusplus
|
||||
extern "C" {
|
||||
# endif
|
||||
|
||||
|
||||
typedef struct ESS_issuer_serial ESS_ISSUER_SERIAL;
|
||||
typedef struct ESS_cert_id ESS_CERT_ID;
|
||||
typedef struct ESS_signing_cert ESS_SIGNING_CERT;
|
||||
|
||||
SKM_DEFINE_STACK_OF_INTERNAL(ESS_CERT_ID, ESS_CERT_ID, ESS_CERT_ID)
|
||||
#define sk_ESS_CERT_ID_num(sk) OPENSSL_sk_num(ossl_check_const_ESS_CERT_ID_sk_type(sk))
|
||||
#define sk_ESS_CERT_ID_value(sk, idx) ((ESS_CERT_ID *)OPENSSL_sk_value(ossl_check_const_ESS_CERT_ID_sk_type(sk), (idx)))
|
||||
#define sk_ESS_CERT_ID_new(cmp) ((STACK_OF(ESS_CERT_ID) *)OPENSSL_sk_new(ossl_check_ESS_CERT_ID_compfunc_type(cmp)))
|
||||
#define sk_ESS_CERT_ID_new_null() ((STACK_OF(ESS_CERT_ID) *)OPENSSL_sk_new_null())
|
||||
#define sk_ESS_CERT_ID_new_reserve(cmp, n) ((STACK_OF(ESS_CERT_ID) *)OPENSSL_sk_new_reserve(ossl_check_ESS_CERT_ID_compfunc_type(cmp), (n)))
|
||||
#define sk_ESS_CERT_ID_reserve(sk, n) OPENSSL_sk_reserve(ossl_check_ESS_CERT_ID_sk_type(sk), (n))
|
||||
#define sk_ESS_CERT_ID_free(sk) OPENSSL_sk_free(ossl_check_ESS_CERT_ID_sk_type(sk))
|
||||
#define sk_ESS_CERT_ID_zero(sk) OPENSSL_sk_zero(ossl_check_ESS_CERT_ID_sk_type(sk))
|
||||
#define sk_ESS_CERT_ID_delete(sk, i) ((ESS_CERT_ID *)OPENSSL_sk_delete(ossl_check_ESS_CERT_ID_sk_type(sk), (i)))
|
||||
#define sk_ESS_CERT_ID_delete_ptr(sk, ptr) ((ESS_CERT_ID *)OPENSSL_sk_delete_ptr(ossl_check_ESS_CERT_ID_sk_type(sk), ossl_check_ESS_CERT_ID_type(ptr)))
|
||||
#define sk_ESS_CERT_ID_push(sk, ptr) OPENSSL_sk_push(ossl_check_ESS_CERT_ID_sk_type(sk), ossl_check_ESS_CERT_ID_type(ptr))
|
||||
#define sk_ESS_CERT_ID_unshift(sk, ptr) OPENSSL_sk_unshift(ossl_check_ESS_CERT_ID_sk_type(sk), ossl_check_ESS_CERT_ID_type(ptr))
|
||||
#define sk_ESS_CERT_ID_pop(sk) ((ESS_CERT_ID *)OPENSSL_sk_pop(ossl_check_ESS_CERT_ID_sk_type(sk)))
|
||||
#define sk_ESS_CERT_ID_shift(sk) ((ESS_CERT_ID *)OPENSSL_sk_shift(ossl_check_ESS_CERT_ID_sk_type(sk)))
|
||||
#define sk_ESS_CERT_ID_pop_free(sk, freefunc) OPENSSL_sk_pop_free(ossl_check_ESS_CERT_ID_sk_type(sk),ossl_check_ESS_CERT_ID_freefunc_type(freefunc))
|
||||
#define sk_ESS_CERT_ID_insert(sk, ptr, idx) OPENSSL_sk_insert(ossl_check_ESS_CERT_ID_sk_type(sk), ossl_check_ESS_CERT_ID_type(ptr), (idx))
|
||||
#define sk_ESS_CERT_ID_set(sk, idx, ptr) ((ESS_CERT_ID *)OPENSSL_sk_set(ossl_check_ESS_CERT_ID_sk_type(sk), (idx), ossl_check_ESS_CERT_ID_type(ptr)))
|
||||
#define sk_ESS_CERT_ID_find(sk, ptr) OPENSSL_sk_find(ossl_check_ESS_CERT_ID_sk_type(sk), ossl_check_ESS_CERT_ID_type(ptr))
|
||||
#define sk_ESS_CERT_ID_find_ex(sk, ptr) OPENSSL_sk_find_ex(ossl_check_ESS_CERT_ID_sk_type(sk), ossl_check_ESS_CERT_ID_type(ptr))
|
||||
#define sk_ESS_CERT_ID_find_all(sk, ptr, pnum) OPENSSL_sk_find_all(ossl_check_ESS_CERT_ID_sk_type(sk), ossl_check_ESS_CERT_ID_type(ptr), pnum)
|
||||
#define sk_ESS_CERT_ID_sort(sk) OPENSSL_sk_sort(ossl_check_ESS_CERT_ID_sk_type(sk))
|
||||
#define sk_ESS_CERT_ID_is_sorted(sk) OPENSSL_sk_is_sorted(ossl_check_const_ESS_CERT_ID_sk_type(sk))
|
||||
#define sk_ESS_CERT_ID_dup(sk) ((STACK_OF(ESS_CERT_ID) *)OPENSSL_sk_dup(ossl_check_const_ESS_CERT_ID_sk_type(sk)))
|
||||
#define sk_ESS_CERT_ID_deep_copy(sk, copyfunc, freefunc) ((STACK_OF(ESS_CERT_ID) *)OPENSSL_sk_deep_copy(ossl_check_const_ESS_CERT_ID_sk_type(sk), ossl_check_ESS_CERT_ID_copyfunc_type(copyfunc), ossl_check_ESS_CERT_ID_freefunc_type(freefunc)))
|
||||
#define sk_ESS_CERT_ID_set_cmp_func(sk, cmp) ((sk_ESS_CERT_ID_compfunc)OPENSSL_sk_set_cmp_func(ossl_check_ESS_CERT_ID_sk_type(sk), ossl_check_ESS_CERT_ID_compfunc_type(cmp)))
|
||||
|
||||
|
||||
|
||||
typedef struct ESS_signing_cert_v2_st ESS_SIGNING_CERT_V2;
|
||||
typedef struct ESS_cert_id_v2_st ESS_CERT_ID_V2;
|
||||
|
||||
SKM_DEFINE_STACK_OF_INTERNAL(ESS_CERT_ID_V2, ESS_CERT_ID_V2, ESS_CERT_ID_V2)
|
||||
#define sk_ESS_CERT_ID_V2_num(sk) OPENSSL_sk_num(ossl_check_const_ESS_CERT_ID_V2_sk_type(sk))
|
||||
#define sk_ESS_CERT_ID_V2_value(sk, idx) ((ESS_CERT_ID_V2 *)OPENSSL_sk_value(ossl_check_const_ESS_CERT_ID_V2_sk_type(sk), (idx)))
|
||||
#define sk_ESS_CERT_ID_V2_new(cmp) ((STACK_OF(ESS_CERT_ID_V2) *)OPENSSL_sk_new(ossl_check_ESS_CERT_ID_V2_compfunc_type(cmp)))
|
||||
#define sk_ESS_CERT_ID_V2_new_null() ((STACK_OF(ESS_CERT_ID_V2) *)OPENSSL_sk_new_null())
|
||||
#define sk_ESS_CERT_ID_V2_new_reserve(cmp, n) ((STACK_OF(ESS_CERT_ID_V2) *)OPENSSL_sk_new_reserve(ossl_check_ESS_CERT_ID_V2_compfunc_type(cmp), (n)))
|
||||
#define sk_ESS_CERT_ID_V2_reserve(sk, n) OPENSSL_sk_reserve(ossl_check_ESS_CERT_ID_V2_sk_type(sk), (n))
|
||||
#define sk_ESS_CERT_ID_V2_free(sk) OPENSSL_sk_free(ossl_check_ESS_CERT_ID_V2_sk_type(sk))
|
||||
#define sk_ESS_CERT_ID_V2_zero(sk) OPENSSL_sk_zero(ossl_check_ESS_CERT_ID_V2_sk_type(sk))
|
||||
#define sk_ESS_CERT_ID_V2_delete(sk, i) ((ESS_CERT_ID_V2 *)OPENSSL_sk_delete(ossl_check_ESS_CERT_ID_V2_sk_type(sk), (i)))
|
||||
#define sk_ESS_CERT_ID_V2_delete_ptr(sk, ptr) ((ESS_CERT_ID_V2 *)OPENSSL_sk_delete_ptr(ossl_check_ESS_CERT_ID_V2_sk_type(sk), ossl_check_ESS_CERT_ID_V2_type(ptr)))
|
||||
#define sk_ESS_CERT_ID_V2_push(sk, ptr) OPENSSL_sk_push(ossl_check_ESS_CERT_ID_V2_sk_type(sk), ossl_check_ESS_CERT_ID_V2_type(ptr))
|
||||
#define sk_ESS_CERT_ID_V2_unshift(sk, ptr) OPENSSL_sk_unshift(ossl_check_ESS_CERT_ID_V2_sk_type(sk), ossl_check_ESS_CERT_ID_V2_type(ptr))
|
||||
#define sk_ESS_CERT_ID_V2_pop(sk) ((ESS_CERT_ID_V2 *)OPENSSL_sk_pop(ossl_check_ESS_CERT_ID_V2_sk_type(sk)))
|
||||
#define sk_ESS_CERT_ID_V2_shift(sk) ((ESS_CERT_ID_V2 *)OPENSSL_sk_shift(ossl_check_ESS_CERT_ID_V2_sk_type(sk)))
|
||||
#define sk_ESS_CERT_ID_V2_pop_free(sk, freefunc) OPENSSL_sk_pop_free(ossl_check_ESS_CERT_ID_V2_sk_type(sk),ossl_check_ESS_CERT_ID_V2_freefunc_type(freefunc))
|
||||
#define sk_ESS_CERT_ID_V2_insert(sk, ptr, idx) OPENSSL_sk_insert(ossl_check_ESS_CERT_ID_V2_sk_type(sk), ossl_check_ESS_CERT_ID_V2_type(ptr), (idx))
|
||||
#define sk_ESS_CERT_ID_V2_set(sk, idx, ptr) ((ESS_CERT_ID_V2 *)OPENSSL_sk_set(ossl_check_ESS_CERT_ID_V2_sk_type(sk), (idx), ossl_check_ESS_CERT_ID_V2_type(ptr)))
|
||||
#define sk_ESS_CERT_ID_V2_find(sk, ptr) OPENSSL_sk_find(ossl_check_ESS_CERT_ID_V2_sk_type(sk), ossl_check_ESS_CERT_ID_V2_type(ptr))
|
||||
#define sk_ESS_CERT_ID_V2_find_ex(sk, ptr) OPENSSL_sk_find_ex(ossl_check_ESS_CERT_ID_V2_sk_type(sk), ossl_check_ESS_CERT_ID_V2_type(ptr))
|
||||
#define sk_ESS_CERT_ID_V2_find_all(sk, ptr, pnum) OPENSSL_sk_find_all(ossl_check_ESS_CERT_ID_V2_sk_type(sk), ossl_check_ESS_CERT_ID_V2_type(ptr), pnum)
|
||||
#define sk_ESS_CERT_ID_V2_sort(sk) OPENSSL_sk_sort(ossl_check_ESS_CERT_ID_V2_sk_type(sk))
|
||||
#define sk_ESS_CERT_ID_V2_is_sorted(sk) OPENSSL_sk_is_sorted(ossl_check_const_ESS_CERT_ID_V2_sk_type(sk))
|
||||
#define sk_ESS_CERT_ID_V2_dup(sk) ((STACK_OF(ESS_CERT_ID_V2) *)OPENSSL_sk_dup(ossl_check_const_ESS_CERT_ID_V2_sk_type(sk)))
|
||||
#define sk_ESS_CERT_ID_V2_deep_copy(sk, copyfunc, freefunc) ((STACK_OF(ESS_CERT_ID_V2) *)OPENSSL_sk_deep_copy(ossl_check_const_ESS_CERT_ID_V2_sk_type(sk), ossl_check_ESS_CERT_ID_V2_copyfunc_type(copyfunc), ossl_check_ESS_CERT_ID_V2_freefunc_type(freefunc)))
|
||||
#define sk_ESS_CERT_ID_V2_set_cmp_func(sk, cmp) ((sk_ESS_CERT_ID_V2_compfunc)OPENSSL_sk_set_cmp_func(ossl_check_ESS_CERT_ID_V2_sk_type(sk), ossl_check_ESS_CERT_ID_V2_compfunc_type(cmp)))
|
||||
|
||||
|
||||
DECLARE_ASN1_ALLOC_FUNCTIONS(ESS_ISSUER_SERIAL)
|
||||
DECLARE_ASN1_ENCODE_FUNCTIONS_only(ESS_ISSUER_SERIAL, ESS_ISSUER_SERIAL)
|
||||
DECLARE_ASN1_DUP_FUNCTION(ESS_ISSUER_SERIAL)
|
||||
|
||||
DECLARE_ASN1_ALLOC_FUNCTIONS(ESS_CERT_ID)
|
||||
DECLARE_ASN1_ENCODE_FUNCTIONS_only(ESS_CERT_ID, ESS_CERT_ID)
|
||||
DECLARE_ASN1_DUP_FUNCTION(ESS_CERT_ID)
|
||||
|
||||
DECLARE_ASN1_FUNCTIONS(ESS_SIGNING_CERT)
|
||||
DECLARE_ASN1_DUP_FUNCTION(ESS_SIGNING_CERT)
|
||||
|
||||
DECLARE_ASN1_ALLOC_FUNCTIONS(ESS_CERT_ID_V2)
|
||||
DECLARE_ASN1_ENCODE_FUNCTIONS_only(ESS_CERT_ID_V2, ESS_CERT_ID_V2)
|
||||
DECLARE_ASN1_DUP_FUNCTION(ESS_CERT_ID_V2)
|
||||
|
||||
DECLARE_ASN1_FUNCTIONS(ESS_SIGNING_CERT_V2)
|
||||
DECLARE_ASN1_DUP_FUNCTION(ESS_SIGNING_CERT_V2)
|
||||
|
||||
ESS_SIGNING_CERT *OSSL_ESS_signing_cert_new_init(const X509 *signcert,
|
||||
const STACK_OF(X509) *certs,
|
||||
int set_issuer_serial);
|
||||
ESS_SIGNING_CERT_V2 *OSSL_ESS_signing_cert_v2_new_init(const EVP_MD *hash_alg,
|
||||
const X509 *signcert,
|
||||
const
|
||||
STACK_OF(X509) *certs,
|
||||
int set_issuer_serial);
|
||||
int OSSL_ESS_check_signing_certs(const ESS_SIGNING_CERT *ss,
|
||||
const ESS_SIGNING_CERT_V2 *ssv2,
|
||||
const STACK_OF(X509) *chain,
|
||||
int require_signing_cert);
|
||||
|
||||
# ifdef __cplusplus
|
||||
}
|
||||
# endif
|
||||
#endif
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* WARNING: do not edit!
|
||||
* Generated by Makefile from include/openssl/fipskey.h.in
|
||||
*
|
||||
* Copyright 2020-2021 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution or at
|
||||
* https://www.openssl.org/source/license.html
|
||||
*/
|
||||
|
||||
#ifndef OPENSSL_FIPSKEY_H
|
||||
# define OPENSSL_FIPSKEY_H
|
||||
# pragma once
|
||||
|
||||
# ifdef __cplusplus
|
||||
extern "C" {
|
||||
# endif
|
||||
|
||||
/*
|
||||
* The FIPS validation HMAC key, usable as an array initializer.
|
||||
*/
|
||||
#define FIPS_KEY_ELEMENTS \
|
||||
0xf4, 0x55, 0x66, 0x50, 0xac, 0x31, 0xd3, 0x54, 0x61, 0x61, 0x0b, 0xac, 0x4e, 0xd8, 0x1b, 0x1a, 0x18, 0x1b, 0x2d, 0x8a, 0x43, 0xea, 0x28, 0x54, 0xcb, 0xae, 0x22, 0xca, 0x74, 0x56, 0x08, 0x13
|
||||
|
||||
/*
|
||||
* The FIPS validation key, as a string.
|
||||
*/
|
||||
#define FIPS_KEY_STRING "f4556650ac31d35461610bac4ed81b1a181b2d8a43ea2854cbae22ca74560813"
|
||||
|
||||
# ifdef __cplusplus
|
||||
}
|
||||
# endif
|
||||
|
||||
#endif
|
||||
@@ -2,7 +2,7 @@
|
||||
* WARNING: do not edit!
|
||||
* Generated by crypto/objects/objects.pl
|
||||
*
|
||||
* Copyright 2000-2021 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 2000-2022 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution or at
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 1999-2021 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 1999-2022 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -39,8 +39,8 @@ extern "C" {
|
||||
* (Prior to 0.9.5a beta1, a different scheme was used: MMNNFFRBB for
|
||||
* major minor fix final patch/beta)
|
||||
*/
|
||||
# define OPENSSL_VERSION_NUMBER 0x101010bfL
|
||||
# define OPENSSL_VERSION_TEXT "OpenSSL 1.1.1k 25 Mar 2021"
|
||||
# define OPENSSL_VERSION_NUMBER 0x1010111fL
|
||||
# define OPENSSL_VERSION_TEXT "OpenSSL 1.1.1q 5 Jul 2022"
|
||||
|
||||
/*-
|
||||
* The macros below are to be used for shared library (.so, .dll, ...)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 1995-2020 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 1995-2022 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved
|
||||
* Copyright 2005 Nokia. All rights reserved.
|
||||
*
|
||||
@@ -1305,6 +1305,8 @@ DECLARE_PEM_rw(SSL_SESSION, SSL_SESSION)
|
||||
# define SSL_CTRL_GET_MAX_PROTO_VERSION 131
|
||||
# define SSL_CTRL_GET_SIGNATURE_NID 132
|
||||
# define SSL_CTRL_GET_TMP_KEY 133
|
||||
# define SSL_CTRL_GET_VERIFY_CERT_STORE 137
|
||||
# define SSL_CTRL_GET_CHAIN_CERT_STORE 138
|
||||
# define SSL_CERT_SET_FIRST 1
|
||||
# define SSL_CERT_SET_NEXT 2
|
||||
# define SSL_CERT_SET_SERVER 3
|
||||
@@ -1360,10 +1362,14 @@ DECLARE_PEM_rw(SSL_SESSION, SSL_SESSION)
|
||||
SSL_CTX_ctrl(ctx,SSL_CTRL_SET_VERIFY_CERT_STORE,0,(char *)(st))
|
||||
# define SSL_CTX_set1_verify_cert_store(ctx,st) \
|
||||
SSL_CTX_ctrl(ctx,SSL_CTRL_SET_VERIFY_CERT_STORE,1,(char *)(st))
|
||||
# define SSL_CTX_get0_verify_cert_store(ctx,st) \
|
||||
SSL_CTX_ctrl(ctx,SSL_CTRL_GET_VERIFY_CERT_STORE,0,(char *)(st))
|
||||
# define SSL_CTX_set0_chain_cert_store(ctx,st) \
|
||||
SSL_CTX_ctrl(ctx,SSL_CTRL_SET_CHAIN_CERT_STORE,0,(char *)(st))
|
||||
# define SSL_CTX_set1_chain_cert_store(ctx,st) \
|
||||
SSL_CTX_ctrl(ctx,SSL_CTRL_SET_CHAIN_CERT_STORE,1,(char *)(st))
|
||||
# define SSL_CTX_get0_chain_cert_store(ctx,st) \
|
||||
SSL_CTX_ctrl(ctx,SSL_CTRL_GET_CHAIN_CERT_STORE,0,(char *)(st))
|
||||
# define SSL_set0_chain(s,sk) \
|
||||
SSL_ctrl(s,SSL_CTRL_CHAIN,0,(char *)(sk))
|
||||
# define SSL_set1_chain(s,sk) \
|
||||
@@ -1386,10 +1392,14 @@ DECLARE_PEM_rw(SSL_SESSION, SSL_SESSION)
|
||||
SSL_ctrl(s,SSL_CTRL_SET_VERIFY_CERT_STORE,0,(char *)(st))
|
||||
# define SSL_set1_verify_cert_store(s,st) \
|
||||
SSL_ctrl(s,SSL_CTRL_SET_VERIFY_CERT_STORE,1,(char *)(st))
|
||||
#define SSL_get0_verify_cert_store(s,st) \
|
||||
SSL_ctrl(s,SSL_CTRL_GET_VERIFY_CERT_STORE,0,(char *)(st))
|
||||
# define SSL_set0_chain_cert_store(s,st) \
|
||||
SSL_ctrl(s,SSL_CTRL_SET_CHAIN_CERT_STORE,0,(char *)(st))
|
||||
# define SSL_set1_chain_cert_store(s,st) \
|
||||
SSL_ctrl(s,SSL_CTRL_SET_CHAIN_CERT_STORE,1,(char *)(st))
|
||||
#define SSL_get0_chain_cert_store(s,st) \
|
||||
SSL_ctrl(s,SSL_CTRL_GET_CHAIN_CERT_STORE,0,(char *)(st))
|
||||
# define SSL_get1_groups(s, glist) \
|
||||
SSL_ctrl(s,SSL_CTRL_GET_GROUPS,0,(int*)(glist))
|
||||
# define SSL_CTX_set1_groups(ctx, glist, glistlen) \
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Generated by util/mkerr.pl DO NOT EDIT
|
||||
* Copyright 1995-2020 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 1995-2021 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -70,6 +70,7 @@ int ERR_load_SSL_strings(void);
|
||||
# define SSL_F_FINAL_EMS 486
|
||||
# define SSL_F_FINAL_KEY_SHARE 503
|
||||
# define SSL_F_FINAL_MAXFRAGMENTLEN 557
|
||||
# define SSL_F_FINAL_PSK 639
|
||||
# define SSL_F_FINAL_RENEGOTIATE 483
|
||||
# define SSL_F_FINAL_SERVER_NAME 558
|
||||
# define SSL_F_FINAL_SIG_ALGS 497
|
||||
@@ -592,6 +593,7 @@ int ERR_load_SSL_strings(void);
|
||||
# define SSL_R_MISSING_ECDSA_SIGNING_CERT 381
|
||||
# define SSL_R_MISSING_FATAL 256
|
||||
# define SSL_R_MISSING_PARAMETERS 290
|
||||
# define SSL_R_MISSING_PSK_KEX_MODES_EXTENSION 310
|
||||
# define SSL_R_MISSING_RSA_CERTIFICATE 168
|
||||
# define SSL_R_MISSING_RSA_ENCRYPTING_CERT 169
|
||||
# define SSL_R_MISSING_RSA_SIGNING_CERT 170
|
||||
@@ -633,6 +635,7 @@ int ERR_load_SSL_strings(void);
|
||||
# define SSL_R_NO_VERIFY_COOKIE_CALLBACK 403
|
||||
# define SSL_R_NULL_SSL_CTX 195
|
||||
# define SSL_R_NULL_SSL_METHOD_PASSED 196
|
||||
# define SSL_R_OCSP_CALLBACK_FAILURE 294
|
||||
# define SSL_R_OLD_SESSION_CIPHER_NOT_RETURNED 197
|
||||
# define SSL_R_OLD_SESSION_COMPRESSION_ALGORITHM_NOT_RETURNED 344
|
||||
# define SSL_R_OVERFLOW_ERROR 237
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Generated by util/mkerr.pl DO NOT EDIT
|
||||
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 1995-2021 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -38,6 +38,7 @@ int ERR_load_X509V3_strings(void);
|
||||
# define X509V3_F_I2S_ASN1_IA5STRING 149
|
||||
# define X509V3_F_I2S_ASN1_INTEGER 120
|
||||
# define X509V3_F_I2V_AUTHORITY_INFO_ACCESS 138
|
||||
# define X509V3_F_I2V_AUTHORITY_KEYID 173
|
||||
# define X509V3_F_LEVEL_ADD_NODE 168
|
||||
# define X509V3_F_NOTICE_SECTION 132
|
||||
# define X509V3_F_NREF_NOS 133
|
||||
@@ -78,6 +79,7 @@ int ERR_load_X509V3_strings(void);
|
||||
# define X509V3_F_V2I_TLS_FEATURE 165
|
||||
# define X509V3_F_V3_GENERIC_EXTENSION 116
|
||||
# define X509V3_F_X509V3_ADD1_I2D 140
|
||||
# define X509V3_F_X509V3_ADD_LEN_VALUE 174
|
||||
# define X509V3_F_X509V3_ADD_VALUE 105
|
||||
# define X509V3_F_X509V3_EXT_ADD 104
|
||||
# define X509V3_F_X509V3_EXT_ADD_ALIAS 106
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -4,6 +4,7 @@
|
||||
#include <android/log.h>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <string.h>
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_limelight_nvstream_jni_MoonBridge_sendMouseMove(JNIEnv *env, jclass clazz, jshort deltaX, jshort deltaY) {
|
||||
@@ -16,6 +17,12 @@ Java_com_limelight_nvstream_jni_MoonBridge_sendMousePosition(JNIEnv *env, jclass
|
||||
LiSendMousePositionEvent(x, y, referenceWidth, referenceHeight);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_limelight_nvstream_jni_MoonBridge_sendMouseMoveAsMousePosition(JNIEnv *env, jclass clazz,
|
||||
jshort deltaX, jshort deltaY, jshort referenceWidth, jshort referenceHeight) {
|
||||
LiSendMouseMoveAsMousePositionEvent(deltaX, deltaY, referenceWidth, referenceHeight);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_limelight_nvstream_jni_MoonBridge_sendMouseButton(JNIEnv *env, jclass clazz, jbyte buttonEvent, jbyte mouseButton) {
|
||||
LiSendMouseButtonEvent(buttonEvent, mouseButton);
|
||||
@@ -54,6 +61,13 @@ Java_com_limelight_nvstream_jni_MoonBridge_sendMouseHighResScroll(JNIEnv *env, j
|
||||
LiSendHighResScrollEvent(scrollAmount);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_limelight_nvstream_jni_MoonBridge_sendUtf8Text(JNIEnv *env, jclass clazz, jstring text) {
|
||||
const char* utf8Text = (*env)->GetStringUTFChars(env, text, NULL);
|
||||
LiSendUtf8TextEvent(utf8Text, strlen(utf8Text));
|
||||
(*env)->ReleaseStringUTFChars(env, text, utf8Text);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_limelight_nvstream_jni_MoonBridge_stopConnection(JNIEnv *env, jclass clazz) {
|
||||
LiStopConnection();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="256dp"
|
||||
android:height="256dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
android:width="256dp"
|
||||
android:height="256dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM17,13h-4v4h-2v-4L7,13v-2h4L11,7h2v4h4v2z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM17,13h-4v4h-2v-4L7,13v-2h4L11,7h2v4h4v2z" />
|
||||
</vector>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="512dp"
|
||||
android:height="512dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
android:width="512dp"
|
||||
android:height="512dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M21,2L3,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h7v2L8,20v2h8v-2h-2v-2h7c1.1,0 2,-0.9 2,-2L23,4c0,-1.1 -0.9,-2 -2,-2zM21,16L3,16L3,4h18v12z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M21,2L3,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h7v2L8,20v2h8v-2h-2v-2h7c1.1,0 2,-0.9 2,-2L23,4c0,-1.1 -0.9,-2 -2,-2zM21,16L3,16L3,4h18v12z" />
|
||||
</vector>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="256dp"
|
||||
android:height="256dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
android:width="256dp"
|
||||
android:height="256dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,19h-2v-2h2v2zM15.07,11.25l-0.9,0.92C13.45,12.9 13,13.5 13,15h-2v-0.5c0,-1.1 0.45,-2.1 1.17,-2.83l1.24,-1.26c0.37,-0.36 0.59,-0.86 0.59,-1.41 0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2L8,9c0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,0.88 -0.36,1.68 -0.93,2.25z"/>
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,19h-2v-2h2v2zM15.07,11.25l-0.9,0.92C13.45,12.9 13,13.5 13,15h-2v-0.5c0,-1.1 0.45,-2.1 1.17,-2.83l1.24,-1.26c0.37,-0.36 0.59,-0.86 0.59,-1.41 0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2L8,9c0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,0.88 -0.36,1.68 -0.93,2.25z" />
|
||||
</vector>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="128dp"
|
||||
android:height="128dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
android:width="128dp"
|
||||
android:height="128dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM15.1,8L8.9,8L8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM15.1,8L8.9,8L8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z" />
|
||||
</vector>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<vector android:height="128dp" android:width="128dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF000000" android:pathData="M1,21h22L12,2 1,21zM13,18h-2v-2h2v2zM13,14h-2v-4h2v4z"/>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="128dp"
|
||||
android:height="128dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M1,21h22L12,2 1,21zM13,18h-2v-2h2v2zM13,14h-2v-4h2v4z" />
|
||||
</vector>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="128dp"
|
||||
android:height="128dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
android:width="128dp"
|
||||
android:height="128dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM10,16.5v-9l6,4.5 -6,4.5z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10s10,-4.48 10,-10S17.52,2 12,2zM9.5,16.5v-9l7,4.5L9.5,16.5z" />
|
||||
</vector>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="256dp"
|
||||
android:height="256dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
android:width="256dp"
|
||||
android:height="256dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z" />
|
||||
</vector>
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
android:text="@string/title_add_pc"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:preferKeepClear="true"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:layout_alignParentTop="true"/>
|
||||
@@ -29,6 +30,7 @@
|
||||
android:layout_alignParentStart="true"
|
||||
android:ems="10"
|
||||
android:singleLine="true"
|
||||
android:preferKeepClear="true"
|
||||
android:inputType="textNoSuggestions"
|
||||
android:hint="@string/ip_hint" >
|
||||
|
||||
@@ -43,6 +45,7 @@
|
||||
android:layout_below="@+id/manuallyAddPcText"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:preferKeepClear="true"
|
||||
android:text="@android:string/ok"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
android:gravity="center"
|
||||
android:paddingTop="5dp"
|
||||
android:paddingBottom="5dp"
|
||||
android:preferKeepClear="true"
|
||||
android:textSize="28sp"/>
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -4,11 +4,23 @@
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".Game" >
|
||||
|
||||
<View
|
||||
android:id="@+id/backgroundTouchView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center" />
|
||||
|
||||
<com.limelight.ui.StreamView
|
||||
android:id="@+id/surfaceView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center" />
|
||||
android:layout_gravity="center"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:focusedByDefault="true"
|
||||
android:defaultFocusHighlightEnabled="false">
|
||||
<requestFocus />
|
||||
</com.limelight.ui.StreamView>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/performanceOverlay"
|
||||
@@ -21,6 +33,7 @@
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:gravity="left"
|
||||
android:background="#80000000"
|
||||
android:preferKeepClear="true"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
@@ -34,6 +47,7 @@
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:gravity="right"
|
||||
android:background="#80000000"
|
||||
android:preferKeepClear="true"
|
||||
android:visibility="gone" />
|
||||
|
||||
</merge>
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:src="@drawable/ic_settings"
|
||||
android:preferKeepClear="true"
|
||||
style="?android:attr/borderlessButtonStyle"/>
|
||||
|
||||
<ImageButton
|
||||
@@ -69,6 +70,7 @@
|
||||
android:layout_toRightOf="@+id/settingsButton"
|
||||
android:layout_toEndOf="@+id/settingsButton"
|
||||
android:src="@drawable/ic_help"
|
||||
android:preferKeepClear="true"
|
||||
style="?android:attr/borderlessButtonStyle"/>
|
||||
|
||||
<ImageButton
|
||||
@@ -81,6 +83,7 @@
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:src="@drawable/ic_add"
|
||||
android:preferKeepClear="true"
|
||||
style="?android:attr/borderlessButtonStyle"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user