diff --git a/.appveyor.yml b/.appveyor.yml index 933f517..db3c3a0 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -5,6 +5,7 @@ image: branches: only: - master + - develop - /^v\d.*$/ - /^deploy-test(-.*)?$/ @@ -36,14 +37,14 @@ for: install: - git submodule update --init --recursive - sudo pip3 install protobuf - - brew install qt opus openssl@1.1 nasm sdl2 protobuf + - HOMEBREW_NO_AUTO_UPDATE=1 brew install qt@5 opus openssl@1.1 nasm sdl2 protobuf - scripts/build-ffmpeg.sh build_script: - - export CMAKE_PREFIX_PATH="`pwd`/ffmpeg-prefix;/usr/local/opt/openssl@1.1;/usr/local/opt/qt" + - export CMAKE_PREFIX_PATH="`pwd`/ffmpeg-prefix;/usr/local/opt/openssl@1.1;/usr/local/opt/qt@5" - scripts/build-common.sh - cp -a build/gui/chiaki.app Chiaki.app - - /usr/local/opt/qt/bin/macdeployqt Chiaki.app -dmg + - /usr/local/opt/qt@5/bin/macdeployqt Chiaki.app -dmg artifacts: - path: Chiaki.dmg diff --git a/.builds/android.yml b/.builds/android.yml index 7b64ab4..20cd88b 100644 --- a/.builds/android.yml +++ b/.builds/android.yml @@ -22,7 +22,7 @@ tasks: sudo docker run \ -v /home/build:/home/build \ -u $(id -u):$(id -g) \ - thestr4ng3r/android:f064ea6 \ + thestr4ng3r/android:90d826e \ /bin/bash -c "cd /home/build/chiaki/android && ./gradlew assembleRelease bundleRelease" cp chiaki/android/app/build/outputs/apk/release/app-release*.apk Chiaki.apk cp chiaki/android/app/build/outputs/bundle/release/app-release*.aab Chiaki.aab diff --git a/.builds/common.yml b/.builds/common.yml index 6208cad..b692b08 100644 --- a/.builds/common.yml +++ b/.builds/common.yml @@ -9,37 +9,47 @@ packages: - ninja - protoc - py3-protobuf + - py3-setuptools - opus-dev - qt5-qtbase-dev - qt5-qtsvg-dev - qt5-qtmultimedia-dev - ffmpeg-dev - sdl2-dev - - docker + - podman - fuse + - udev + - argp-standalone artifacts: - chiaki.nro - Chiaki.AppImage tasks: - - start_docker: | - sudo service docker start - sudo chmod +s /usr/bin/docker # Yes, I know what I am doing - sudo service fuse start # Fuse for AppImages + - setup_podman: | + sudo rc-service udev start + sudo rc-service cgroups start + sudo rc-service fuse start # Fuse for AppImages + echo build:100000:65536 | sudo tee /etc/subuid + echo build:100000:65536 | sudo tee /etc/subgid + # https://www.kernel.org/doc/Documentation/networking/tuntap.txt + # for slirp4netns + sudo mkdir -p /dev/net + sudo mknod /dev/net/tun c 10 200 + sudo chmod 0666 /dev/net/tun - local_build_and_test: | cd chiaki - cmake -Bbuild -GNinja + cmake -Bbuild -GNinja -DCHIAKI_ENABLE_CLI=ON -DCHIAKI_ENABLE_GUI=ON -DCHIAKI_CLI_ARGP_STANDALONE=ON ninja -C build build/test/chiaki-unit - appimage: | cd chiaki - scripts/run-docker-build-appimage.sh + scripts/run-podman-build-appimage.sh cp appimage/Chiaki.AppImage ../Chiaki.AppImage - switch: | cd chiaki - scripts/switch/run-docker-build-chiaki.sh + scripts/switch/run-podman-build-chiaki.sh cp build_switch/switch/chiaki.nro ../chiaki.nro - bullseye: | cd chiaki - scripts/run-docker-build-bullseye.sh + scripts/run-podman-build-bullseye.sh diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml index caa9016..72109b5 100644 --- a/.builds/freebsd.yml +++ b/.builds/freebsd.yml @@ -1,5 +1,5 @@ -image: freebsd/latest +image: freebsd/14.x sources: - https://git.sr.ht/~thestr4ng3r/chiaki @@ -7,7 +7,8 @@ sources: packages: - cmake - protobuf - - py37-protobuf + - py311-setuptools # should not be needed with nanopb >= 0.4.9 + - py311-protobuf - opus - qt5-core - qt5-qmake diff --git a/.builds/openbsd.yml b/.builds/openbsd.yml index b8785ef..e1595ac 100644 --- a/.builds/openbsd.yml +++ b/.builds/openbsd.yml @@ -1,5 +1,5 @@ -image: openbsd/6.7 +image: openbsd/latest sources: - https://git.sr.ht/~thestr4ng3r/chiaki @@ -7,6 +7,7 @@ sources: packages: - cmake - protobuf + - py3-setuptools # should not be needed with nanopb >= 0.4.9 - py3-protobuf - opus - qtbase diff --git a/.gitignore b/.gitignore index 0b9eccd..f30ef42 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,5 @@ compile_commands.json chiaki.conf /appimage .cache/ +/*.app +/*.dmg diff --git a/.gitmodules b/.gitmodules index ac87125..250f8fe 100644 --- a/.gitmodules +++ b/.gitmodules @@ -15,4 +15,4 @@ url = https://github.com/google/oboe [submodule "switch/borealis"] path = switch/borealis - url = https://github.com/natinusala/borealis.git + url = https://git.sr.ht/~thestr4ng3r/borealis diff --git a/CMakeLists.txt b/CMakeLists.txt index 611af18..64caaee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,7 @@ endif() tri_option(CHIAKI_ENABLE_FFMPEG_DECODER "Enable FFMPEG video decoder" ${CHIAKI_FFMPEG_DEFAULT}) tri_option(CHIAKI_ENABLE_PI_DECODER "Enable Raspberry Pi-specific video decoder (requires libraspberrypi0 and libraspberrypi-doc)" AUTO) option(CHIAKI_LIB_ENABLE_MBEDTLS "Use mbedtls instead of OpenSSL as part of Chiaki Lib" OFF) +option(CHIAKI_LIB_MBEDTLS_EXTERNAL_PROJECT "Fetch Mbed TLS instead of using system-provided libs" OFF) option(CHIAKI_LIB_OPENSSL_EXTERNAL_PROJECT "Use OpenSSL as CMake external project" OFF) option(CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER "Use SDL Gamecontroller for Input" ON) option(CHIAKI_CLI_ARGP_STANDALONE "Search for standalone argp lib for CLI" OFF) @@ -31,8 +32,8 @@ tri_option(CHIAKI_USE_SYSTEM_JERASURE "Use system-provided jerasure instead of s tri_option(CHIAKI_USE_SYSTEM_NANOPB "Use system-provided nanopb instead of submodule" AUTO) set(CHIAKI_VERSION_MAJOR 2) -set(CHIAKI_VERSION_MINOR 1) -set(CHIAKI_VERSION_PATCH 1) +set(CHIAKI_VERSION_MINOR 2) +set(CHIAKI_VERSION_PATCH 0) set(CHIAKI_VERSION ${CHIAKI_VERSION_MAJOR}.${CHIAKI_VERSION_MINOR}.${CHIAKI_VERSION_PATCH}) set(CPACK_PACKAGE_NAME "chiaki") @@ -89,6 +90,20 @@ endif() if(CHIAKI_LIB_ENABLE_MBEDTLS) add_definitions(-DCHIAKI_LIB_ENABLE_MBEDTLS) + if(CHIAKI_LIB_MBEDTLS_EXTERNAL_PROJECT) + set(FETCHCONTENT_QUIET CACHE BOOL FALSE) + include(FetchContent) + set(ENABLE_TESTING CACHE INTERNAL OFF) + set(ENABLE_PROGRAMS CACHE INTERNAL OFF) + set(USE_SHARED_MBEDTLS_LIBRARY CACHE INTERNAL OFF) + FetchContent_Declare( + mbedtls + GIT_REPOSITORY https://github.com/Mbed-TLS/mbedtls.git + GIT_TAG 8b3f26a5ac38d4fdccbc5c5366229f3e01dafcc0 # v2.28.0 + GIT_PROGRESS TRUE + ) + FetchContent_MakeAvailable(mbedtls) + endif() endif() if(CHIAKI_ENABLE_FFMPEG_DECODER) @@ -135,21 +150,30 @@ if(CHIAKI_ENABLE_CLI) add_subdirectory(cli) endif() +if(CHIAKI_ENABLE_GUI AND CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER) + find_package(SDL2 MODULE REQUIRED) +endif() + if(CHIAKI_ENABLE_SETSU) - find_package(Udev QUIET) - find_package(Evdev QUIET) - if(Udev_FOUND AND Evdev_FOUND) - set(CHIAKI_ENABLE_SETSU ON) - else() - if(NOT CHIAKI_ENABLE_SETSU STREQUAL AUTO) - message(FATAL_ERROR " -CHIAKI_ENABLE_SETSU is set to ON, but its dependencies (udev and evdev) could not be resolved. -Keep in mind that setsu is only supported on Linux!") - endif() + if(CHIAKI_ENABLE_SETSU STREQUAL AUTO AND SDL2_FOUND AND (SDL2_VERSION_MINOR GREATER 0 OR SDL2_VERSION_PATCH GREATER_EQUAL 14)) + message(STATUS "SDL version ${SDL2_VERSION} is >= 2.0.14, disabling Setsu") set(CHIAKI_ENABLE_SETSU OFF) - endif() - if(CHIAKI_ENABLE_SETSU) - add_subdirectory(setsu) + else() + find_package(Udev QUIET) + find_package(Evdev QUIET) + if(Udev_FOUND AND Evdev_FOUND) + set(CHIAKI_ENABLE_SETSU ON) + else() + if(NOT CHIAKI_ENABLE_SETSU STREQUAL AUTO) + message(FATAL_ERROR " + CHIAKI_ENABLE_SETSU is set to ON, but its dependencies (udev and evdev) could not be resolved. + Keep in mind that setsu is only supported on Linux!") + endif() + set(CHIAKI_ENABLE_SETSU OFF) + endif() + if(CHIAKI_ENABLE_SETSU) + add_subdirectory(setsu) + endif() endif() endif() @@ -160,7 +184,6 @@ else() endif() if(CHIAKI_ENABLE_GUI) - #add_subdirectory(setsu) add_subdirectory(gui) endif() diff --git a/README.md b/README.md index f306470..1a94b66 100644 --- a/README.md +++ b/README.md @@ -8,14 +8,19 @@ [![AppVeyor Build status](https://ci.appveyor.com/api/projects/status/c81ogebvsmo43dd3?svg=true)](https://ci.appveyor.com/project/thestr4ng3r/chiaki) [![builds.sr.ht Status](https://builds.sr.ht/~thestr4ng3r/chiaki.svg)](https://builds.sr.ht/~thestr4ng3r/chiaki?) Chiaki is a Free and Open Source Software Client for PlayStation 4 and PlayStation 5 Remote Play -for Linux, FreeBSD, OpenBSD, Android, macOS, Windows, Nintendo Switch and potentially even more platforms. +for Linux, FreeBSD, OpenBSD, NetBSD, Android, macOS, Windows, Nintendo Switch and potentially even more platforms. -![Screenshot](assets/screenshot.png) - -## Project Status +## Project Status and Contributing As all relevant features are implemented, this project is considered to be finished and in maintenance mode only. -No major updates are planned and contributions are only accepted in special cases. +No major updates are planned and contributions are only accepted in special cases such as security issues. +The objective is to keep a stable base and not break existing support for less mainstream platforms such as BSDs. + +**For a more active, fast moving and community-oriented project, refer +to [chiaki-ng](https://streetpea.github.io/chiaki-ng/) ("next generation"). +If you would like to contribute, this will likely also be the best place to do so.** + +![Screenshot](assets/screenshot.png) ## Installing @@ -28,7 +33,7 @@ Builds are provided for Linux, Android, macOS, Nintendo Switch and Windows. You can download them [here](https://git.sr.ht/~thestr4ng3r/chiaki/refs). * **Linux**: The provided file is an [AppImage](https://appimage.org/). Simply make it executable (`chmod +x .AppImage`) and run it. -* **Android**: Install from [Google Play](https://play.google.com/store/apps/details?id=com.metallic.chiaki), [F-Droid](https://f-droid.org/packages/com.metallic.chiaki/) or download the APK from Sourcehut. +* **Android**: Install from [F-Droid](https://f-droid.org/packages/com.metallic.chiaki/) or download the APK from Sourcehut. * **macOS**: Drag the application from the `.dmg` into your Applications folder. * **Windows**: Extract the `.zip` file and execute `chiaki.exe`. * **Switch**: Download the `.nro` file and copy it into the `switch/` directory on your SD card. diff --git a/android/app/build.gradle b/android/app/build.gradle index 282b42c..21a89f3 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -18,13 +18,12 @@ def chiakiVersion = "$chiakiVersionMajor.$chiakiVersionMinor.$chiakiVersionPatch println("Determined Chiaki Version: $chiakiVersion") android { - compileSdkVersion 30 - buildToolsVersion "30.0.2" + compileSdkVersion 33 defaultConfig { applicationId "com.metallic.chiaki" minSdkVersion 21 - targetSdkVersion 30 - versionCode 11 + targetSdkVersion 33 + versionCode 12 versionName chiakiVersion externalNativeBuild { cmake { @@ -33,6 +32,8 @@ android { "-DCHIAKI_ENABLE_GUI=OFF", "-DCHIAKI_ENABLE_SETSU=OFF", "-DCHIAKI_ENABLE_ANDROID=ON", + "-DCHIAKI_ENABLE_FFMPEG_DECODER=OFF", + "-DCHIAKI_ENABLE_PI_DECODER=OFF", "-DCHIAKI_LIB_ENABLE_OPUS=OFF", "-DCHIAKI_LIB_OPENSSL_EXTERNAL_PROJECT=ON" } @@ -50,7 +51,7 @@ android { } externalNativeBuild { cmake { - version "3.10.2+" + version "3.22.1" path rootCMakeLists } } @@ -93,23 +94,23 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'androidx.appcompat:appcompat:1.2.0' - implementation 'androidx.core:core-ktx:1.3.2' - implementation 'androidx.constraintlayout:constraintlayout:2.0.4' - implementation 'androidx.recyclerview:recyclerview:1.1.0' - implementation 'androidx.preference:preference:1.1.1' - implementation 'com.google.android.material:material:1.2.1' + implementation 'androidx.appcompat:appcompat:1.6.0' + implementation 'androidx.core:core-ktx:1.9.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation 'androidx.recyclerview:recyclerview:1.2.1' + implementation 'androidx.preference:preference:1.2.0' + implementation 'com.google.android.material:material:1.8.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' - implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0' - implementation 'androidx.lifecycle:lifecycle-reactivestreams:2.2.0' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1' + implementation 'androidx.lifecycle:lifecycle-reactivestreams:2.5.1' implementation "io.reactivex.rxjava2:rxjava:2.2.20" implementation "io.reactivex.rxjava2:rxkotlin:2.4.0" implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' - def room_version = "2.2.6" + def room_version = "2.5.0" implementation "androidx.room:room-runtime:$room_version" kapt "androidx.room:room-compiler:$room_version" implementation "androidx.room:room-ktx:$room_version" implementation "androidx.room:room-rxjava2:$room_version" - implementation "com.squareup.moshi:moshi:1.9.2" - kapt "com.squareup.moshi:moshi-kotlin-codegen:1.9.2" + implementation "com.squareup.moshi:moshi:1.14.0" + kapt "com.squareup.moshi:moshi-kotlin-codegen:1.14.0" } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index fa14662..a0d28cb 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + + android:configChanges="keyboard|keyboardHidden|orientation|screenSize" + android:exported="true"> diff --git a/android/app/src/main/cpp/chiaki-jni.c b/android/app/src/main/cpp/chiaki-jni.c index d0b374a..9b14b60 100644 --- a/android/app/src/main/cpp/chiaki-jni.c +++ b/android/app/src/main/cpp/chiaki-jni.c @@ -110,9 +110,9 @@ JNIEXPORT jstring JNICALL JNI_FCN(quitReasonToString)(JNIEnv *env, jobject obj, return E->NewStringUTF(env, chiaki_quit_reason_string((ChiakiQuitReason)value)); } -JNIEXPORT jboolean JNICALL JNI_FCN(quitReasonIsStopped)(JNIEnv *env, jobject obj, jint value) +JNIEXPORT jboolean JNICALL JNI_FCN(quitReasonIsError)(JNIEnv *env, jobject obj, jint value) { - return value == CHIAKI_QUIT_REASON_STOPPED; + return chiaki_quit_reason_is_error(value); } JNIEXPORT jobject JNICALL JNI_FCN(videoProfilePreset)(JNIEnv *env, jobject obj, jint resolution_preset, jint fps_preset, jobject codec) diff --git a/android/app/src/main/cpp/oboe b/android/app/src/main/cpp/oboe index 0ab5b12..8740d0f 160000 --- a/android/app/src/main/cpp/oboe +++ b/android/app/src/main/cpp/oboe @@ -1 +1 @@ -Subproject commit 0ab5b12a5bc3630a3d6c83b20eed2a669ebf7a24 +Subproject commit 8740d0fc321a55489dbbf6067298201b7d2e106d diff --git a/android/app/src/main/java/com/metallic/chiaki/common/ManualHost.kt b/android/app/src/main/java/com/metallic/chiaki/common/ManualHost.kt index 5825000..7065610 100644 --- a/android/app/src/main/java/com/metallic/chiaki/common/ManualHost.kt +++ b/android/app/src/main/java/com/metallic/chiaki/common/ManualHost.kt @@ -3,7 +3,7 @@ package com.metallic.chiaki.common import androidx.room.* -import androidx.room.ForeignKey.SET_NULL +import androidx.room.ForeignKey.Companion.SET_NULL import io.reactivex.Completable import io.reactivex.Flowable import io.reactivex.Single diff --git a/android/app/src/main/java/com/metallic/chiaki/common/RegisteredHost.kt b/android/app/src/main/java/com/metallic/chiaki/common/RegisteredHost.kt index b96d88d..d920a8c 100644 --- a/android/app/src/main/java/com/metallic/chiaki/common/RegisteredHost.kt +++ b/android/app/src/main/java/com/metallic/chiaki/common/RegisteredHost.kt @@ -3,7 +3,7 @@ package com.metallic.chiaki.common import androidx.room.* -import androidx.room.ColumnInfo.BLOB +import androidx.room.ColumnInfo.Companion.BLOB import com.metallic.chiaki.lib.RegistHost import com.metallic.chiaki.lib.Target import io.reactivex.Completable diff --git a/android/app/src/main/java/com/metallic/chiaki/common/SerializedSettings.kt b/android/app/src/main/java/com/metallic/chiaki/common/SerializedSettings.kt index 8334dc5..74257ac 100644 --- a/android/app/src/main/java/com/metallic/chiaki/common/SerializedSettings.kt +++ b/android/app/src/main/java/com/metallic/chiaki/common/SerializedSettings.kt @@ -25,6 +25,8 @@ import io.reactivex.rxkotlin.addTo import io.reactivex.schedulers.Schedulers import okio.Buffer import okio.Okio +import okio.buffer +import okio.source import java.io.File import java.io.IOException @@ -164,7 +166,7 @@ fun importSettingsFromUri(activity: Activity, uri: Uri, disposable: CompositeDis try { val inputStream = activity.contentResolver.openInputStream(uri) ?: throw IOException() - val buffer = Okio.buffer(Okio.source(inputStream)) + val buffer = inputStream.source().buffer() val reader = JsonReader.of(buffer) val adapter = moshi().serializedSettingsAdapter() diff --git a/android/app/src/main/java/com/metallic/chiaki/lib/Chiaki.kt b/android/app/src/main/java/com/metallic/chiaki/lib/Chiaki.kt index 2c474f0..bb0b0a6 100644 --- a/android/app/src/main/java/com/metallic/chiaki/lib/Chiaki.kt +++ b/android/app/src/main/java/com/metallic/chiaki/lib/Chiaki.kt @@ -83,7 +83,7 @@ private class ChiakiNative } @JvmStatic external fun errorCodeToString(value: Int): String @JvmStatic external fun quitReasonToString(value: Int): String - @JvmStatic external fun quitReasonIsStopped(value: Int): Boolean + @JvmStatic external fun quitReasonIsError(value: Int): Boolean @JvmStatic external fun videoProfilePreset(resolutionPreset: Int, fpsPreset: Int, codec: Codec): ConnectVideoProfile @JvmStatic external fun sessionCreate(result: CreateResult, connectInfo: ConnectInfo, logFile: String?, logVerbose: Boolean, javaSession: Session) @JvmStatic external fun sessionFree(ptr: Long) @@ -309,10 +309,7 @@ class QuitReason(val value: Int) { override fun toString() = ChiakiNative.quitReasonToString(value) - /** - * whether the reason is CHIAKI_QUIT_REASON_STOPPED - */ - val isStopped = ChiakiNative.quitReasonIsStopped(value) + val isError = ChiakiNative.quitReasonIsError(value) } sealed class Event diff --git a/android/app/src/main/java/com/metallic/chiaki/stream/StreamActivity.kt b/android/app/src/main/java/com/metallic/chiaki/stream/StreamActivity.kt index b0248be..bd77e0c 100644 --- a/android/app/src/main/java/com/metallic/chiaki/stream/StreamActivity.kt +++ b/android/app/src/main/java/com/metallic/chiaki/stream/StreamActivity.kt @@ -174,7 +174,7 @@ class StreamActivity : AppCompatActivity(), View.OnSystemUiVisibilityChangeListe .alpha(1.0f) .setListener(object: AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator?) + override fun onAnimationEnd(animation: Animator) { binding.overlay.alpha = 1.0f } @@ -189,7 +189,7 @@ class StreamActivity : AppCompatActivity(), View.OnSystemUiVisibilityChangeListe .alpha(0.0f) .setListener(object: AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator?) + override fun onAnimationEnd(animation: Animator) { binding.overlay.isGone = true } @@ -230,28 +230,33 @@ class StreamActivity : AppCompatActivity(), View.OnSystemUiVisibilityChangeListe { is StreamStateQuit -> { - if(!state.reason.isStopped && dialogContents != StreamQuitDialog) + if(dialogContents != StreamQuitDialog) { - dialog?.dismiss() - val reasonStr = state.reasonString - val dialog = MaterialAlertDialogBuilder(this) - .setMessage(getString(R.string.alert_message_session_quit, state.reason.toString()) - + (if(reasonStr != null) "\n$reasonStr" else "")) - .setPositiveButton(R.string.action_reconnect) { _, _ -> - dialog = null - reconnect() - } - .setOnCancelListener { - dialog = null - finish() - } - .setNegativeButton(R.string.action_quit_session) { _, _ -> - dialog = null - finish() - } - .create() - dialogContents = StreamQuitDialog - dialog.show() + if(state.reason.isError) + { + dialog?.dismiss() + val reasonStr = state.reasonString + val dialog = MaterialAlertDialogBuilder(this) + .setMessage(getString(R.string.alert_message_session_quit, state.reason.toString()) + + (if(reasonStr != null) "\n$reasonStr" else "")) + .setPositiveButton(R.string.action_reconnect) { _, _ -> + dialog = null + reconnect() + } + .setOnCancelListener { + dialog = null + finish() + } + .setNegativeButton(R.string.action_quit_session) { _, _ -> + dialog = null + finish() + } + .create() + dialogContents = StreamQuitDialog + dialog.show() + } + else + finish() } } @@ -306,6 +311,8 @@ class StreamActivity : AppCompatActivity(), View.OnSystemUiVisibilityChangeListe dialog.show() } } + + else -> {} } } diff --git a/android/build.gradle b/android/build.gradle index 7ec4859..cdfc5d6 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,14 +1,14 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.4.21' + ext.kotlin_version = '1.8.0' repositories { google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.1' + classpath 'com.android.tools.build:gradle:7.4.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index c21407a..7a0d628 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Jan 15 11:37:05 CET 2021 +#Sun Feb 05 16:25:19 CET 2023 distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip +zipStoreBase=GRADLE_USER_HOME diff --git a/assets/chiaki_macos.svg b/assets/chiaki_macos.svg new file mode 100644 index 0000000..a4ce8f4 --- /dev/null +++ b/assets/chiaki_macos.svg @@ -0,0 +1,266 @@ + + + + diff --git a/assets/chiaki_macos_simple.svg b/assets/chiaki_macos_simple.svg new file mode 100644 index 0000000..7f1359d --- /dev/null +++ b/assets/chiaki_macos_simple.svg @@ -0,0 +1,102 @@ + + + + diff --git a/cli/CMakeLists.txt b/cli/CMakeLists.txt index 83c37a9..83de1f9 100644 --- a/cli/CMakeLists.txt +++ b/cli/CMakeLists.txt @@ -15,3 +15,4 @@ endif() add_executable(chiaki-cli src/main.c) target_link_libraries(chiaki-cli chiaki-cli-lib) +install(TARGETS chiaki-cli) diff --git a/cli/src/discover.c b/cli/src/discover.c index 503b53a..cf9319f 100644 --- a/cli/src/discover.c +++ b/cli/src/discover.c @@ -142,13 +142,19 @@ CHIAKI_EXPORT int chiaki_cli_cmd_discover(ChiakiLog *log, int argc, char *argv[] return 1; } - ((struct sockaddr_in *)host_addr)->sin_port = htons(CHIAKI_DISCOVERY_PORT_PS4); // TODO: IPv6, PS5, should probably use the service - ChiakiDiscoveryPacket packet; memset(&packet, 0, sizeof(packet)); packet.cmd = CHIAKI_DISCOVERY_CMD_SRCH; - - chiaki_discovery_send(&discovery, &packet, host_addr, host_addr_len); + packet.protocol_version = CHIAKI_DISCOVERY_PROTOCOL_VERSION_PS4; + ((struct sockaddr_in *)host_addr)->sin_port = htons(CHIAKI_DISCOVERY_PORT_PS4); + err = chiaki_discovery_send(&discovery, &packet, host_addr, host_addr_len); + if(err != CHIAKI_ERR_SUCCESS) + CHIAKI_LOGE(log, "Failed to send discovery packet for PS4: %s", chiaki_error_string(err)); + packet.protocol_version = CHIAKI_DISCOVERY_PROTOCOL_VERSION_PS5; + ((struct sockaddr_in *)host_addr)->sin_port = htons(CHIAKI_DISCOVERY_PORT_PS5); + err = chiaki_discovery_send(&discovery, &packet, host_addr, host_addr_len); + if(err != CHIAKI_ERR_SUCCESS) + CHIAKI_LOGE(log, "Failed to send discovery packet for PS5: %s", chiaki_error_string(err)); while(1) sleep(1); // TODO: wtf diff --git a/cli/src/wakeup.c b/cli/src/wakeup.c index cf5ab45..cea2792 100644 --- a/cli/src/wakeup.c +++ b/cli/src/wakeup.c @@ -11,10 +11,14 @@ static char doc[] = "Send a PS4 wakeup packet."; #define ARG_KEY_HOST 'h' #define ARG_KEY_REGISTKEY 'r' +#define ARG_KEY_PS4 '4' +#define ARG_KEY_PS5 '5' static struct argp_option options[] = { { "host", ARG_KEY_HOST, "Host", 0, "Host to send wakeup packet to", 0 }, - { "registkey", ARG_KEY_REGISTKEY, "RegistKey", 0, "PS4 registration key", 0 }, + { "registkey", ARG_KEY_REGISTKEY, "RegistKey", 0, "Remote Play registration key (plaintext)", 0 }, + { "ps4", ARG_KEY_PS4, NULL, 0, "PlayStation 4", 0 }, + { "ps5", ARG_KEY_PS5, NULL, 0, "PlayStation 5 (default)", 0 }, { 0 } }; @@ -22,6 +26,7 @@ typedef struct arguments { const char *host; const char *registkey; + bool ps5; } Arguments; static int parse_opt(int key, char *arg, struct argp_state *state) @@ -39,6 +44,12 @@ static int parse_opt(int key, char *arg, struct argp_state *state) case ARGP_KEY_ARG: argp_usage(state); break; + case ARG_KEY_PS4: + arguments->ps5 = false; + break; + case ARG_KEY_PS5: + arguments->ps5 = true; + break; default: return ARGP_ERR_UNKNOWN; } @@ -51,6 +62,7 @@ static struct argp argp = { options, parse_opt, 0, doc, 0, 0, 0 }; CHIAKI_EXPORT int chiaki_cli_cmd_wakeup(ChiakiLog *log, int argc, char *argv[]) { Arguments arguments = { 0 }; + arguments.ps5 = true; error_t argp_r = argp_parse(&argp, argc, argv, ARGP_IN_ORDER, NULL, &arguments); if(argp_r != 0) return 1; @@ -73,5 +85,5 @@ CHIAKI_EXPORT int chiaki_cli_cmd_wakeup(ChiakiLog *log, int argc, char *argv[]) uint64_t credential = (uint64_t)strtoull(arguments.registkey, NULL, 16); - return chiaki_discovery_wakeup(log, NULL, arguments.host, credential, false); + return chiaki_discovery_wakeup(log, NULL, arguments.host, credential, arguments.ps5); } diff --git a/cmake/FindSDL2.cmake b/cmake/FindSDL2.cmake index a0b9ebd..03717b0 100644 --- a/cmake/FindSDL2.cmake +++ b/cmake/FindSDL2.cmake @@ -1,6 +1,26 @@ find_package(SDL2 NO_MODULE QUIET) +# Adapted from libsdl-org/SDL_ttf: https://github.com/libsdl-org/SDL_ttf/blob/main/cmake/FindPrivateSDL2.cmake#L19-L31 +# Copyright (C) 1997-2022 Sam Lantinga +# Licensed under the zlib license (https://github.com/libsdl-org/SDL_ttf/blob/main/LICENSE.txt) +set(SDL2_VERSION_MAJOR) +set(SDL2_VERSION_MINOR) +set(SDL2_VERSION_PATCH) +set(SDL2_VERSION) +if(SDL2_INCLUDE_DIR) + file(READ "${SDL2_INCLUDE_DIR}/SDL_version.h" _sdl_version_h) + string(REGEX MATCH "#define[ \t]+SDL_MAJOR_VERSION[ \t]+([0-9]+)" _sdl2_major_re "${_sdl_version_h}") + set(SDL2_VERSION_MAJOR "${CMAKE_MATCH_1}") + string(REGEX MATCH "#define[ \t]+SDL_MINOR_VERSION[ \t]+([0-9]+)" _sdl2_minor_re "${_sdl_version_h}") + set(SDL2_VERSION_MINOR "${CMAKE_MATCH_1}") + string(REGEX MATCH "#define[ \t]+SDL_PATCHLEVEL[ \t]+([0-9]+)" _sdl2_patch_re "${_sdl_version_h}") + set(SDL2_VERSION_PATCH "${CMAKE_MATCH_1}") + if(_sdl2_major_re AND _sdl2_minor_re AND _sdl2_patch_re) + set(SDL2_VERSION "${SDL2_VERSION_MAJOR}.${SDL2_VERSION_MINOR}.${SDL2_VERSION_PATCH}") + endif() +endif() + if(SDL2_FOUND AND (NOT TARGET SDL2::SDL2)) add_library(SDL2::SDL2 UNKNOWN IMPORTED GLOBAL) if(NOT SDL2_LIBDIR) diff --git a/cmake/OpenSSLExternalProject.cmake b/cmake/OpenSSLExternalProject.cmake index 0538d48..1e1370d 100644 --- a/cmake/OpenSSLExternalProject.cmake +++ b/cmake/OpenSSLExternalProject.cmake @@ -33,8 +33,8 @@ endif() find_program(MAKE_EXE NAMES gmake make) ExternalProject_Add(OpenSSL-ExternalProject - URL https://www.openssl.org/source/openssl-1.1.1d.tar.gz - URL_HASH SHA256=1e3a91bc1f9dfce01af26026f856e064eab4c8ee0a8f457b5ae30b40b8b711f2 + URL https://www.openssl.org/source/openssl-1.1.1s.tar.gz + URL_HASH SHA256=c5ac01e760ee6ff0dab61d6b2bbd30146724d063eb322180c6f18a6f74e4b6aa INSTALL_DIR "${OPENSSL_INSTALL_DIR}" CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env ${OPENSSL_BUILD_ENV} "/Configure" "--prefix=" no-shared ${OPENSSL_CONFIG_EXTRA_ARGS} "${OPENSSL_OS_COMPILER}" diff --git a/cmake/switch.cmake b/cmake/switch.cmake index 6046d67..7e600be 100644 --- a/cmake/switch.cmake +++ b/cmake/switch.cmake @@ -1,34 +1,15 @@ # Find DEVKITPRO set(DEVKITPRO "$ENV{DEVKITPRO}" CACHE PATH "Path to DevKitPro") -set(PORTLIBS_PREFIX "$ENV{PORTLIBS_PREFIX}" CACHE PATH "Path to portlibs inside DevKitPro") -if(NOT DEVKITPRO OR NOT PORTLIBS_PREFIX) - message(FATAL_ERROR "Please set DEVKITPRO & PORTLIBS_PREFIX env before calling cmake. https://devkitpro.org/wiki/Getting_Started") +if(NOT DEVKITPRO) + message(FATAL_ERROR "Please set DEVKITPRO env before calling cmake. https://devkitpro.org/wiki/Getting_Started") endif() # include devkitpro toolchain -include("${DEVKITPRO}/switch.cmake") +include("${DEVKITPRO}/cmake/Switch.cmake") set(NSWITCH TRUE) -# Enable gcc -g, to use -# /opt/devkitpro/devkitA64/bin/aarch64-none-elf-addr2line -e build_switch/switch/chiaki -f -p -C -a 0xCCB5C -# set(CMAKE_BUILD_TYPE Debug) -# set(CMAKE_POSITION_INDEPENDENT_CODE ON) -# set(BUILD_SHARED_LIBS OFF CACHE INTERNAL "Shared libs not available" ) - -# FIXME rework this file to use the toolchain only -# https://github.com/diasurgical/devilutionX/pull/764 -set(ARCH "-march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -ftls-model=local-exec") -# set(CMAKE_C_FLAGS "-O2 -ffunction-sections ${ARCH}") -set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS}") -# workaroud force -fPIE to avoid -# aarch64-none-elf/bin/ld: read-only segment has dynamic relocations -set(CMAKE_EXE_LINKER_FLAGS "-specs=${DEVKITPRO}/libnx/switch.specs ${ARCH} -fPIE -Wl,-Map,Output.map") - -# add portlibs to the list of include dir -include_directories("${PORTLIBS_PREFIX}/include") - # troubleshoot message(STATUS "CMAKE_FIND_ROOT_PATH = ${CMAKE_FIND_ROOT_PATH}") message(STATUS "PKG_CONFIG_EXECUTABLE = ${PKG_CONFIG_EXECUTABLE}") @@ -79,4 +60,3 @@ function(add_nro_target output_name target title author version icon romfs) endfunction() set(CMAKE_USE_SYSTEM_ENVIRONMENT_PATH OFF) -set(CMAKE_PREFIX_PATH "/") diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt index 5f44908..443150f 100644 --- a/gui/CMakeLists.txt +++ b/gui/CMakeLists.txt @@ -6,9 +6,6 @@ find_package(Qt5 REQUIRED COMPONENTS Core Widgets Gui Concurrent Multimedia Open if(APPLE) find_package(Qt5 REQUIRED COMPONENTS MacExtras) endif() -if(CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER) - find_package(SDL2 MODULE REQUIRED) -endif() if(WIN32) add_definitions(-DWIN32_LEAN_AND_MEAN) @@ -70,12 +67,12 @@ if(CHIAKI_ENABLE_CLI) endif() target_link_libraries(chiaki Qt5::Core Qt5::Widgets Qt5::Gui Qt5::Concurrent Qt5::Multimedia Qt5::OpenGL Qt5::Svg) +target_link_libraries(chiaki SDL2::SDL2) if(APPLE) target_link_libraries(chiaki Qt5::MacExtras) target_compile_definitions(chiaki PRIVATE CHIAKI_GUI_ENABLE_QT_MACEXTRAS) endif() if(CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER) - target_link_libraries(chiaki SDL2::SDL2) target_compile_definitions(chiaki PRIVATE CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER) endif() if(CHIAKI_ENABLE_SETSU) diff --git a/gui/chiaki.icns b/gui/chiaki.icns index 13613ff..e51fa65 100644 Binary files a/gui/chiaki.icns and b/gui/chiaki.icns differ diff --git a/gui/include/avopenglwidget.h b/gui/include/avopenglwidget.h index 6f234e9..19bd287 100644 --- a/gui/include/avopenglwidget.h +++ b/gui/include/avopenglwidget.h @@ -3,6 +3,8 @@ #ifndef CHIAKI_AVOPENGLWIDGET_H #define CHIAKI_AVOPENGLWIDGET_H +#include "transformmode.h" + #include #include @@ -74,21 +76,24 @@ class AVOpenGLWidget: public QOpenGLWidget public: static QSurfaceFormat CreateSurfaceFormat(); - explicit AVOpenGLWidget(StreamSession *session, QWidget *parent = nullptr); + explicit AVOpenGLWidget(StreamSession *session, QWidget *parent = nullptr, TransformMode transform_mode = TransformMode::Fit); ~AVOpenGLWidget() override; void SwapFrames(); AVOpenGLFrame *GetBackgroundFrame() { return &frames[1 - frame_fg]; } + void SetTransformMode(TransformMode mode) { transform_mode = mode; } + TransformMode GetTransformMode() const { return transform_mode; } + protected: + TransformMode transform_mode; void mouseMoveEvent(QMouseEvent *event) override; void initializeGL() override; void paintGL() override; - private slots: - void ResetMouseTimeout(); public slots: + void ResetMouseTimeout(); void HideMouse(); }; diff --git a/gui/include/controllermanager.h b/gui/include/controllermanager.h index 18d9d72..eee0120 100644 --- a/gui/include/controllermanager.h +++ b/gui/include/controllermanager.h @@ -12,8 +12,12 @@ #ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER #include +#include #endif +#define PS_TOUCHPAD_MAX_X 1920 +#define PS_TOUCHPAD_MAX_Y 1079 + class Controller; class ControllerManager : public QObject @@ -33,7 +37,9 @@ class ControllerManager : public QObject private slots: void UpdateAvailableControllers(); void HandleEvents(); - void ControllerEvent(int device_id); +#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER + void ControllerEvent(SDL_Event evt); +#endif public: static ControllerManager *GetInstance(); @@ -57,12 +63,24 @@ class Controller : public QObject private: Controller(int device_id, ControllerManager *manager); - void UpdateState(); +#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER + void UpdateState(SDL_Event event); + bool HandleButtonEvent(SDL_ControllerButtonEvent event); + bool HandleAxisEvent(SDL_ControllerAxisEvent event); +#if SDL_VERSION_ATLEAST(2, 0, 14) + bool HandleSensorEvent(SDL_ControllerSensorEvent event); + bool HandleTouchpadEvent(SDL_ControllerTouchpadEvent event); +#endif +#endif ControllerManager *manager; int id; + ChiakiOrientationTracker orientation_tracker; + ChiakiControllerState state; + bool is_dualsense; #ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER + QMap, uint8_t> touch_ids; SDL_GameController *controller; #endif @@ -74,9 +92,43 @@ class Controller : public QObject QString GetName(); ChiakiControllerState GetState(); void SetRumble(uint8_t left, uint8_t right); + void SetTriggerEffects(uint8_t type_left, const uint8_t *data_left, uint8_t type_right, const uint8_t *data_right); + bool IsDualSense(); signals: void StateChanged(); }; +/* PS5 trigger effect documentation: + https://controllers.fandom.com/wiki/Sony_DualSense#FFB_Trigger_Modes + + Taken from SDL2, licensed under the zlib license, + Copyright (C) 1997-2022 Sam Lantinga + https://github.com/libsdl-org/SDL/blob/release-2.24.1/test/testgamecontroller.c#L263-L289 +*/ +typedef struct +{ + Uint8 ucEnableBits1; /* 0 */ + Uint8 ucEnableBits2; /* 1 */ + Uint8 ucRumbleRight; /* 2 */ + Uint8 ucRumbleLeft; /* 3 */ + Uint8 ucHeadphoneVolume; /* 4 */ + Uint8 ucSpeakerVolume; /* 5 */ + Uint8 ucMicrophoneVolume; /* 6 */ + Uint8 ucAudioEnableBits; /* 7 */ + Uint8 ucMicLightMode; /* 8 */ + Uint8 ucAudioMuteBits; /* 9 */ + Uint8 rgucRightTriggerEffect[11]; /* 10 */ + Uint8 rgucLeftTriggerEffect[11]; /* 21 */ + Uint8 rgucUnknown1[6]; /* 32 */ + Uint8 ucLedFlags; /* 38 */ + Uint8 rgucUnknown2[2]; /* 39 */ + Uint8 ucLedAnim; /* 41 */ + Uint8 ucLedBrightness; /* 42 */ + Uint8 ucPadLights; /* 43 */ + Uint8 ucLedRed; /* 44 */ + Uint8 ucLedGreen; /* 45 */ + Uint8 ucLedBlue; /* 46 */ +} DS5EffectsState_t; + #endif // CHIAKI_CONTROLLERMANAGER_H diff --git a/gui/include/mainwindow.h b/gui/include/mainwindow.h index f1190de..4112c19 100644 --- a/gui/include/mainwindow.h +++ b/gui/include/mainwindow.h @@ -55,6 +55,7 @@ class MainWindow : public QMainWindow void UpdateDiscoveryEnabled(); void ShowSettings(); + void Quit(); void UpdateDisplayServers(); void UpdateServerWidgets(); diff --git a/gui/include/settings.h b/gui/include/settings.h index c2320b8..00f38ab 100644 --- a/gui/include/settings.h +++ b/gui/include/settings.h @@ -63,6 +63,9 @@ class Settings : public QObject void SetLogVerbose(bool enabled) { settings.setValue("settings/log_verbose", enabled); } uint32_t GetLogLevelMask(); + bool GetDualSenseEnabled() const { return settings.value("settings/dualsense_enabled", false).toBool(); } + void SetDualSenseEnabled(bool enabled) { settings.setValue("settings/dualsense_enabled", enabled); } + ChiakiVideoResolutionPreset GetResolution() const; void SetResolution(ChiakiVideoResolutionPreset resolution); diff --git a/gui/include/settingsdialog.h b/gui/include/settingsdialog.h index d564bef..00af648 100644 --- a/gui/include/settingsdialog.h +++ b/gui/include/settingsdialog.h @@ -20,6 +20,7 @@ class SettingsDialog : public QDialog QCheckBox *log_verbose_check_box; QComboBox *disconnect_action_combo_box; + QCheckBox *dualsense_check_box; QComboBox *resolution_combo_box; QComboBox *fps_combo_box; @@ -37,6 +38,7 @@ class SettingsDialog : public QDialog private slots: void LogVerboseChanged(); + void DualSenseChanged(); void DisconnectActionSelected(); void ResolutionSelected(); diff --git a/gui/include/streamsession.h b/gui/include/streamsession.h index cb1397e..4b0ba01 100644 --- a/gui/include/streamsession.h +++ b/gui/include/streamsession.h @@ -20,6 +20,7 @@ #include "sessionlog.h" #include "controllermanager.h" #include "settings.h" +#include "transformmode.h" #include #include @@ -53,9 +54,18 @@ struct StreamSessionConnectInfo ChiakiConnectVideoProfile video_profile; unsigned int audio_buffer_size; bool fullscreen; + TransformMode transform_mode; bool enable_keyboard; + bool enable_dualsense; - StreamSessionConnectInfo(Settings *settings, ChiakiTarget target, QString host, QByteArray regist_key, QByteArray morning, bool fullscreen); + StreamSessionConnectInfo( + Settings *settings, + ChiakiTarget target, + QString host, + QByteArray regist_key, + QByteArray morning, + bool fullscreen, + TransformMode transform_mode); }; class StreamSession : public QObject @@ -92,17 +102,23 @@ class StreamSession : public QObject unsigned int audio_buffer_size; QAudioOutput *audio_output; QIODevice *audio_io; + SDL_AudioDeviceID haptics_output; + uint8_t *haptics_resampler_buf; QMap key_map; void PushAudioFrame(int16_t *buf, size_t samples_count); + void PushHapticsFrame(uint8_t *buf, size_t buf_size); #if CHIAKI_GUI_ENABLE_SETSU void HandleSetsuEvent(SetsuEvent *event); #endif private slots: void InitAudio(unsigned int channels, unsigned int rate); + void InitHaptics(); void Event(ChiakiEvent *event); + void DisconnectHaptics(); + void ConnectHaptics(); public: explicit StreamSession(const StreamSessionConnectInfo &connect_info, QObject *parent = nullptr); @@ -124,7 +140,7 @@ class StreamSession : public QObject #endif void HandleKeyboardEvent(QKeyEvent *event); - void HandleMouseEvent(QMouseEvent *event); + bool HandleMouseEvent(QMouseEvent *event); signals: void FfmpegFrameAvailable(); diff --git a/gui/include/streamwindow.h b/gui/include/streamwindow.h index 5c526b0..f3bd8bb 100644 --- a/gui/include/streamwindow.h +++ b/gui/include/streamwindow.h @@ -22,10 +22,14 @@ class StreamWindow: public QMainWindow const StreamSessionConnectInfo connect_info; StreamSession *session; + QAction *fullscreen_action; + QAction *stretch_action; + QAction *zoom_action; AVOpenGLWidget *av_widget; void Init(); void UpdateVideoTransform(); + void UpdateTransformModeActions(); protected: void keyPressEvent(QKeyEvent *event) override; @@ -42,6 +46,9 @@ class StreamWindow: public QMainWindow void SessionQuit(ChiakiQuitReason reason, const QString &reason_str); void LoginPINRequested(bool incorrect); void ToggleFullscreen(); + void ToggleStretch(); + void ToggleZoom(); + void Quit(); }; #endif // CHIAKI_GUI_STREAMWINDOW_H diff --git a/gui/include/transformmode.h b/gui/include/transformmode.h new file mode 100644 index 0000000..5c01d87 --- /dev/null +++ b/gui/include/transformmode.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL + +#ifndef CHIAKI_TRANSFORMMODE_H +#define CHIAKI_TRANSFORMMODE_H + +enum class TransformMode { + Fit, + Zoom, + Stretch +}; + +#endif diff --git a/gui/res/chiaki_macos.svg b/gui/res/chiaki_macos.svg new file mode 100644 index 0000000..7f1359d --- /dev/null +++ b/gui/res/chiaki_macos.svg @@ -0,0 +1,102 @@ + + + + diff --git a/gui/res/resources.qrc b/gui/res/resources.qrc index 86efda9..b89bee7 100644 --- a/gui/res/resources.qrc +++ b/gui/res/resources.qrc @@ -5,6 +5,7 @@ discover-24px.svg discover-off-24px.svg chiaki.svg + chiaki_macos.svg console-ps4.svg console-ps5.svg diff --git a/gui/src/avopenglwidget.cpp b/gui/src/avopenglwidget.cpp index 857af09..bfd184a 100644 --- a/gui/src/avopenglwidget.cpp +++ b/gui/src/avopenglwidget.cpp @@ -122,9 +122,9 @@ QSurfaceFormat AVOpenGLWidget::CreateSurfaceFormat() return format; } -AVOpenGLWidget::AVOpenGLWidget(StreamSession *session, QWidget *parent) +AVOpenGLWidget::AVOpenGLWidget(StreamSession *session, QWidget *parent, TransformMode transform_mode) : QOpenGLWidget(parent), - session(session) + session(session), transform_mode(transform_mode) { enum AVPixelFormat pixel_format = chiaki_ffmpeg_decoder_get_pixel_format(session->GetFfmpegDecoder()); conversion_config = nullptr; @@ -381,10 +381,10 @@ void AVOpenGLWidget::paintGL() vp_width = widget_width; vp_height = widget_height; } - else + else if(transform_mode == TransformMode::Fit) { float aspect = (float)frame->width / (float)frame->height; - if(aspect < (float)widget_width / (float)widget_height) + if(widget_height && aspect < (float)widget_width / (float)widget_height) { vp_height = widget_height; vp_width = (GLsizei)(vp_height * aspect); @@ -395,6 +395,34 @@ void AVOpenGLWidget::paintGL() vp_height = (GLsizei)(vp_width / aspect); } } + else if(transform_mode == TransformMode::Zoom) + { + float aspect = (float)frame->width / (float)frame->height; + if(widget_height && aspect < (float)widget_width / (float)widget_height) + { + vp_width = widget_width; + vp_height = (GLsizei)(vp_width / aspect); + } + else + { + vp_height = widget_height; + vp_width = (GLsizei)(vp_height * aspect); + } + } + else // transform_mode == TransformMode::Stretch + { + float aspect = (float)frame->width / (float)frame->height; + if(widget_height && aspect < (float)widget_width / (float)widget_height) + { + vp_height = widget_height; + vp_width = widget_width; + } + else + { + vp_width = widget_width; + vp_height = widget_height; + } + } f->glViewport((widget_width - vp_width) / 2, (widget_height - vp_height) / 2, vp_width, vp_height); diff --git a/gui/src/controllermanager.cpp b/gui/src/controllermanager.cpp index 667b736..8cc501f 100644 --- a/gui/src/controllermanager.cpp +++ b/gui/src/controllermanager.cpp @@ -68,6 +68,12 @@ static QSet chiaki_motion_controller_guids({ "030000008f0e00001431000000000000", }); +static QSet> chiaki_dualsense_controller_ids({ + // in format (vendor id, product id) + QPair(0x054c, 0x0ce6), // DualSense controller + QPair(0x054c, 0x0df2), // DualSense Edge controller +}); + static ControllerManager *instance = nullptr; #define UPDATE_INTERVAL_MS 4 @@ -84,6 +90,15 @@ ControllerManager::ControllerManager(QObject *parent) { #ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER SDL_SetMainReady(); +#ifdef SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); +#endif +#ifdef SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); +#endif +#ifdef SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS + SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); +#endif if(SDL_Init(SDL_INIT_GAMECONTROLLER) < 0) { const char *err = SDL_GetError(); @@ -156,22 +171,51 @@ void ControllerManager::HandleEvents() break; case SDL_CONTROLLERBUTTONUP: case SDL_CONTROLLERBUTTONDOWN: - ControllerEvent(event.cbutton.which); - break; case SDL_CONTROLLERAXISMOTION: - ControllerEvent(event.caxis.which); +#if not defined(CHIAKI_ENABLE_SETSU) and SDL_VERSION_ATLEAST(2, 0, 14) + case SDL_CONTROLLERSENSORUPDATE: + case SDL_CONTROLLERTOUCHPADDOWN: + case SDL_CONTROLLERTOUCHPADMOTION: + case SDL_CONTROLLERTOUCHPADUP: +#endif + ControllerEvent(event); break; } } #endif } -void ControllerManager::ControllerEvent(int device_id) +#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER +void ControllerManager::ControllerEvent(SDL_Event event) { + int device_id; + switch(event.type) + { + case SDL_CONTROLLERBUTTONDOWN: + case SDL_CONTROLLERBUTTONUP: + device_id = event.cbutton.which; + break; + case SDL_CONTROLLERAXISMOTION: + device_id = event.caxis.which; + break; +#if SDL_VERSION_ATLEAST(2, 0, 14) + case SDL_CONTROLLERSENSORUPDATE: + device_id = event.csensor.which; + break; + case SDL_CONTROLLERTOUCHPADDOWN: + case SDL_CONTROLLERTOUCHPADMOTION: + case SDL_CONTROLLERTOUCHPADUP: + device_id = event.ctouchpad.which; + break; +#endif + default: + return; + } if(!open_controllers.contains(device_id)) return; - open_controllers[device_id]->UpdateState(); + open_controllers[device_id]->UpdateState(event); } +#endif QSet ControllerManager::GetAvailableControllers() { @@ -196,10 +240,13 @@ void ControllerManager::ControllerClosed(Controller *controller) open_controllers.remove(controller->GetDeviceID()); } -Controller::Controller(int device_id, ControllerManager *manager) : QObject(manager) +Controller::Controller(int device_id, ControllerManager *manager) + : QObject(manager), is_dualsense(false) { this->id = device_id; this->manager = manager; + chiaki_orientation_tracker_init(&this->orientation_tracker); + chiaki_controller_state_set_idle(&this->state); #ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER controller = nullptr; @@ -208,6 +255,14 @@ Controller::Controller(int device_id, ControllerManager *manager) : QObject(mana if(SDL_JoystickGetDeviceInstanceID(i) == device_id) { controller = SDL_GameControllerOpen(i); +#if SDL_VERSION_ATLEAST(2, 0, 14) + if(SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL)) + SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE); + if(SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO)) + SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE); +#endif + auto controller_id = QPair(SDL_GameControllerGetVendor(controller), SDL_GameControllerGetProduct(controller)); + is_dualsense = chiaki_dualsense_controller_ids.contains(controller_id); break; } } @@ -218,16 +273,197 @@ Controller::~Controller() { #ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER if(controller) + { + // Clear trigger effects, SDL doesn't do it automatically + const uint8_t clear_effect[10] = { 0 }; + this->SetTriggerEffects(0x05, clear_effect, 0x05, clear_effect); SDL_GameControllerClose(controller); + } #endif manager->ControllerClosed(this); } -void Controller::UpdateState() +#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER +void Controller::UpdateState(SDL_Event event) { + switch(event.type) + { + case SDL_CONTROLLERBUTTONDOWN: + case SDL_CONTROLLERBUTTONUP: + if(!HandleButtonEvent(event.cbutton)) + return; + break; + case SDL_CONTROLLERAXISMOTION: + if(!HandleAxisEvent(event.caxis)) + return; + break; +#if SDL_VERSION_ATLEAST(2, 0, 14) + case SDL_CONTROLLERSENSORUPDATE: + if(!HandleSensorEvent(event.csensor)) + return; + break; + case SDL_CONTROLLERTOUCHPADDOWN: + case SDL_CONTROLLERTOUCHPADMOTION: + case SDL_CONTROLLERTOUCHPADUP: + if(!HandleTouchpadEvent(event.ctouchpad)) + return; + break; +#endif + default: + return; + + } emit StateChanged(); } +inline bool Controller::HandleButtonEvent(SDL_ControllerButtonEvent event) { + ChiakiControllerButton ps_btn; + switch(event.button) + { + case SDL_CONTROLLER_BUTTON_A: + ps_btn = CHIAKI_CONTROLLER_BUTTON_CROSS; + break; + case SDL_CONTROLLER_BUTTON_B: + ps_btn = CHIAKI_CONTROLLER_BUTTON_MOON; + break; + case SDL_CONTROLLER_BUTTON_X: + ps_btn = CHIAKI_CONTROLLER_BUTTON_BOX; + break; + case SDL_CONTROLLER_BUTTON_Y: + ps_btn = CHIAKI_CONTROLLER_BUTTON_PYRAMID; + break; + case SDL_CONTROLLER_BUTTON_DPAD_LEFT: + ps_btn = CHIAKI_CONTROLLER_BUTTON_DPAD_LEFT; + break; + case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: + ps_btn = CHIAKI_CONTROLLER_BUTTON_DPAD_RIGHT; + break; + case SDL_CONTROLLER_BUTTON_DPAD_UP: + ps_btn = CHIAKI_CONTROLLER_BUTTON_DPAD_UP; + break; + case SDL_CONTROLLER_BUTTON_DPAD_DOWN: + ps_btn = CHIAKI_CONTROLLER_BUTTON_DPAD_DOWN; + break; + case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: + ps_btn = CHIAKI_CONTROLLER_BUTTON_L1; + break; + case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: + ps_btn = CHIAKI_CONTROLLER_BUTTON_R1; + break; + case SDL_CONTROLLER_BUTTON_LEFTSTICK: + ps_btn = CHIAKI_CONTROLLER_BUTTON_L3; + break; + case SDL_CONTROLLER_BUTTON_RIGHTSTICK: + ps_btn = CHIAKI_CONTROLLER_BUTTON_R3; + break; + case SDL_CONTROLLER_BUTTON_START: + ps_btn = CHIAKI_CONTROLLER_BUTTON_OPTIONS; + break; + case SDL_CONTROLLER_BUTTON_BACK: + ps_btn = CHIAKI_CONTROLLER_BUTTON_SHARE; + break; + case SDL_CONTROLLER_BUTTON_GUIDE: + ps_btn = CHIAKI_CONTROLLER_BUTTON_PS; + break; +#if SDL_VERSION_ATLEAST(2, 0, 14) + case SDL_CONTROLLER_BUTTON_TOUCHPAD: + ps_btn = CHIAKI_CONTROLLER_BUTTON_TOUCHPAD; + break; +#endif + default: + return false; + } + if(event.type == SDL_CONTROLLERBUTTONDOWN) + state.buttons |= ps_btn; + else + state.buttons &= ~ps_btn; + return true; +} + +inline bool Controller::HandleAxisEvent(SDL_ControllerAxisEvent event) { + switch(event.axis) + { + case SDL_CONTROLLER_AXIS_TRIGGERLEFT: + state.l2_state = (uint8_t)(event.value >> 7); + break; + case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: + state.r2_state = (uint8_t)(event.value >> 7); + break; + case SDL_CONTROLLER_AXIS_LEFTX: + state.left_x = event.value; + break; + case SDL_CONTROLLER_AXIS_LEFTY: + state.left_y = event.value; + break; + case SDL_CONTROLLER_AXIS_RIGHTX: + state.right_x = event.value; + break; + case SDL_CONTROLLER_AXIS_RIGHTY: + state.right_y = event.value; + break; + default: + return false; + } + return true; +} + +#if SDL_VERSION_ATLEAST(2, 0, 14) +inline bool Controller::HandleSensorEvent(SDL_ControllerSensorEvent event) +{ + switch(event.sensor) + { + case SDL_SENSOR_ACCEL: + state.accel_x = event.data[0] / SDL_STANDARD_GRAVITY; + state.accel_y = event.data[1] / SDL_STANDARD_GRAVITY; + state.accel_z = event.data[2] / SDL_STANDARD_GRAVITY; + break; + case SDL_SENSOR_GYRO: + state.gyro_x = event.data[0]; + state.gyro_y = event.data[1]; + state.gyro_z = event.data[2]; + break; + default: + return false; + } + chiaki_orientation_tracker_update( + &orientation_tracker, state.gyro_x, state.gyro_y, state.gyro_z, + state.accel_x, state.accel_y, state.accel_z, event.timestamp * 1000); + chiaki_orientation_tracker_apply_to_controller_state(&orientation_tracker, &state); + return true; +} + +inline bool Controller::HandleTouchpadEvent(SDL_ControllerTouchpadEvent event) +{ + auto key = qMakePair(event.touchpad, event.finger); + bool exists = touch_ids.contains(key); + uint8_t chiaki_id; + switch(event.type) + { + case SDL_CONTROLLERTOUCHPADDOWN: + if(touch_ids.size() >= CHIAKI_CONTROLLER_TOUCHES_MAX) + return false; + chiaki_id = chiaki_controller_state_start_touch(&state, event.x * PS_TOUCHPAD_MAX_X, event.y * PS_TOUCHPAD_MAX_Y); + touch_ids.insert(key, chiaki_id); + break; + case SDL_CONTROLLERTOUCHPADMOTION: + if(!exists) + return false; + chiaki_controller_state_set_touch_pos(&state, touch_ids[key], event.x * PS_TOUCHPAD_MAX_X, event.y * PS_TOUCHPAD_MAX_Y); + break; + case SDL_CONTROLLERTOUCHPADUP: + if(!exists) + return false; + chiaki_controller_state_stop_touch(&state, touch_ids[key]); + touch_ids.remove(key); + break; + default: + return false; + } + return true; +} +#endif +#endif + bool Controller::IsConnected() { #ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER @@ -263,35 +499,6 @@ QString Controller::GetName() ChiakiControllerState Controller::GetState() { - ChiakiControllerState state; - chiaki_controller_state_set_idle(&state); -#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER - if(!controller) - return state; - - state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_A) ? CHIAKI_CONTROLLER_BUTTON_CROSS : 0; - state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_B) ? CHIAKI_CONTROLLER_BUTTON_MOON : 0; - state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_X) ? CHIAKI_CONTROLLER_BUTTON_BOX : 0; - state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_Y) ? CHIAKI_CONTROLLER_BUTTON_PYRAMID : 0; - state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_DPAD_LEFT) ? CHIAKI_CONTROLLER_BUTTON_DPAD_LEFT : 0; - state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_DPAD_RIGHT) ? CHIAKI_CONTROLLER_BUTTON_DPAD_RIGHT : 0; - state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_DPAD_UP) ? CHIAKI_CONTROLLER_BUTTON_DPAD_UP : 0; - state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_DPAD_DOWN) ? CHIAKI_CONTROLLER_BUTTON_DPAD_DOWN : 0; - state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_LEFTSHOULDER) ? CHIAKI_CONTROLLER_BUTTON_L1 : 0; - state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER) ? CHIAKI_CONTROLLER_BUTTON_R1 : 0; - state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_LEFTSTICK) ? CHIAKI_CONTROLLER_BUTTON_L3 : 0; - state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_RIGHTSTICK) ? CHIAKI_CONTROLLER_BUTTON_R3 : 0; - state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_START) ? CHIAKI_CONTROLLER_BUTTON_OPTIONS : 0; - state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_BACK) ? CHIAKI_CONTROLLER_BUTTON_SHARE : 0; - state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_GUIDE) ? CHIAKI_CONTROLLER_BUTTON_PS : 0; - state.l2_state = (uint8_t)(SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_TRIGGERLEFT) >> 7); - state.r2_state = (uint8_t)(SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT) >> 7); - state.left_x = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_LEFTX); - state.left_y = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_LEFTY); - state.right_x = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX); - state.right_y = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY); - -#endif return state; } @@ -303,3 +510,28 @@ void Controller::SetRumble(uint8_t left, uint8_t right) SDL_GameControllerRumble(controller, (uint16_t)left << 8, (uint16_t)right << 8, 5000); #endif } + +void Controller::SetTriggerEffects(uint8_t type_left, const uint8_t *data_left, uint8_t type_right, const uint8_t *data_right) +{ +#if defined(CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER) && SDL_VERSION_ATLEAST(2, 0, 16) + if(!is_dualsense || !controller) + return; + DS5EffectsState_t state; + SDL_zero(state); + state.ucEnableBits1 |= (0x04 /* left trigger */ | 0x08 /* right trigger */); + state.rgucLeftTriggerEffect[0] = type_left; + SDL_memcpy(state.rgucLeftTriggerEffect + 1, data_left, 10); + state.rgucRightTriggerEffect[0] = type_right; + SDL_memcpy(state.rgucRightTriggerEffect + 1, data_right, 10); + SDL_GameControllerSendEffect(controller, &state, sizeof(state)); +#endif +} + +bool Controller::IsDualSense() +{ +#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER + if(!controller) + return false; + return is_dualsense; +#endif +} diff --git a/gui/src/main.cpp b/gui/src/main.cpp index 010e744..5f2aad7 100644 --- a/gui/src/main.cpp +++ b/gui/src/main.cpp @@ -71,7 +71,11 @@ int real_main(int argc, char *argv[]) QApplication app(argc, argv); +#ifdef Q_OS_MACOS + QApplication::setWindowIcon(QIcon(":/icons/chiaki_macos.svg")); +#else QApplication::setWindowIcon(QIcon(":/icons/chiaki.svg")); +#endif QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); @@ -99,9 +103,15 @@ int real_main(int argc, char *argv[]) QCommandLineOption morning_option("morning", "", "morning"); parser.addOption(morning_option); - QCommandLineOption fullscreen_option("fullscreen", "Start window in fullscreen (only for use with stream command)"); + QCommandLineOption fullscreen_option("fullscreen", "Start window in fullscreen mode [maintains aspect ratio, adds black bars to fill unsused parts of screen if applicable] (only for use with stream command)"); parser.addOption(fullscreen_option); + QCommandLineOption zoom_option("zoom", "Start window in fullscreen zoomed in to fit screen [maintains aspect ratio, cutting off edges of image to fill screen] (only for use with stream command)"); + parser.addOption(zoom_option); + + QCommandLineOption stretch_option("stretch", "Start window in fullscreen stretched to fit screen [distorts aspect ratio to fill screen] (only for use with stream command)"); + parser.addOption(stretch_option); + parser.process(app); QStringList args = parser.positionalArguments(); @@ -129,15 +139,21 @@ int real_main(int argc, char *argv[]) { if(args.length() < 3) parser.showHelp(1); + + bool found = false; for(const auto &temphost : settings.GetRegisteredHosts()) { if(temphost.GetServerNickname() == args[1]) { + found = true; morning = temphost.GetRPKey(); regist_key = temphost.GetRPRegistKey(); target = temphost.GetTarget(); break; } + } + if(!found) + { printf("No configuration found for '%s'\n", args[1].toLocal8Bit().constData()); return 1; } @@ -164,7 +180,21 @@ int real_main(int argc, char *argv[]) return 1; } } - StreamSessionConnectInfo connect_info(&settings, target, host, regist_key, morning, parser.isSet(fullscreen_option)); + if ((parser.isSet(stretch_option) && (parser.isSet(zoom_option) || parser.isSet(fullscreen_option))) || (parser.isSet(zoom_option) && parser.isSet(fullscreen_option))) + { + printf("Must choose between fullscreen, zoom or stretch option."); + return 1; + } + + StreamSessionConnectInfo connect_info( + &settings, + target, + host, + regist_key, + morning, + parser.isSet(fullscreen_option), + parser.isSet(zoom_option) ? TransformMode::Zoom : parser.isSet(stretch_option) ? TransformMode::Stretch : TransformMode::Fit); + return RunStream(app, connect_info); } #ifdef CHIAKI_ENABLE_CLI diff --git a/gui/src/mainwindow.cpp b/gui/src/mainwindow.cpp index aa76ab3..0912b2e 100644 --- a/gui/src/mainwindow.cpp +++ b/gui/src/mainwindow.cpp @@ -146,6 +146,11 @@ MainWindow::MainWindow(Settings *settings, QWidget *parent) AddToolBarAction(settings_action); connect(settings_action, &QAction::triggered, this, &MainWindow::ShowSettings); + auto quit_action = new QAction(tr("Quit"), this); + quit_action->setShortcut(Qt::CTRL + Qt::Key_Q); + addAction(quit_action); + connect(quit_action, &QAction::triggered, this, &MainWindow::Quit); + auto scroll_area = new QScrollArea(this); scroll_area->setWidgetResizable(true); scroll_area->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); @@ -249,7 +254,14 @@ void MainWindow::ServerItemWidgetTriggered() } QString host = server.GetHostAddr(); - StreamSessionConnectInfo info(settings, server.registered_host.GetTarget(), host, server.registered_host.GetRPRegistKey(), server.registered_host.GetRPKey(), false); + StreamSessionConnectInfo info( + settings, + server.registered_host.GetTarget(), + host, + server.registered_host.GetRPRegistKey(), + server.registered_host.GetRPKey(), + false, + TransformMode::Fit); new StreamWindow(info); } else @@ -298,6 +310,11 @@ void MainWindow::ShowSettings() dialog.exec(); } +void MainWindow::Quit() +{ + qApp->exit(); +} + void MainWindow::UpdateDisplayServers() { display_servers.clear(); diff --git a/gui/src/settingsdialog.cpp b/gui/src/settingsdialog.cpp index 3f8856f..1770932 100644 --- a/gui/src/settingsdialog.cpp +++ b/gui/src/settingsdialog.cpp @@ -69,6 +69,11 @@ SettingsDialog::SettingsDialog(Settings *settings, QWidget *parent) : QDialog(pa log_verbose_check_box->setChecked(settings->GetLogVerbose()); connect(log_verbose_check_box, &QCheckBox::stateChanged, this, &SettingsDialog::LogVerboseChanged); + dualsense_check_box = new QCheckBox(this); + general_layout->addRow(tr("Extended DualSense Support:\nEnable haptics and adaptive triggers\nfor attached DualSense controllers.\nThis is currently experimental."), dualsense_check_box); + dualsense_check_box->setChecked(settings->GetDualSenseEnabled()); + connect(dualsense_check_box, &QCheckBox::stateChanged, this, &SettingsDialog::DualSenseChanged); + auto log_directory_label = new QLineEdit(GetLogBaseDir(), this); log_directory_label->setReadOnly(true); general_layout->addRow(tr("Log Directory:"), log_directory_label); @@ -322,6 +327,11 @@ void SettingsDialog::LogVerboseChanged() settings->SetLogVerbose(log_verbose_check_box->isChecked()); } +void SettingsDialog::DualSenseChanged() +{ + settings->SetDualSenseEnabled(dualsense_check_box->isChecked()); +} + void SettingsDialog::FPSSelected() { settings->SetFPS((ChiakiVideoFPSPreset)fps_combo_box->currentData().toInt()); diff --git a/gui/src/streamsession.cpp b/gui/src/streamsession.cpp index 3f46070..42c9959 100644 --- a/gui/src/streamsession.cpp +++ b/gui/src/streamsession.cpp @@ -14,7 +14,20 @@ #define SETSU_UPDATE_INTERVAL_MS 4 -StreamSessionConnectInfo::StreamSessionConnectInfo(Settings *settings, ChiakiTarget target, QString host, QByteArray regist_key, QByteArray morning, bool fullscreen) +#ifdef Q_OS_LINUX +#define DUALSENSE_AUDIO_DEVICE_NEEDLE "DualSense" +#else +#define DUALSENSE_AUDIO_DEVICE_NEEDLE "Wireless Controller" +#endif + +StreamSessionConnectInfo::StreamSessionConnectInfo( + Settings *settings, + ChiakiTarget target, + QString host, + QByteArray regist_key, + QByteArray morning, + bool fullscreen, + TransformMode transform_mode) : settings(settings) { key_map = settings->GetControllerMappingForDecoding(); @@ -30,11 +43,14 @@ StreamSessionConnectInfo::StreamSessionConnectInfo(Settings *settings, ChiakiTar this->morning = morning; audio_buffer_size = settings->GetAudioBufferSize(); this->fullscreen = fullscreen; + this->transform_mode = transform_mode; this->enable_keyboard = false; // TODO: from settings + this->enable_dualsense = settings->GetDualSenseEnabled(); } static void AudioSettingsCb(uint32_t channels, uint32_t rate, void *user); static void AudioFrameCb(int16_t *buf, size_t samples_count, void *user); +static void HapticsFrameCb(uint8_t *buf, size_t buf_size, void *user); static void EventCb(ChiakiEvent *event, void *user); #if CHIAKI_GUI_ENABLE_SETSU static void SessionSetsuCb(SetsuEvent *event, void *user); @@ -49,7 +65,9 @@ StreamSession::StreamSession(const StreamSessionConnectInfo &connect_info, QObje pi_decoder(nullptr), #endif audio_output(nullptr), - audio_io(nullptr) + audio_io(nullptr), + haptics_output(0), + haptics_resampler_buf(nullptr) { connected = false; ChiakiErrorCode err; @@ -108,6 +126,7 @@ StreamSession::StreamSession(const StreamSessionConnectInfo &connect_info, QObje chiaki_connect_info.video_profile = connect_info.video_profile; chiaki_connect_info.video_profile_auto_downgrade = true; chiaki_connect_info.enable_keyboard = false; + chiaki_connect_info.enable_dualsense = connect_info.enable_dualsense; #if CHIAKI_LIB_ENABLE_PI_DECODER if(connect_info.decoder == Decoder::Pi && chiaki_connect_info.video_profile.codec != CHIAKI_CODEC_H264) @@ -136,6 +155,14 @@ StreamSession::StreamSession(const StreamSessionConnectInfo &connect_info, QObje chiaki_opus_decoder_get_sink(&opus_decoder, &audio_sink); chiaki_session_set_audio_sink(&session, &audio_sink); + if(connect_info.enable_dualsense) + { + ChiakiAudioSink haptics_sink; + haptics_sink.user = this; + haptics_sink.frame_cb = HapticsFrameCb; + chiaki_session_set_haptics_sink(&session, &haptics_sink); + } + #if CHIAKI_LIB_ENABLE_PI_DECODER if(pi_decoder) chiaki_session_set_video_sample_cb(&session, chiaki_pi_decoder_video_sample_cb, pi_decoder); @@ -173,6 +200,10 @@ StreamSession::StreamSession(const StreamSessionConnectInfo &connect_info, QObje #endif key_map = connect_info.key_map; + if(connect_info.enable_dualsense) + { + InitHaptics(); + } UpdateGamepads(); } @@ -200,6 +231,16 @@ StreamSession::~StreamSession() chiaki_ffmpeg_decoder_fini(ffmpeg_decoder); delete ffmpeg_decoder; } + if(haptics_output > 0) + { + SDL_CloseAudioDevice(haptics_output); + haptics_output = 0; + } + if(haptics_resampler_buf) + { + free(haptics_resampler_buf); + haptics_resampler_buf = nullptr; + } } void StreamSession::Start() @@ -228,13 +269,16 @@ void StreamSession::SetLoginPIN(const QString &pin) chiaki_session_set_login_pin(&session, (const uint8_t *)data.constData(), data.size()); } -void StreamSession::HandleMouseEvent(QMouseEvent *event) +bool StreamSession::HandleMouseEvent(QMouseEvent *event) { + if(event->button() != Qt::MouseButton::LeftButton) + return false; if(event->type() == QEvent::MouseButtonPress) keyboard_state.buttons |= CHIAKI_CONTROLLER_BUTTON_TOUCHPAD; else keyboard_state.buttons &= ~CHIAKI_CONTROLLER_BUTTON_TOUCHPAD; SendFeedbackState(); + return true; } void StreamSession::HandleKeyboardEvent(QKeyEvent *event) @@ -301,6 +345,8 @@ void StreamSession::UpdateGamepads() { CHIAKI_LOGI(log.GetChiakiLog(), "Controller %d disconnected", controller->GetDeviceID()); controllers.remove(controller_id); + if(controller->IsDualSense()) + DisconnectHaptics(); delete controller; } } @@ -319,6 +365,11 @@ void StreamSession::UpdateGamepads() CHIAKI_LOGI(log.GetChiakiLog(), "Controller %d opened: \"%s\"", controller_id, controller->GetName().toLocal8Bit().constData()); connect(controller, &Controller::StateChanged, this, &StreamSession::SendFeedbackState); controllers[controller_id] = controller; + if(controller->IsDualSense()) + { + // Connect haptics audio device with a delay to give the sound system time to set up + QTimer::singleShot(1000, this, &StreamSession::ConnectHaptics); + } } } @@ -377,6 +428,82 @@ void StreamSession::InitAudio(unsigned int channels, unsigned int rate) channels, rate, audio_output->bufferSize()); } +void StreamSession::InitHaptics() +{ + haptics_output = 0; + haptics_resampler_buf = nullptr; +#ifdef Q_OS_LINUX + // Haptics work most reliably with Pipewire, so try to use that if available + SDL_SetHint("SDL_AUDIODRIVER", "pipewire"); +#endif + + if(SDL_Init(SDL_INIT_AUDIO) < 0) + { + CHIAKI_LOGE(log.GetChiakiLog(), "Could not initialize SDL Audio for haptics output: %s", SDL_GetError()); + return; + } + +#ifdef Q_OS_LINUX + if(!strstr(SDL_GetCurrentAudioDriver(), "pipewire")) + { + CHIAKI_LOGW( + log.GetChiakiLog(), + "Haptics output is not using Pipewire, this may not work reliably. (was: '%s')", + SDL_GetCurrentAudioDriver()); + } +#endif + + SDL_AudioCVT cvt; + SDL_BuildAudioCVT(&cvt, AUDIO_S16LSB, 4, 3000, AUDIO_S16LSB, 4, 48000); + cvt.len = 240; // 10 16bit stereo samples + haptics_resampler_buf = (uint8_t*) calloc(cvt.len * cvt.len_mult, sizeof(uint8_t)); +} + +void StreamSession::DisconnectHaptics() +{ + if(this->haptics_output > 0) + { + SDL_CloseAudioDevice(haptics_output); + this->haptics_output = 0; + } +} + +void StreamSession::ConnectHaptics() +{ + if(this->haptics_output > 0) + { + CHIAKI_LOGW(this->log.GetChiakiLog(), "Haptics already connected to an attached DualSense controller, ignoring additional controllers."); + return; + } + + SDL_AudioSpec want, have; + SDL_zero(want); + want.freq = 48000; + want.format = AUDIO_S16LSB; + want.channels = 4; + want.samples = 480; // 10ms buffer + want.callback = NULL; + + const char *device_name = nullptr; + for(int i=0; i < SDL_GetNumAudioDevices(0); i++) + { + device_name = SDL_GetAudioDeviceName(i, 0); + if(!device_name || !strstr(device_name, DUALSENSE_AUDIO_DEVICE_NEEDLE)) + continue; + haptics_output = SDL_OpenAudioDevice(device_name, 0, &want, &have, 0); + if(haptics_output == 0) + { + CHIAKI_LOGE(log.GetChiakiLog(), "Could not open SDL Audio Device %s for haptics output: %s", device_name, SDL_GetError()); + continue; + } + SDL_PauseAudioDevice(haptics_output, 0); + CHIAKI_LOGI(log.GetChiakiLog(), "Haptics Audio Device '%s' opened with %d channels @ %d Hz, buffer size %u (driver=%s)", device_name, have.channels, have.freq, have.size, SDL_GetCurrentAudioDriver()); + return; + } + CHIAKI_LOGW(log.GetChiakiLog(), "DualSense features were enabled and a DualSense is connected, but could not find the DualSense audio device!"); + return; +} + void StreamSession::PushAudioFrame(int16_t *buf, size_t samples_count) { if(!audio_io) @@ -384,6 +511,35 @@ void StreamSession::PushAudioFrame(int16_t *buf, size_t samples_count) audio_io->write((const char *)buf, static_cast(samples_count * 2 * 2)); } +void StreamSession::PushHapticsFrame(uint8_t *buf, size_t buf_size) +{ + if(haptics_output == 0) + return; + SDL_AudioCVT cvt; + // Haptics samples are coming in at 3KHZ, but the DualSense expects 48KHZ + SDL_BuildAudioCVT(&cvt, AUDIO_S16LSB, 4, 3000, AUDIO_S16LSB, 4, 48000); + cvt.len = buf_size * 2; + cvt.buf = haptics_resampler_buf; + // Remix to 4 channels + for (int i=0; i < buf_size; i+=4) + { + SDL_memset(haptics_resampler_buf + i * 2, 0, 4); + SDL_memcpy(haptics_resampler_buf + (i * 2) + 4, buf + i, 4); + } + // Resample to 48kHZ + if(SDL_ConvertAudio(&cvt) != 0) + { + CHIAKI_LOGE(log.GetChiakiLog(), "Failed to resample haptics audio: %s", SDL_GetError()); + return; + } + + if(SDL_QueueAudio(haptics_output, cvt.buf, cvt.len_cvt) < 0) + { + CHIAKI_LOGE(log.GetChiakiLog(), "Failed to submit haptics audio to device: %s", SDL_GetError()); + return; + } +} + void StreamSession::Event(ChiakiEvent *event) { switch(event->type) @@ -407,6 +563,19 @@ void StreamSession::Event(ChiakiEvent *event) }); break; } + case CHIAKI_EVENT_TRIGGER_EFFECTS: { + uint8_t type_left = event->trigger_effects.type_left; + uint8_t data_left[10]; + memcpy(data_left, event->trigger_effects.left, 10); + uint8_t data_right[10]; + memcpy(data_right, event->trigger_effects.right, 10); + uint8_t type_right = event->trigger_effects.type_right; + QMetaObject::invokeMethod(this, [this, type_left, data_left, type_right, data_right]() { + for(auto controller : controllers) + controller->SetTriggerEffects(type_left, data_left, type_right, data_right); + }); + break; + } default: break; } @@ -533,6 +702,7 @@ class StreamSessionPrivate } static void PushAudioFrame(StreamSession *session, int16_t *buf, size_t samples_count) { session->PushAudioFrame(buf, samples_count); } + static void PushHapticsFrame(StreamSession *session, uint8_t *buf, size_t buf_size) { session->PushHapticsFrame(buf, buf_size); } static void Event(StreamSession *session, ChiakiEvent *event) { session->Event(event); } #if CHIAKI_GUI_ENABLE_SETSU static void HandleSetsuEvent(StreamSession *session, SetsuEvent *event) { session->HandleSetsuEvent(event); } @@ -552,6 +722,12 @@ static void AudioFrameCb(int16_t *buf, size_t samples_count, void *user) StreamSessionPrivate::PushAudioFrame(session, buf, samples_count); } +static void HapticsFrameCb(uint8_t *buf, size_t buf_size, void *user) +{ + auto session = reinterpret_cast(user); + StreamSessionPrivate::PushHapticsFrame(session, buf, buf_size); +} + static void EventCb(ChiakiEvent *event, void *user) { auto session = reinterpret_cast(user); diff --git a/gui/src/streamwindow.cpp b/gui/src/streamwindow.cpp index feff758..a2337d7 100644 --- a/gui/src/streamwindow.cpp +++ b/gui/src/streamwindow.cpp @@ -10,6 +10,7 @@ #include #include #include +#include StreamWindow::StreamWindow(const StreamSessionConnectInfo &connect_info, QWidget *parent) : QMainWindow(parent), @@ -23,8 +24,6 @@ StreamWindow::StreamWindow(const StreamSessionConnectInfo &connect_info, QWidget try { - if(connect_info.fullscreen) - showFullScreen(); Init(); } catch(const Exception &e) @@ -40,6 +39,8 @@ StreamWindow::~StreamWindow() delete av_widget; } +#include + void StreamWindow::Init() { session = new StreamSession(connect_info, this); @@ -47,10 +48,36 @@ void StreamWindow::Init() connect(session, &StreamSession::SessionQuit, this, &StreamWindow::SessionQuit); connect(session, &StreamSession::LoginPINRequested, this, &StreamWindow::LoginPINRequested); + const QKeySequence fullscreen_shortcut = Qt::Key_F11; + const QKeySequence stretch_shortcut = Qt::CTRL + Qt::Key_S; + const QKeySequence zoom_shortcut = Qt::CTRL + Qt::Key_Z; + + fullscreen_action = new QAction(tr("Fullscreen"), this); + fullscreen_action->setCheckable(true); + fullscreen_action->setShortcut(fullscreen_shortcut); + addAction(fullscreen_action); + connect(fullscreen_action, &QAction::triggered, this, &StreamWindow::ToggleFullscreen); + if(session->GetFfmpegDecoder()) { - av_widget = new AVOpenGLWidget(session, this); + av_widget = new AVOpenGLWidget(session, this, connect_info.transform_mode); setCentralWidget(av_widget); + + av_widget->setContextMenuPolicy(Qt::CustomContextMenu); + connect(av_widget, &QWidget::customContextMenuRequested, this, [this](const QPoint &pos) { + av_widget->ResetMouseTimeout(); + + QMenu menu(av_widget); + menu.addAction(fullscreen_action); + menu.addSeparator(); + menu.addAction(stretch_action); + menu.addAction(zoom_action); + releaseKeyboard(); + connect(&menu, &QMenu::aboutToHide, this, [this] { + grabKeyboard(); + }); + menu.exec(av_widget->mapToGlobal(pos)); + }); } else { @@ -63,13 +90,34 @@ void StreamWindow::Init() session->Start(); - auto fullscreen_action = new QAction(tr("Fullscreen"), this); - fullscreen_action->setShortcut(Qt::Key_F11); - addAction(fullscreen_action); - connect(fullscreen_action, &QAction::triggered, this, &StreamWindow::ToggleFullscreen); + stretch_action = new QAction(tr("Stretch"), this); + stretch_action->setCheckable(true); + stretch_action->setShortcut(stretch_shortcut); + addAction(stretch_action); + connect(stretch_action, &QAction::triggered, this, &StreamWindow::ToggleStretch); + + zoom_action = new QAction(tr("Zoom"), this); + zoom_action->setCheckable(true); + zoom_action->setShortcut(zoom_shortcut); + addAction(zoom_action); + connect(zoom_action, &QAction::triggered, this, &StreamWindow::ToggleZoom); + + auto quit_action = new QAction(tr("Quit"), this); + quit_action->setShortcut(Qt::CTRL + Qt::Key_Q); + addAction(quit_action); + connect(quit_action, &QAction::triggered, this, &StreamWindow::Quit); resize(connect_info.video_profile.width, connect_info.video_profile.height); - show(); + + if(connect_info.fullscreen) + { + showFullScreen(); + fullscreen_action->setChecked(true); + } + else + show(); + + UpdateTransformModeActions(); } void StreamWindow::keyPressEvent(QKeyEvent *event) @@ -84,22 +132,32 @@ void StreamWindow::keyReleaseEvent(QKeyEvent *event) session->HandleKeyboardEvent(event); } +void StreamWindow::Quit() +{ + close(); +} + void StreamWindow::mousePressEvent(QMouseEvent *event) { - if(session) - session->HandleMouseEvent(event); + if(session && session->HandleMouseEvent(event)) + return; + QMainWindow::mousePressEvent(event); } void StreamWindow::mouseReleaseEvent(QMouseEvent *event) { - if(session) - session->HandleMouseEvent(event); + if(session && session->HandleMouseEvent(event)) + return; + QMainWindow::mouseReleaseEvent(event); } void StreamWindow::mouseDoubleClickEvent(QMouseEvent *event) { - ToggleFullscreen(); - + if(event->button() == Qt::MouseButton::LeftButton) + { + ToggleFullscreen(); + return; + } QMainWindow::mouseDoubleClickEvent(event); } @@ -143,7 +201,7 @@ void StreamWindow::closeEvent(QCloseEvent *event) void StreamWindow::SessionQuit(ChiakiQuitReason reason, const QString &reason_str) { - if(reason != CHIAKI_QUIT_REASON_STOPPED) + if(chiaki_quit_reason_is_error(reason)) { QString m = tr("Chiaki Session has quit") + ":\n" + chiaki_quit_reason_string(reason); if(!reason_str.isEmpty()) @@ -175,15 +233,48 @@ void StreamWindow::LoginPINRequested(bool incorrect) void StreamWindow::ToggleFullscreen() { if(isFullScreen()) + { showNormal(); + fullscreen_action->setChecked(false); + } else { showFullScreen(); if(av_widget) av_widget->HideMouse(); + fullscreen_action->setChecked(true); } } +void StreamWindow::UpdateTransformModeActions() +{ + TransformMode tm = av_widget ? av_widget->GetTransformMode() : TransformMode::Fit; + stretch_action->setChecked(tm == TransformMode::Stretch); + zoom_action->setChecked(tm == TransformMode::Zoom); +} + +void StreamWindow::ToggleStretch() +{ + if(!av_widget) + return; + av_widget->SetTransformMode( + av_widget->GetTransformMode() == TransformMode::Stretch + ? TransformMode::Fit + : TransformMode::Stretch); + UpdateTransformModeActions(); +} + +void StreamWindow::ToggleZoom() +{ + if(!av_widget) + return; + av_widget->SetTransformMode( + av_widget->GetTransformMode() == TransformMode::Zoom + ? TransformMode::Fit + : TransformMode::Zoom); + UpdateTransformModeActions(); +} + void StreamWindow::resizeEvent(QResizeEvent *event) { UpdateVideoTransform(); diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 36ac38c..cbfd6b0 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -72,7 +72,7 @@ set(SOURCE_FILES src/controller.c src/takionsendbuffer.c src/time.c - src/fec + src/fec.c src/regist.c src/opusdecoder.c src/orientation.c) @@ -119,11 +119,15 @@ find_package(Threads REQUIRED) target_link_libraries(chiaki-lib Threads::Threads) if(CHIAKI_LIB_ENABLE_MBEDTLS) - # provided by mbedtls-static (mbedtls-devel) - find_library(MBEDTLS mbedtls) - find_library(MBEDX509 mbedx509) - find_library(MBEDCRYPTO mbedcrypto) - target_link_libraries(chiaki-lib ${MBEDTLS} ${MBEDX509} ${MBEDCRYPTO}) + if(CHIAKI_LIB_MBEDTLS_EXTERNAL_PROJECT) + target_link_libraries(chiaki-lib mbedtls mbedx509 mbedcrypto) + else() + # provided by mbedtls-static (mbedtls-devel) + find_library(MBEDTLS mbedtls) + find_library(MBEDX509 mbedx509) + find_library(MBEDCRYPTO mbedcrypto) + target_link_libraries(chiaki-lib ${MBEDTLS} ${MBEDX509} ${MBEDCRYPTO}) + endif() elseif(CHIAKI_LIB_OPENSSL_EXTERNAL_PROJECT) target_link_libraries(chiaki-lib OpenSSL_Crypto) else() diff --git a/lib/include/chiaki/session.h b/lib/include/chiaki/session.h index 3a60417..5c355ad 100644 --- a/lib/include/chiaki/session.h +++ b/lib/include/chiaki/session.h @@ -78,6 +78,7 @@ typedef struct chiaki_connect_info_t ChiakiConnectVideoProfile video_profile; bool video_profile_auto_downgrade; // Downgrade video_profile if server does not seem to support it. bool enable_keyboard; + bool enable_dualsense; } ChiakiConnectInfo; @@ -93,11 +94,17 @@ typedef enum { CHIAKI_QUIT_REASON_CTRL_CONNECT_FAILED, CHIAKI_QUIT_REASON_CTRL_CONNECTION_REFUSED, CHIAKI_QUIT_REASON_STREAM_CONNECTION_UNKNOWN, - CHIAKI_QUIT_REASON_STREAM_CONNECTION_REMOTE_DISCONNECTED + CHIAKI_QUIT_REASON_STREAM_CONNECTION_REMOTE_DISCONNECTED, + CHIAKI_QUIT_REASON_STREAM_CONNECTION_REMOTE_SHUTDOWN, // like REMOTE_DISCONNECTED, but because the server shut down } ChiakiQuitReason; CHIAKI_EXPORT const char *chiaki_quit_reason_string(ChiakiQuitReason reason); +static inline bool chiaki_quit_reason_is_error(ChiakiQuitReason reason) +{ + return reason != CHIAKI_QUIT_REASON_STOPPED && reason != CHIAKI_QUIT_REASON_STREAM_CONNECTION_REMOTE_SHUTDOWN; +} + typedef struct chiaki_quit_event_t { ChiakiQuitReason reason; @@ -121,6 +128,14 @@ typedef struct chiaki_rumble_event_t uint8_t right; // high-frequency } ChiakiRumbleEvent; +typedef struct chiaki_trigger_effects_event_t +{ + uint8_t type_left; + uint8_t type_right; + uint8_t left[10]; + uint8_t right[10]; +} ChiakiTriggerEffectsEvent; + typedef enum { CHIAKI_EVENT_CONNECTED, CHIAKI_EVENT_LOGIN_PIN_REQUEST, @@ -129,6 +144,7 @@ typedef enum { CHIAKI_EVENT_KEYBOARD_REMOTE_CLOSE, CHIAKI_EVENT_RUMBLE, CHIAKI_EVENT_QUIT, + CHIAKI_EVENT_TRIGGER_EFFECTS, } ChiakiEventType; typedef struct chiaki_event_t @@ -139,6 +155,7 @@ typedef struct chiaki_event_t ChiakiQuitEvent quit; ChiakiKeyboardEvent keyboard; ChiakiRumbleEvent rumble; + ChiakiTriggerEffectsEvent trigger_effects; struct { bool pin_incorrect; // false on first request, true if the pin entered before was incorrect @@ -170,6 +187,7 @@ typedef struct chiaki_session_t ChiakiConnectVideoProfile video_profile; bool video_profile_auto_downgrade; bool enable_keyboard; + bool enable_dualsense; } connect_info; ChiakiTarget target; @@ -191,6 +209,7 @@ typedef struct chiaki_session_t ChiakiVideoSampleCallback video_sample_cb; void *video_sample_cb_user; ChiakiAudioSink audio_sink; + ChiakiAudioSink haptics_sink; ChiakiThread session_thread; @@ -246,6 +265,14 @@ static inline void chiaki_session_set_audio_sink(ChiakiSession *session, ChiakiA session->audio_sink = *sink; } +/** + * @param sink contents are copied + */ +static inline void chiaki_session_set_haptics_sink(ChiakiSession *session, ChiakiAudioSink *sink) +{ + session->haptics_sink = *sink; +} + #ifdef __cplusplus } #endif diff --git a/lib/include/chiaki/streamconnection.h b/lib/include/chiaki/streamconnection.h index ba1817a..90578c6 100644 --- a/lib/include/chiaki/streamconnection.h +++ b/lib/include/chiaki/streamconnection.h @@ -32,6 +32,7 @@ typedef struct chiaki_stream_connection_t ChiakiPacketStats packet_stats; ChiakiAudioReceiver *audio_receiver; ChiakiVideoReceiver *video_receiver; + ChiakiAudioReceiver *haptics_receiver; ChiakiFeedbackSender feedback_sender; /** diff --git a/lib/include/chiaki/takion.h b/lib/include/chiaki/takion.h index 1715842..15d62e5 100644 --- a/lib/include/chiaki/takion.h +++ b/lib/include/chiaki/takion.h @@ -27,7 +27,8 @@ extern "C" { typedef enum chiaki_takion_message_data_type_t { CHIAKI_TAKION_MESSAGE_DATA_TYPE_PROTOBUF = 0, CHIAKI_TAKION_MESSAGE_DATA_TYPE_RUMBLE = 7, - CHIAKI_TAKION_MESSAGE_DATA_TYPE_9 = 9 + CHIAKI_TAKION_MESSAGE_DATA_TYPE_9 = 9, + CHIAKI_TAKION_MESSAGE_DATA_TYPE_TRIGGER_EFFECTS = 11, } ChiakiTakionMessageDataType; typedef struct chiaki_takion_av_packet_t @@ -36,6 +37,7 @@ typedef struct chiaki_takion_av_packet_t ChiakiSeqNum16 frame_index; bool uses_nalu_info_structs; bool is_video; + bool is_haptics; ChiakiSeqNum16 unit_index; uint16_t units_in_frame_total; // source + units_in_frame_fec uint16_t units_in_frame_fec; @@ -46,8 +48,6 @@ typedef struct chiaki_takion_av_packet_t uint64_t key_pos; - uint8_t byte_before_audio_data; - uint8_t *data; // not owned size_t data_size; } ChiakiTakionAVPacket; @@ -106,6 +106,7 @@ typedef struct chiaki_takion_connect_info_t ChiakiTakionCallback cb; void *cb_user; bool enable_crypt; + bool enable_dualsense; uint8_t protocol_version; } ChiakiTakionConnectInfo; @@ -162,6 +163,8 @@ typedef struct chiaki_takion_t ChiakiTakionAVPacketParse av_packet_parse; ChiakiKeyState key_state; + + bool enable_dualsense; } ChiakiTakion; diff --git a/lib/protobuf/CMakeLists.txt b/lib/protobuf/CMakeLists.txt index 6efa342..43fde68 100644 --- a/lib/protobuf/CMakeLists.txt +++ b/lib/protobuf/CMakeLists.txt @@ -11,8 +11,16 @@ add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/takion.pb" set(SOURCE_FILES "${CMAKE_CURRENT_BINARY_DIR}/takion.pb.c") set(HEADER_FILES "${CMAKE_CURRENT_BINARY_DIR}/takion.pb.h") +if(UNIX AND IS_ABSOLUTE "${PROTOC}") + # make sure protoc is in PATH when invoking the generator below, which needs it. + get_filename_component(PROTOC_PATH "${PROTOC}" DIRECTORY) + set(GEN_PREFIX "${CMAKE_COMMAND}" -E env "PATH=${PROTOC_PATH}:$ENV{PATH}") +else() + set(GEN_PREFIX "") +endif() + add_custom_command(OUTPUT ${SOURCE_FILES} ${HEADER_FILES} - COMMAND "${PYTHON_EXECUTABLE}" "${NANOPB_GENERATOR_PY}" "${CMAKE_CURRENT_BINARY_DIR}/takion.pb" + COMMAND ${GEN_PREFIX} "${PYTHON_EXECUTABLE}" "${NANOPB_GENERATOR_PY}" "${CMAKE_CURRENT_BINARY_DIR}/takion.pb" MAIN_DEPENDENCY "${CMAKE_CURRENT_BINARY_DIR}/takion.pb") set(CHIAKI_LIB_PROTO_SOURCE_FILES "${SOURCE_FILES}" PARENT_SCOPE) diff --git a/lib/protobuf/takion.proto b/lib/protobuf/takion.proto index 748d70a..ceef0f1 100644 --- a/lib/protobuf/takion.proto +++ b/lib/protobuf/takion.proto @@ -312,7 +312,8 @@ message ControllerConnectionPayload { VITA = 3; XINPUT = 4; MOBILE = 5; - BOND = 6; + DUALSENSE = 6; + VR2SENSE = 7; } } diff --git a/lib/src/audioreceiver.c b/lib/src/audioreceiver.c index 9fec69d..744d807 100644 --- a/lib/src/audioreceiver.c +++ b/lib/src/audioreceiver.c @@ -5,7 +5,7 @@ #include -static void chiaki_audio_receiver_frame(ChiakiAudioReceiver *audio_receiver, ChiakiSeqNum16 frame_index, uint8_t *buf, size_t buf_size); +static void chiaki_audio_receiver_frame(ChiakiAudioReceiver *audio_receiver, ChiakiSeqNum16 frame_index, bool is_haptics, uint8_t *buf, size_t buf_size); CHIAKI_EXPORT ChiakiErrorCode chiaki_audio_receiver_init(ChiakiAudioReceiver *audio_receiver, ChiakiSession *session, ChiakiPacketStats *packet_stats) { @@ -102,14 +102,14 @@ CHIAKI_EXPORT void chiaki_audio_receiver_av_packet(ChiakiAudioReceiver *audio_re frame_index = packet->frame_index - fec_units_count + fec_index; } - chiaki_audio_receiver_frame(audio_receiver, frame_index, packet->data + unit_size * i, unit_size); + chiaki_audio_receiver_frame(audio_receiver, frame_index, packet->is_haptics, packet->data + unit_size * i, unit_size); } if(audio_receiver->packet_stats) chiaki_packet_stats_push_seq(audio_receiver->packet_stats, packet->frame_index); } -static void chiaki_audio_receiver_frame(ChiakiAudioReceiver *audio_receiver, ChiakiSeqNum16 frame_index, uint8_t *buf, size_t buf_size) +static void chiaki_audio_receiver_frame(ChiakiAudioReceiver *audio_receiver, ChiakiSeqNum16 frame_index, bool is_haptics, uint8_t *buf, size_t buf_size) { chiaki_mutex_lock(&audio_receiver->mutex); @@ -117,7 +117,9 @@ static void chiaki_audio_receiver_frame(ChiakiAudioReceiver *audio_receiver, Chi goto beach; audio_receiver->frame_index_prev = frame_index; - if(audio_receiver->session->audio_sink.frame_cb) + if(is_haptics && audio_receiver->session->haptics_sink.frame_cb) + audio_receiver->session->haptics_sink.frame_cb(buf, buf_size, audio_receiver->session->haptics_sink.user); + else if(!is_haptics && audio_receiver->session->audio_sink.frame_cb) audio_receiver->session->audio_sink.frame_cb(buf, buf_size, audio_receiver->session->audio_sink.user); beach: diff --git a/lib/src/controller.c b/lib/src/controller.c index 21b9971..744a83f 100644 --- a/lib/src/controller.c +++ b/lib/src/controller.c @@ -121,6 +121,19 @@ CHIAKI_EXPORT void chiaki_controller_state_or(ChiakiControllerState *out, Chiaki out->right_x = MAX_ABS(a->right_x, b->right_x); out->right_y = MAX_ABS(a->right_y, b->right_y); + #define ORF(n, idle_val) if(a->n == idle_val) out->n = b->n; else out->n = a->n + ORF(accel_x, 0.0f); + ORF(accel_y, 1.0f); + ORF(accel_z, 0.0f); + ORF(gyro_x, 0.0f); + ORF(gyro_y, 0.0f); + ORF(gyro_z, 0.0f); + ORF(orient_x, 0.0f); + ORF(orient_y, 0.0f); + ORF(orient_z, 0.0f); + ORF(orient_w, 1.0f); + #undef ORF + out->touch_id_next = 0; for(size_t i = 0; i < CHIAKI_CONTROLLER_TOUCHES_MAX; i++) { diff --git a/lib/src/ctrl.c b/lib/src/ctrl.c index 9dfc277..29a39b2 100644 --- a/lib/src/ctrl.c +++ b/lib/src/ctrl.c @@ -488,17 +488,25 @@ static void ctrl_message_received(ChiakiCtrl *ctrl, uint16_t msg_type, uint8_t * static void ctrl_enable_optional_features(ChiakiCtrl *ctrl) { - if(!ctrl->session->connect_info.enable_keyboard) - return; - // TODO: Last byte of pre_enable request is random (?) - // TODO: Signature ?! - uint8_t enable = 1; - uint8_t pre_enable[4] = { 0x00, 0x01, 0x01, 0x80 }; - uint8_t signature[0x10] = { 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x05, 0xAE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; - ctrl_message_send(ctrl, 0xD, signature, 0x10); - ctrl_message_send(ctrl, 0x36, pre_enable, 4); - ctrl_message_send(ctrl, CTRL_MESSAGE_TYPE_KEYBOARD_ENABLE_TOGGLE, &enable, 1); - ctrl_message_send(ctrl, 0x36, pre_enable, 4); + if(ctrl->session->connect_info.enable_dualsense) + { + CHIAKI_LOGI(ctrl->session->log, "Enabling DualSense features"); + const uint8_t enable[3] = { 0x00, 0x40, 0x00 }; + ctrl_message_send(ctrl, 0x13, enable, 3); + } + if(ctrl->session->connect_info.enable_keyboard) + { + CHIAKI_LOGI(ctrl->session->log, "Enabling Keyboard"); + // TODO: Last byte of pre_enable request is random (?) + // TODO: Signature ?! + uint8_t enable = 1; + uint8_t pre_enable[4] = { 0x00, 0x01, 0x01, 0x80 }; + uint8_t signature[0x10] = { 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x05, 0xAE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + ctrl_message_send(ctrl, 0xD, signature, 0x10); + ctrl_message_send(ctrl, 0x36, pre_enable, 4); + ctrl_message_send(ctrl, CTRL_MESSAGE_TYPE_KEYBOARD_ENABLE_TOGGLE, &enable, 1); + ctrl_message_send(ctrl, 0x36, pre_enable, 4); + } } static void ctrl_message_received_session_id(ChiakiCtrl *ctrl, uint8_t *payload, size_t payload_size) diff --git a/lib/src/feedback.c b/lib/src/feedback.c index 1ae190e..6db8364 100644 --- a/lib/src/feedback.c +++ b/lib/src/feedback.c @@ -81,7 +81,11 @@ CHIAKI_EXPORT void chiaki_feedback_state_format_v12(uint8_t *buf, ChiakiFeedback chiaki_feedback_state_format_v9(buf, state); buf[0x19] = 0x0; buf[0x1a] = 0x0; - buf[0x1b] = 0x1; // 1 for Shock, 0 for Sense + + // 1 is classic DualShock, 0 is DualSense, but using 0 requires setting [0x19] and [0x1a] to + // values taken from raw HID, which is generally not available. But setting 1 for both seems + // to always work fine. + buf[0x1b] = 0x1; } CHIAKI_EXPORT ChiakiErrorCode chiaki_feedback_history_event_set_button(ChiakiFeedbackHistoryEvent *event, uint64_t button, uint8_t state) diff --git a/lib/src/http.c b/lib/src/http.c index f55c438..5c07802 100644 --- a/lib/src/http.c +++ b/lib/src/http.c @@ -146,7 +146,15 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_recv_http_header(int sock, char *buf, size_ return err; } - int received = (int)recv(sock, buf, (int)buf_size, 0); + int received; + do + { + received = (int)recv(sock, buf, (int)buf_size, 0); +#if _WIN32 + } while(false); +#else + } while(received < 0 && errno == EINTR); +#endif if(received <= 0) return received == 0 ? CHIAKI_ERR_DISCONNECTED : CHIAKI_ERR_NETWORK; diff --git a/lib/src/session.c b/lib/src/session.c index b5b928f..aa56e19 100644 --- a/lib/src/session.c +++ b/lib/src/session.c @@ -158,6 +158,8 @@ CHIAKI_EXPORT const char *chiaki_quit_reason_string(ChiakiQuitReason reason) return "Unknown Error in Stream Connection"; case CHIAKI_QUIT_REASON_STREAM_CONNECTION_REMOTE_DISCONNECTED: return "Remote has disconnected from Stream Connection"; + case CHIAKI_QUIT_REASON_STREAM_CONNECTION_REMOTE_SHUTDOWN: + return "Remote has disconnected from Stream Connection the because Server shut down"; case CHIAKI_QUIT_REASON_NONE: default: return "Unknown"; @@ -227,6 +229,7 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_session_init(ChiakiSession *session, Chiaki session->connect_info.video_profile = connect_info->video_profile; session->connect_info.video_profile_auto_downgrade = connect_info->video_profile_auto_downgrade; session->connect_info.enable_keyboard = connect_info->enable_keyboard; + session->connect_info.enable_dualsense = connect_info->enable_dualsense; return CHIAKI_ERR_SUCCESS; error_stop_pipe: @@ -504,7 +507,10 @@ ctrl_failed: if(err == CHIAKI_ERR_DISCONNECTED) { CHIAKI_LOGE(session->log, "Remote disconnected from StreamConnection"); - session->quit_reason = CHIAKI_QUIT_REASON_STREAM_CONNECTION_REMOTE_DISCONNECTED; + if(!strcmp(session->stream_connection.remote_disconnect_reason, "Server shutting down")) + session->quit_reason = CHIAKI_QUIT_REASON_STREAM_CONNECTION_REMOTE_SHUTDOWN; + else + session->quit_reason = CHIAKI_QUIT_REASON_STREAM_CONNECTION_REMOTE_DISCONNECTED; session->quit_reason_str = strdup(session->stream_connection.remote_disconnect_reason); } else if(err != CHIAKI_ERR_SUCCESS && err != CHIAKI_ERR_CANCELED) diff --git a/lib/src/stoppipe.c b/lib/src/stoppipe.c index 7ad2d2a..003ad5d 100644 --- a/lib/src/stoppipe.c +++ b/lib/src/stoppipe.c @@ -147,7 +147,11 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_stop_pipe_select_single(ChiakiStopPipe *sto timeout = &timeout_s; } - int r = select(nfds, &rfds, write ? &wfds : NULL, NULL, timeout); + int r; + do + { + r = select(nfds, &rfds, write ? &wfds : NULL, NULL, timeout); + } while(r < 0 && errno == EINTR); if(r < 0) return CHIAKI_ERR_UNKNOWN; diff --git a/lib/src/streamconnection.c b/lib/src/streamconnection.c index 2a7bb6f..7187c1b 100644 --- a/lib/src/streamconnection.c +++ b/lib/src/streamconnection.c @@ -47,7 +47,9 @@ static void stream_connection_takion_cb(ChiakiTakionEvent *event, void *user); static void stream_connection_takion_data(ChiakiStreamConnection *stream_connection, ChiakiTakionMessageDataType data_type, uint8_t *buf, size_t buf_size); static void stream_connection_takion_data_protobuf(ChiakiStreamConnection *stream_connection, uint8_t *buf, size_t buf_size); static void stream_connection_takion_data_rumble(ChiakiStreamConnection *stream_connection, uint8_t *buf, size_t buf_size); +static void stream_connection_takion_data_trigger_effects(ChiakiStreamConnection *stream_connection, uint8_t *buf, size_t buf_size); static ChiakiErrorCode stream_connection_send_big(ChiakiStreamConnection *stream_connection); +static ChiakiErrorCode stream_connection_send_controller_connection(ChiakiStreamConnection *stream_connection); static ChiakiErrorCode stream_connection_send_disconnect(ChiakiStreamConnection *stream_connection); static void stream_connection_takion_data_idle(ChiakiStreamConnection *stream_connection, uint8_t *buf, size_t buf_size); static void stream_connection_takion_data_expect_bang(ChiakiStreamConnection *stream_connection, uint8_t *buf, size_t buf_size); @@ -79,6 +81,7 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_stream_connection_init(ChiakiStreamConnecti stream_connection->video_receiver = NULL; stream_connection->audio_receiver = NULL; + stream_connection->haptics_receiver = NULL; err = chiaki_mutex_init(&stream_connection->feedback_sender_mutex, false); if(err != CHIAKI_ERR_SUCCESS) @@ -143,6 +146,7 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_stream_connection_run(ChiakiStreamConnectio takion_info.ip_dontfrag = false; takion_info.enable_crypt = true; + takion_info.enable_dualsense = session->connect_info.enable_dualsense; takion_info.protocol_version = chiaki_target_is_ps5(session->target) ? 12 : 9; takion_info.cb = stream_connection_takion_cb; @@ -164,12 +168,20 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_stream_connection_run(ChiakiStreamConnectio return CHIAKI_ERR_UNKNOWN; } + stream_connection->haptics_receiver = chiaki_audio_receiver_new(session, NULL); + if(!stream_connection->haptics_receiver) + { + CHIAKI_LOGE(session->log, "StreamConnection failed to initialize Haptics Receiver"); + err = CHIAKI_ERR_UNKNOWN; + goto err_audio_receiver; + } + stream_connection->video_receiver = chiaki_video_receiver_new(session, &stream_connection->packet_stats); if(!stream_connection->video_receiver) { CHIAKI_LOGE(session->log, "StreamConnection failed to initialize Video Receiver"); err = CHIAKI_ERR_UNKNOWN; - goto err_audio_receiver; + goto err_haptics_receiver; } stream_connection->state = STATE_TAKION_CONNECT; @@ -321,6 +333,10 @@ err_video_receiver: chiaki_video_receiver_free(stream_connection->video_receiver); stream_connection->video_receiver = NULL; +err_haptics_receiver: + chiaki_audio_receiver_free(stream_connection->haptics_receiver); + stream_connection->haptics_receiver = NULL; + err_audio_receiver: chiaki_audio_receiver_free(stream_connection->audio_receiver); stream_connection->audio_receiver = NULL; @@ -376,6 +392,9 @@ static void stream_connection_takion_data(ChiakiStreamConnection *stream_connect case CHIAKI_TAKION_MESSAGE_DATA_TYPE_RUMBLE: stream_connection_takion_data_rumble(stream_connection, buf, buf_size); break; + case CHIAKI_TAKION_MESSAGE_DATA_TYPE_TRIGGER_EFFECTS: + stream_connection_takion_data_trigger_effects(stream_connection, buf, buf_size); + break; default: break; } @@ -415,6 +434,24 @@ static void stream_connection_takion_data_rumble(ChiakiStreamConnection *stream_ chiaki_session_send_event(stream_connection->session, &event); } + +static void stream_connection_takion_data_trigger_effects(ChiakiStreamConnection *stream_connection, uint8_t *buf, size_t buf_size) +{ + if(buf_size < 25) + { + CHIAKI_LOGE(stream_connection->log, "StreamConnection got trigger effects packet with size %#llx < 25", + (unsigned long long)buf_size); + return; + } + ChiakiEvent event = { 0 }; + event.type = CHIAKI_EVENT_TRIGGER_EFFECTS; + event.trigger_effects.type_left = buf[1]; + event.trigger_effects.type_right = buf[2]; + memcpy(&event.trigger_effects.left, buf + 5, 10); + memcpy(&event.trigger_effects.right, buf + 15, 10); + chiaki_session_send_event(stream_connection->session, &event); +} + static void stream_connection_takion_data_handle_disconnect(ChiakiStreamConnection *stream_connection, uint8_t *buf, size_t buf_size) { tkproto_TakionMessage msg; @@ -460,7 +497,7 @@ static void stream_connection_takion_data_idle(ChiakiStreamConnection *stream_co return; } - CHIAKI_LOGV(stream_connection->log, "StreamConnection received data"); + CHIAKI_LOGV(stream_connection->log, "StreamConnection received data with msg.type == %d", msg.type); chiaki_log_hexdump(stream_connection->log, CHIAKI_LOG_VERBOSE, buf, buf_size); if(msg.type == tkproto_TakionMessage_PayloadType_DISCONNECT) @@ -522,7 +559,8 @@ static void stream_connection_takion_data_expect_bang(ChiakiStreamConnection *st return; } - CHIAKI_LOGE(stream_connection->log, "StreamConnection expected bang payload but received something else"); + CHIAKI_LOGE(stream_connection->log, "StreamConnection expected bang payload but received something else: %d", msg.type); + chiaki_log_hexdump(stream_connection->log, CHIAKI_LOG_VERBOSE, buf, buf_size); return; } @@ -584,6 +622,12 @@ static void stream_connection_takion_data_expect_bang(ChiakiStreamConnection *st // stream_connection->state_mutex is expected to be locked by the caller of this function stream_connection->state_finished = true; chiaki_cond_signal(&stream_connection->state_cond); + err = stream_connection_send_controller_connection(stream_connection); + if(err != CHIAKI_ERR_SUCCESS) + { + CHIAKI_LOGE(stream_connection->log, "StreamConnection failed to send controller connection"); + goto error; + } return; error: stream_connection->state_failed = true; @@ -811,6 +855,37 @@ static ChiakiErrorCode stream_connection_send_big(ChiakiStreamConnection *stream return err; } +static ChiakiErrorCode stream_connection_send_controller_connection(ChiakiStreamConnection *stream_connection) +{ + ChiakiSession *session = stream_connection->session; + tkproto_TakionMessage msg; + memset(&msg, 0, sizeof(msg)); + + msg.type = tkproto_TakionMessage_PayloadType_CONTROLLERCONNECTION; + msg.has_controller_connection_payload = true; + msg.controller_connection_payload.has_connected = true; + msg.controller_connection_payload.connected = true; + msg.controller_connection_payload.has_controller_id = false; + msg.controller_connection_payload.has_controller_type = true; + msg.controller_connection_payload.controller_type = session->connect_info.enable_dualsense + ? tkproto_ControllerConnectionPayload_ControllerType_DUALSENSE + : tkproto_ControllerConnectionPayload_ControllerType_DUALSHOCK4; + + uint8_t buf[2048]; + size_t buf_size; + + pb_ostream_t stream = pb_ostream_from_buffer(buf, sizeof(buf)); + bool pbr = pb_encode(&stream, tkproto_TakionMessage_fields, &msg); + if(!pbr) + { + CHIAKI_LOGE(stream_connection->log, "StreamConnection controller connection protobuf encoding failed"); + return CHIAKI_ERR_UNKNOWN; + } + + buf_size = stream.bytes_written; + return chiaki_takion_send_message_data(&stream_connection->takion, 1, 1, buf, buf_size, NULL); +} + static ChiakiErrorCode stream_connection_send_streaminfo_ack(ChiakiStreamConnection *stream_connection) { tkproto_TakionMessage msg; @@ -867,6 +942,8 @@ static void stream_connection_takion_av(ChiakiStreamConnection *stream_connectio if(packet->is_video) chiaki_video_receiver_av_packet(stream_connection->video_receiver, packet); + else if(packet->is_haptics) + chiaki_audio_receiver_av_packet(stream_connection->haptics_receiver, packet); else chiaki_audio_receiver_av_packet(stream_connection->audio_receiver, packet); } diff --git a/lib/src/takion.c b/lib/src/takion.c index c433977..baae28c 100644 --- a/lib/src/takion.c +++ b/lib/src/takion.c @@ -57,7 +57,8 @@ typedef enum takion_packet_type_t { TAKION_PACKET_TYPE_CONGESTION = 5, TAKION_PACKET_TYPE_FEEDBACK_STATE = 6, TAKION_PACKET_TYPE_CLIENT_INFO = 8, - TAKION_PACKET_TYPE_PAD_INFO_EVENT = 9 + TAKION_PACKET_TYPE_PAD_INFO_EVENT = 9, + TAKION_PACKET_TYPE_PAD_ADAPTIVE_TRIGGERS = 11, } TakionPacketType; /** @@ -215,6 +216,7 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_connect(ChiakiTakion *takion, Chiaki takion->postponed_packets = NULL; takion->postponed_packets_size = 0; takion->postponed_packets_count = 0; + takion->enable_dualsense = info->enable_dualsense; CHIAKI_LOGI(takion->log, "Takion connecting (version %u)", (unsigned int)info->protocol_version); @@ -950,6 +952,7 @@ static void takion_flush_data_queue(ChiakiTakion *takion) if(data_type != CHIAKI_TAKION_MESSAGE_DATA_TYPE_PROTOBUF && data_type != CHIAKI_TAKION_MESSAGE_DATA_TYPE_RUMBLE + && data_type != CHIAKI_TAKION_MESSAGE_DATA_TYPE_TRIGGER_EFFECTS && data_type != CHIAKI_TAKION_MESSAGE_DATA_TYPE_9) { CHIAKI_LOGW(takion->log, "Takion received data with unexpected data type %#x", data_type); @@ -1308,7 +1311,7 @@ static ChiakiErrorCode av_packet_parse(bool v12, ChiakiTakionAVPacket *packet, C if(v12 && !packet->is_video) { - packet->byte_before_audio_data = *av; + packet->is_haptics = *av == 0x02; av += 1; av_size -= 1; } diff --git a/scripts/Dockerfile.noble b/scripts/Dockerfile.noble new file mode 100644 index 0000000..abc33c3 --- /dev/null +++ b/scripts/Dockerfile.noble @@ -0,0 +1,13 @@ + +FROM ubuntu:noble + +RUN apt-get update +# Hint: python3-setuptools should not be needed with nanopb >= 0.4.9 +RUN apt-get -y install git g++ cmake ninja-build curl pkg-config unzip \ + python3-protobuf protobuf-compiler \ + python3-setuptools \ + libssl-dev libopus-dev qtbase5-dev qtmultimedia5-dev libqt5multimedia5-plugins libqt5svg5-dev \ + libgl1-mesa-dev nasm libudev-dev libva-dev fuse libevdev-dev libudev-dev file + +CMD [] + diff --git a/scripts/Dockerfile.xenial b/scripts/Dockerfile.xenial deleted file mode 100644 index dd92581..0000000 --- a/scripts/Dockerfile.xenial +++ /dev/null @@ -1,13 +0,0 @@ - -FROM ubuntu:xenial - -RUN apt-get update -RUN apt-get install -y software-properties-common -RUN add-apt-repository ppa:beineri/opt-qt-5.12.3-xenial -RUN apt-get update -RUN apt-get -y install git g++ cmake ninja-build curl pkg-config unzip python3-pip \ - libssl-dev libopus-dev qt512base qt512multimedia qt512svg \ - libgl1-mesa-dev nasm libudev-dev libva-dev fuse libevdev-dev libudev-dev - -CMD [] - diff --git a/scripts/appveyor-win.sh b/scripts/appveyor-win.sh index f751200..7bbc0f3 100755 --- a/scripts/appveyor-win.sh +++ b/scripts/appveyor-win.sh @@ -1,77 +1,92 @@ #!/bin/bash -echo "APPVEYOR_BUILD_FOLDER=$APPVEYOR_BUILD_FOLDER" +set -xe -mkdir ninja && cd ninja || exit 1 -wget https://github.com/ninja-build/ninja/releases/download/v1.9.0/ninja-win.zip && 7z x ninja-win.zip || exit 1 -cd .. || exit 1 +BUILD_ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd )" +BUILD_ROOT="$(echo $BUILD_ROOT | sed 's|^/\([a-z]\)|\1:|g')" # replace /c/... by c:/... for cmake to understand it +echo "BUILD_ROOT=$BUILD_ROOT" -mkdir yasm && cd yasm || exit 1 -wget http://www.tortall.net/projects/yasm/releases/yasm-1.3.0-win64.exe && mv yasm-1.3.0-win64.exe yasm.exe || exit 1 -cd .. || exit 1 +mkdir ninja && cd ninja +wget https://github.com/ninja-build/ninja/releases/download/v1.9.0/ninja-win.zip && 7z x ninja-win.zip +cd .. + +mkdir yasm && cd yasm +wget http://www.tortall.net/projects/yasm/releases/yasm-1.3.0-win64.exe && mv yasm-1.3.0-win64.exe yasm.exe +cd .. export PATH="$PWD/ninja:$PWD/yasm:/c/Qt/5.12/msvc2017_64/bin:$PATH" -scripts/build-ffmpeg.sh . --target-os=win64 --arch=x86_64 --toolchain=msvc || exit 1 +scripts/build-ffmpeg.sh . --target-os=win64 --arch=x86_64 --toolchain=msvc -git clone https://github.com/xiph/opus.git && cd opus && git checkout ad8fe90db79b7d2a135e3dfd2ed6631b0c5662ab || exit 1 -mkdir build && cd build || exit 1 +git clone https://github.com/xiph/opus.git && cd opus && git checkout ad8fe90db79b7d2a135e3dfd2ed6631b0c5662ab +mkdir build && cd build cmake \ -G Ninja \ -DCMAKE_C_COMPILER=cl \ -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_INSTALL_PREFIX="$APPVEYOR_BUILD_FOLDER/opus-prefix" \ - .. || exit 1 -ninja || exit 1 -ninja install || exit 1 -cd ../.. || exit 1 + -DCMAKE_INSTALL_PREFIX="$BUILD_ROOT/opus-prefix" \ + .. +ninja +ninja install +cd ../.. -wget https://mirror.firedaemon.com/OpenSSL/openssl-1.1.1d-dev.zip && 7z x openssl-1.1.1d-dev.zip || exit 1 +wget https://download.firedaemon.com/FireDaemon-OpenSSL/openssl-1.1.1s.zip && 7z x openssl-1.1.*.zip -wget https://www.libsdl.org/release/SDL2-devel-2.0.10-VC.zip && 7z x SDL2-devel-2.0.10-VC.zip || exit 1 -export SDL_ROOT="$APPVEYOR_BUILD_FOLDER/SDL2-2.0.10" || exit 1 -export SDL_ROOT=${SDL_ROOT//[\\]//} || exit 1 +wget https://www.libsdl.org/release/SDL2-devel-2.26.2-VC.zip && 7z x SDL2-devel-2.26.2-VC.zip +export SDL_ROOT="$BUILD_ROOT/SDL2-2.26.2" +export SDL_ROOT=${SDL_ROOT//[\\]//} echo "set(SDL2_INCLUDE_DIRS \"$SDL_ROOT/include\") set(SDL2_LIBRARIES \"$SDL_ROOT/lib/x64/SDL2.lib\") -set(SDL2_LIBDIR \"$SDL_ROOT/lib/x64\")" > "$SDL_ROOT/SDL2Config.cmake" || exit 1 +set(SDL2_LIBDIR \"$SDL_ROOT/lib/x64\") +include($SDL_ROOT/cmake/sdl2-config-version.cmake)" > "$SDL_ROOT/SDL2Config.cmake" -mkdir protoc && cd protoc || exit 1 -wget https://github.com/protocolbuffers/protobuf/releases/download/v3.9.1/protoc-3.9.1-win64.zip && 7z x protoc-3.9.1-win64.zip || exit 1 -cd .. || exit 1 -export PATH="$PWD/protoc/bin:$PATH" || exit 1 +mkdir protoc && cd protoc +wget https://github.com/protocolbuffers/protobuf/releases/download/v3.9.1/protoc-3.9.1-win64.zip && 7z x protoc-3.9.1-win64.zip +cd .. +export PATH="$PWD/protoc/bin:$PATH" PYTHON="C:/Python37/python.exe" -"$PYTHON" -m pip install protobuf || exit 1 +"$PYTHON" -m pip install protobuf==3.19.5 -QT_PATH="C:/Qt/5.12/msvc2017_64" +QT_PATH="C:/Qt/5.15/msvc2019_64" COPY_DLLS="$PWD/openssl-1.1/x64/bin/libcrypto-1_1-x64.dll $PWD/openssl-1.1/x64/bin/libssl-1_1-x64.dll $SDL_ROOT/lib/x64/SDL2.dll" -mkdir build && cd build || exit 1 +echo "-- Configure" + +mkdir build && cd build + cmake \ -G Ninja \ -DCMAKE_C_COMPILER=cl \ -DCMAKE_C_FLAGS="-we4013" \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \ - -DCMAKE_PREFIX_PATH="$APPVEYOR_BUILD_FOLDER/ffmpeg-prefix;$APPVEYOR_BUILD_FOLDER/opus-prefix;$APPVEYOR_BUILD_FOLDER/openssl-1.1/x64;$QT_PATH;$SDL_ROOT" \ + -DCMAKE_PREFIX_PATH="$BUILD_ROOT/ffmpeg-prefix;$BUILD_ROOT/opus-prefix;$BUILD_ROOT/openssl-1.1/x64;$QT_PATH;$SDL_ROOT" \ -DPYTHON_EXECUTABLE="$PYTHON" \ -DCHIAKI_ENABLE_TESTS=ON \ -DCHIAKI_ENABLE_CLI=OFF \ -DCHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER=ON \ - .. || exit 1 + .. -ninja || exit 1 +echo "-- Build" -test/chiaki-unit.exe || exit 1 +ninja -cd .. || exit 1 +echo "-- Test" + +cp $COPY_DLLS test/ +test/chiaki-unit.exe + +cd .. # Deploy -mkdir Chiaki && cp build/gui/chiaki.exe Chiaki || exit 1 -mkdir Chiaki-PDB && cp build/gui/chiaki.pdb Chiaki-PDB || exit 1 +echo "-- Deploy" -"$QT_PATH/bin/windeployqt.exe" Chiaki/chiaki.exe || exit 1 +mkdir Chiaki && cp build/gui/chiaki.exe Chiaki +mkdir Chiaki-PDB && cp build/gui/chiaki.pdb Chiaki-PDB + +"$QT_PATH/bin/windeployqt.exe" Chiaki/chiaki.exe cp -v $COPY_DLLS Chiaki diff --git a/scripts/build-appimage.sh b/scripts/build-appimage.sh index dd01ef4..05c0428 100755 --- a/scripts/build-appimage.sh +++ b/scripts/build-appimage.sh @@ -2,10 +2,11 @@ set -xe +# sometimes there are errors in linuxdeploy in docker/podman when the appdir is on a mount +appdir=${1:-`pwd`/appimage/appdir} + mkdir appimage -pip3 install --user protobuf -scripts/fetch-protoc.sh appimage export PATH="`pwd`/appimage/protoc/bin:$PATH" scripts/build-ffmpeg.sh appimage scripts/build-sdl2.sh appimage @@ -15,31 +16,31 @@ cd build_appimage cmake \ -GNinja \ -DCMAKE_BUILD_TYPE=Release \ - "-DCMAKE_PREFIX_PATH=`pwd`/../appimage/ffmpeg-prefix;`pwd`/../appimage/sdl2-prefix;/opt/qt512" \ + "-DCMAKE_PREFIX_PATH=`pwd`/../appimage/ffmpeg-prefix;`pwd`/../appimage/sdl2-prefix" \ -DCHIAKI_ENABLE_TESTS=ON \ -DCHIAKI_ENABLE_CLI=OFF \ -DCHIAKI_ENABLE_GUI=ON \ - -DCHIAKI_ENABLE_SETSU=ON \ -DCHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER=ON \ -DCMAKE_INSTALL_PREFIX=/usr \ .. cd .. + +# purge leftover proto/nanopb_pb2.py which may have been created with another protobuf version +rm -fv third-party/nanopb/generator/proto/nanopb_pb2.py + ninja -C build_appimage build_appimage/test/chiaki-unit -DESTDIR=`pwd`/appimage/appdir ninja -C build_appimage install +DESTDIR="${appdir}" ninja -C build_appimage install cd appimage curl -L -O https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage chmod +x linuxdeploy-x86_64.AppImage curl -L -O https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage chmod +x linuxdeploy-plugin-qt-x86_64.AppImage -set +e -source /opt/qt512/bin/qt512-env.sh -set -e export LD_LIBRARY_PATH="`pwd`/sdl2-prefix/lib:$LD_LIBRARY_PATH" export EXTRA_QT_PLUGINS=opengl -./linuxdeploy-x86_64.AppImage --appdir=appdir -e appdir/usr/bin/chiaki -d appdir/usr/share/applications/chiaki.desktop --plugin qt --output appimage -mv Chiaki-*-x86_64.AppImage Chiaki.AppImage +./linuxdeploy-x86_64.AppImage --appdir="${appdir}" -e "${appdir}/usr/bin/chiaki" -d "${appdir}/usr/share/applications/chiaki.desktop" --plugin qt --output appimage +mv Chiaki*-x86_64.AppImage Chiaki.AppImage diff --git a/scripts/build-common.sh b/scripts/build-common.sh index 0eb87b9..dd4be2c 100755 --- a/scripts/build-common.sh +++ b/scripts/build-common.sh @@ -1,5 +1,8 @@ #!/bin/bash +# purge leftover proto/nanopb_pb2.py which may have been created with another protobuf version +rm -fv third-party/nanopb/generator/proto/nanopb_pb2.py + mkdir build && cd build || exit 1 cmake \ -DCMAKE_BUILD_TYPE=Release \ diff --git a/scripts/build-ffmpeg.sh b/scripts/build-ffmpeg.sh index d00b962..9b6348f 100755 --- a/scripts/build-ffmpeg.sh +++ b/scripts/build-ffmpeg.sh @@ -5,7 +5,7 @@ cd "./$1" shift ROOT="`pwd`" -TAG=n4.3.1 +TAG=n4.3.9 git clone https://git.ffmpeg.org/ffmpeg.git --depth 1 -b $TAG && cd ffmpeg || exit 1 diff --git a/scripts/build-sdl2.sh b/scripts/build-sdl2.sh index f06b9cc..716b486 100755 --- a/scripts/build-sdl2.sh +++ b/scripts/build-sdl2.sh @@ -6,9 +6,10 @@ cd $(dirname "${BASH_SOURCE[0]}")/.. cd "./$1" ROOT="`pwd`" -URL=https://www.libsdl.org/release/SDL2-2.0.10.tar.gz -FILE=SDL2-2.0.10.tar.gz -DIR=SDL2-2.0.10 +SDL_VER=2.26.1 +URL=https://www.libsdl.org/release/SDL2-${SDL_VER}.tar.gz +FILE=SDL2-${SDL_VER}.tar.gz +DIR=SDL2-${SDL_VER} if [ ! -d "$DIR" ]; then curl -L "$URL" -O @@ -21,14 +22,14 @@ mkdir -p build && cd build || exit 1 cmake \ -DCMAKE_INSTALL_PREFIX="$ROOT/sdl2-prefix" \ -DSDL_ATOMIC=OFF \ - -DSDL_AUDIO=OFF \ + -DSDL_AUDIO=ON \ -DSDL_CPUINFO=OFF \ -DSDL_EVENTS=ON \ -DSDL_FILE=OFF \ -DSDL_FILESYSTEM=OFF \ -DSDL_HAPTIC=ON \ -DSDL_JOYSTICK=ON \ - -DSDL_LOADSO=OFF \ + -DSDL_LOADSO=ON \ -DSDL_RENDER=OFF \ -DSDL_SHARED=ON \ -DSDL_STATIC=OFF \ diff --git a/scripts/fetch-protoc.sh b/scripts/fetch-protoc.sh deleted file mode 100755 index e1d2d2f..0000000 --- a/scripts/fetch-protoc.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -set -xe - -cd $(dirname "${BASH_SOURCE[0]}")/.. -cd "./$1" -ROOT="`pwd`" - -URL=https://github.com/protocolbuffers/protobuf/releases/download/v3.9.1/protoc-3.9.1-linux-x86_64.zip - -curl -L "$URL" -o protoc.zip -unzip protoc.zip -d protoc - diff --git a/scripts/flatpak/com.github.thestr4ng3r.Chiaki.json b/scripts/flatpak/com.github.thestr4ng3r.Chiaki.json index 992ca18..daab8a0 100644 --- a/scripts/flatpak/com.github.thestr4ng3r.Chiaki.json +++ b/scripts/flatpak/com.github.thestr4ng3r.Chiaki.json @@ -89,8 +89,8 @@ { "type": "git", "url": "https://git.sr.ht/~thestr4ng3r/chiaki", - "tag": "v2.1.0", - "commit": "fcdc414692b33ecae1f30a19dc4cd81d4bd77121" + "tag": "v2.1.1", + "commit": "2257030adeb5c5a84380c78d34be4d783971663c" } ] } diff --git a/scripts/macos-dist-local.sh b/scripts/macos-dist-local.sh new file mode 100755 index 0000000..d2ac740 --- /dev/null +++ b/scripts/macos-dist-local.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# Build Chiaki for macOS distribution using dependencies from MacPorts and custom ffmpeg + +set -xe +cd $(dirname "${BASH_SOURCE[0]}")/.. +scripts/build-ffmpeg.sh +export CMAKE_PREFIX_PATH="`pwd`/ffmpeg-prefix" +scripts/build-common.sh +cp -a build/gui/chiaki.app Chiaki.app +/opt/local/libexec/qt5/bin/macdeployqt Chiaki.app + +# Remove all LC_RPATH load commands that have absolute paths of the build machine +RPATHS=$(otool -l Chiaki.app/Contents/MacOS/chiaki | grep -A 2 LC_RPATH | grep 'path /' | awk '{print $2}') +for p in ${RPATHS}; do install_name_tool -delete_rpath "$p" Chiaki.app/Contents/MacOS/chiaki; done + +# This may warn because we already ran macdeployqt above +/opt/local/libexec/qt5/bin/macdeployqt Chiaki.app -dmg diff --git a/scripts/run-docker-build-appimage.sh b/scripts/run-docker-build-appimage.sh deleted file mode 100755 index 931499c..0000000 --- a/scripts/run-docker-build-appimage.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -set -xe -cd "`dirname $(readlink -f ${0})`" - -docker build -t chiaki-xenial . -f Dockerfile.xenial -cd .. -docker run --rm \ - -v "`pwd`:/build/chiaki" \ - -w "/build/chiaki" \ - --device /dev/fuse \ - --cap-add SYS_ADMIN \ - -t chiaki-xenial \ - /bin/bash -c "scripts/build-appimage.sh" - diff --git a/scripts/run-docker-build-bullseye.sh b/scripts/run-docker-build-bullseye.sh deleted file mode 100755 index fd19bb1..0000000 --- a/scripts/run-docker-build-bullseye.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -set -xe -cd "`dirname $(readlink -f ${0})`" - -docker build -t chiaki-bullseye . -f Dockerfile.bullseye -cd .. -docker run --rm -v "`pwd`:/build" chiaki-bullseye /bin/bash -c " - cd /build && - mkdir build_bullseye && - cmake -Bbuild_bullseye -GNinja -DCHIAKI_ENABLE_SETSU=ON -DCHIAKI_USE_SYSTEM_JERASURE=ON -DCHIAKI_USE_SYSTEM_NANOPB=ON && - ninja -C build_bullseye && - ninja -C build_bullseye test" - diff --git a/scripts/run-podman-build-appimage.sh b/scripts/run-podman-build-appimage.sh new file mode 100755 index 0000000..5024952 --- /dev/null +++ b/scripts/run-podman-build-appimage.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -xe +cd "`dirname $(readlink -f ${0})`" + +podman build --arch amd64 -t localhost/chiaki-noble . -f Dockerfile.noble +cd .. +podman run --rm \ + --arch amd64 \ + -v "`pwd`:/build/chiaki" \ + -w "/build/chiaki" \ + --device /dev/fuse \ + --cap-add SYS_ADMIN \ + -t localhost/chiaki-noble \ + /bin/bash -c "scripts/build-appimage.sh /build/appdir" + diff --git a/scripts/run-podman-build-bullseye.sh b/scripts/run-podman-build-bullseye.sh new file mode 100755 index 0000000..344a76e --- /dev/null +++ b/scripts/run-podman-build-bullseye.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +set -xe +cd "`dirname $(readlink -f ${0})`" + +podman build -t chiaki-bullseye . -f Dockerfile.bullseye +cd .. +podman run --rm -v "`pwd`:/build" chiaki-bullseye /bin/bash -c " + cd /build && + rm -fv third-party/nanopb/generator/proto/nanopb_pb2.py && + mkdir build_bullseye && + cmake -Bbuild_bullseye -GNinja -DCHIAKI_USE_SYSTEM_JERASURE=ON -DCHIAKI_USE_SYSTEM_NANOPB=ON && + ninja -C build_bullseye && + ninja -C build_bullseye test" + diff --git a/scripts/switch/build.sh b/scripts/switch/build.sh index 1e03a2a..37b825d 100755 --- a/scripts/switch/build.sh +++ b/scripts/switch/build.sh @@ -5,10 +5,7 @@ set -xveo pipefail arg1=$1 build="./build" if [ "$arg1" != "linux" ]; then - # source /opt/devkitpro/switchvars.sh - # toolchain="${DEVKITPRO}/switch.cmake" toolchain="cmake/switch.cmake" - export PORTLIBS_PREFIX="$(${DEVKITPRO}/portlibs_prefix.sh switch)" build="./build_switch" fi @@ -19,6 +16,9 @@ build_chiaki (){ pushd "${BASEDIR}" #rm -rf ./build + # purge leftover proto/nanopb_pb2.py which may have been created with another protobuf version + rm -fv third-party/nanopb/generator/proto/nanopb_pb2.py + cmake -B "${build}" \ -GNinja \ -DCMAKE_TOOLCHAIN_FILE=${toolchain} \ diff --git a/scripts/switch/run-docker-build-chiaki.sh b/scripts/switch/dev-container.sh similarity index 55% rename from scripts/switch/run-docker-build-chiaki.sh rename to scripts/switch/dev-container.sh index c81c788..4cfedb3 100755 --- a/scripts/switch/run-docker-build-chiaki.sh +++ b/scripts/switch/dev-container.sh @@ -2,10 +2,9 @@ cd "`dirname $(readlink -f ${0})`/../.." -docker run \ +podman run --rm \ -v "`pwd`:/build/chiaki" \ -w "/build/chiaki" \ - -t \ - thestr4ng3r/chiaki-build-switch \ - -c "scripts/switch/build.sh" - + -it \ + quay.io/thestr4ng3r/chiaki-build-switch:v3 \ + /bin/bash diff --git a/scripts/switch/push-docker-build-chiaki.sh b/scripts/switch/push-podman-build-chiaki.sh similarity index 100% rename from scripts/switch/push-docker-build-chiaki.sh rename to scripts/switch/push-podman-build-chiaki.sh diff --git a/scripts/switch/run-podman-build-chiaki.sh b/scripts/switch/run-podman-build-chiaki.sh new file mode 100755 index 0000000..513cdc8 --- /dev/null +++ b/scripts/switch/run-podman-build-chiaki.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +cd "`dirname $(readlink -f ${0})`/../.." + +podman run --rm \ + -v "`pwd`:/build/chiaki" \ + -w "/build/chiaki" \ + -it \ + quay.io/thestr4ng3r/chiaki-build-switch:v3 \ + ${1:-/bin/bash -c "scripts/switch/build.sh"} diff --git a/switch/CMakeLists.txt b/switch/CMakeLists.txt index 4916a2e..3d5639a 100644 --- a/switch/CMakeLists.txt +++ b/switch/CMakeLists.txt @@ -61,17 +61,16 @@ target_include_directories(borealis PUBLIC find_package(glfw3 REQUIRED) find_library(EGL EGL) -find_library(GLAPI glapi) -find_library(DRM_NOUVEAU drm_nouveau) -target_link_libraries(borealis +target_link_libraries(borealis PUBLIC glfw - ${EGL} - ${GLAPI} - ${DRM_NOUVEAU}) + ${EGL}) if(CHIAKI_IS_SWITCH) target_compile_definitions(borealis PUBLIC BOREALIS_RESOURCES="romfs:/") + find_library(GLAPI glapi) + find_library(DRM_NOUVEAU drm_nouveau) + target_link_libraries(borealis PUBLIC ${GLAPI} ${DRM_NOUVEAU}) else() target_compile_definitions(borealis PUBLIC BOREALIS_RESOURCES="./switch/res/") @@ -114,9 +113,7 @@ target_link_libraries(chiaki-borealis if(CHIAKI_IS_SWITCH) # libnx is forced by the switch toolchain find_library(Z z) - find_library(GLAPI glapi) # TODO: make it transitive from borealis - find_library(DRM_NOUVEAU drm_nouveau) # TODO: make it transitive from borealis - target_link_libraries(chiaki-borealis ${Z} ${GLAPI} ${DRM_NOUVEAU}) + target_link_libraries(chiaki-borealis ${Z} ${GLAPI}) endif() install(TARGETS chiaki-borealis diff --git a/switch/README.md b/switch/README.md index f5fbe68..2307fa4 100644 --- a/switch/README.md +++ b/switch/README.md @@ -7,7 +7,7 @@ but the easiest way is to use the following container. Build Project ------------- ``` -bash scripts/switch/run-docker-build-chiaki.sh +bash scripts/switch/run-podman-build-chiaki.sh ``` tools @@ -15,7 +15,7 @@ tools Push to homebrew Netloader ``` # where X.X.X.X is the IP of your switch -bash scripts/switch/push-docker-build-chiaki.sh -a 192.168.0.200 +bash scripts/switch/push-podman-build-chiaki.sh -a 192.168.0.200 ``` Troubleshoot diff --git a/switch/borealis b/switch/borealis index cbdc1b6..eae1371 160000 --- a/switch/borealis +++ b/switch/borealis @@ -1 +1 @@ -Subproject commit cbdc1b65314d1eeb2799deae5cf6f113d6d67b46 +Subproject commit eae1371831d6cebf11b8ebd4c611069bccc6fb9b diff --git a/switch/include/io.h b/switch/include/io.h index 3c1031d..3bb6fd1 100644 --- a/switch/include/io.h +++ b/switch/include/io.h @@ -67,7 +67,7 @@ class IO // default nintendo switch res int screen_width = 1280; int screen_height = 720; - AVCodec *codec; + const AVCodec *codec; AVCodecContext *codec_context; AVFrame *frame; SDL_AudioDeviceID sdl_audio_device_id = 0; diff --git a/switch/include/settings.h b/switch/include/settings.h index fbf43b8..6bcb445 100644 --- a/switch/include/settings.h +++ b/switch/include/settings.h @@ -49,7 +49,7 @@ class Settings // the goal is to read/write inernal flat configuration file const std::map re_map = { {HOST_NAME, std::regex("^\\[\\s*(.+)\\s*\\]")}, - {HOST_ADDR, std::regex("^\\s*host_(?:ip|addr)\\s*=\\s*\"?((\\d+\\.\\d+\\.\\d+\\.\\d+)|([A-Za-z0-9-]{1,255}))\"?")}, + {HOST_ADDR, std::regex("^\\s*host_(?:ip|addr)\\s*=\\s*\"?([^\"]*)\"?")}, {PSN_ONLINE_ID, std::regex("^\\s*psn_online_id\\s*=\\s*\"?([\\w_-]+)\"?")}, {PSN_ACCOUNT_ID, std::regex("^\\s*psn_account_id\\s*=\\s*\"?([\\w/=+]+)\"?")}, {RP_KEY, std::regex("^\\s*rp_key\\s*=\\s*\"?([\\w/=+]+)\"?")}, @@ -61,7 +61,6 @@ class Settings }; ConfigurationItem ParseLine(std::string * line, std::string * value); - size_t GetB64encodeSize(size_t); public: // singleton configuration diff --git a/switch/src/main.cpp b/switch/src/main.cpp index 6b7c95e..1dd33de 100644 --- a/switch/src/main.cpp +++ b/switch/src/main.cpp @@ -28,8 +28,6 @@ bool appletMainLoop() // use a custom nintendo switch socket config // chiaki requiers many threads with udp/tcp sockets static const SocketInitConfig g_chiakiSocketInitConfig = { - .bsdsockets_version = 1, - .tcp_tx_buf_size = 0x8000, .tcp_rx_buf_size = 0x10000, .tcp_tx_buf_max_size = 0x40000, diff --git a/switch/src/settings.cpp b/switch/src/settings.cpp index fb39861..7d3300a 100644 --- a/switch/src/settings.cpp +++ b/switch/src/settings.cpp @@ -29,11 +29,7 @@ Settings::ConfigurationItem Settings::ParseLine(std::string *line, std::string * return UNKNOWN; } -size_t Settings::GetB64encodeSize(size_t in) -{ - // calculate base64 buffer size after encode - return ((4 * in / 3) + 3) & ~3; -} +#define B64_ENCODED_SIZE(in) (((4 * in / 3) + 3) & ~3) Settings *Settings::instance = nullptr; @@ -458,8 +454,7 @@ std::string Settings::GetHostRPKey(Host *host) { if(host->rp_key_data || host->registered) { - size_t rp_key_b64_sz = this->GetB64encodeSize(0x10); - char rp_key_b64[rp_key_b64_sz + 1] = { 0 }; + char rp_key_b64[B64_ENCODED_SIZE(0x10) + 1] = { 0 }; ChiakiErrorCode err; err = chiaki_base64_encode( host->rp_key, 0x10, @@ -502,8 +497,7 @@ std::string Settings::GetHostRPRegistKey(Host *host) { if(host->rp_key_data || host->registered) { - size_t rp_regist_key_b64_sz = this->GetB64encodeSize(CHIAKI_SESSION_AUTH_SIZE); - char rp_regist_key_b64[rp_regist_key_b64_sz + 1] = { 0 }; + char rp_regist_key_b64[B64_ENCODED_SIZE(CHIAKI_SESSION_AUTH_SIZE) + 1] = { 0 }; ChiakiErrorCode err; err = chiaki_base64_encode( (uint8_t *)host->rp_regist_key, CHIAKI_SESSION_AUTH_SIZE, diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index b1f0b46..f6ebc40 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -4,11 +4,13 @@ if(NOT CHIAKI_USE_SYSTEM_NANOPB) # nanopb ################## + add_definitions(-DPB_C99_STATIC_ASSERT) # Fix PB_STATIC_ASSERT on msvc without using C11 for now add_subdirectory(nanopb EXCLUDE_FROM_ALL) set(NANOPB_GENERATOR_PY "${CMAKE_CURRENT_SOURCE_DIR}/nanopb/generator/nanopb_generator.py" PARENT_SCOPE) add_library(nanopb INTERFACE) target_link_libraries(nanopb INTERFACE protobuf-nanopb-static) target_include_directories(nanopb INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/nanopb") + target_compile_definitions(nanopb INTERFACE -DPB_C99_STATIC_ASSERT) # see above add_library(Nanopb::nanopb ALIAS nanopb) endif() diff --git a/third-party/nanopb b/third-party/nanopb index ae9901f..afc499f 160000 --- a/third-party/nanopb +++ b/third-party/nanopb @@ -1 +1 @@ -Subproject commit ae9901f2a31500e8fdc93fa9804d24851c58bb1e +Subproject commit afc499f9a410fc9bbf6c9c48cdd8d8b199d49eb4