diff --git a/README.md b/README.md index b84b73d..5129f3a 100644 --- a/README.md +++ b/README.md @@ -12,17 +12,9 @@ for Linux, FreeBSD, OpenBSD, Android, macOS, Windows, Nintendo Switch and potent ![Screenshot](assets/screenshot.png) -## Features - -Everything necessary for a full streaming session, including the initial -registration and wakeup of the console, is supported. -The following features however are yet to be implemented: -* Rumble -* Accelerometer/Gyroscope - ## Installing -You can either download a pre-built release (easier) or build Chiaki from source. +You can either download a pre-built release or build Chiaki from source. ### Downloading a Release @@ -48,7 +40,7 @@ make ``` For more detailed platform-specific instructions, see [doc/platform-build.md](doc/platform-build.md). -in + ## Usage If your Console is on your local network, is turned on or in standby mode and does not have Discovery explicitly disabled, Chiaki should find it. diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 14b0466..fa14662 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -5,6 +5,7 @@ + CallVoidMethod(env, session->java_session, + session->java_session_event_rumble_meth, + (jint)event->rumble.left, + (jint)event->rumble.right); + break; + default: + break; } (*global_vm)->DetachCurrentThread(global_vm); @@ -310,6 +319,7 @@ JNIEXPORT void JNICALL JNI_FCN(sessionCreate)(JNIEnv *env, jobject obj, jobject session->java_session_event_connected_meth = E->GetMethodID(env, session->java_session_class, "eventConnected", "()V"); session->java_session_event_login_pin_request_meth = E->GetMethodID(env, session->java_session_class, "eventLoginPinRequest", "(Z)V"); session->java_session_event_quit_meth = E->GetMethodID(env, session->java_session_class, "eventQuit", "(ILjava/lang/String;)V"); + session->java_session_event_rumble_meth = E->GetMethodID(env, session->java_session_class, "eventRumble", "(II)V"); jclass controller_state_class = E->FindClass(env, BASE_PACKAGE"/ControllerState"); session->java_controller_state_buttons = E->GetFieldID(env, controller_state_class, "buttons", "I"); diff --git a/android/app/src/main/java/com/metallic/chiaki/common/Preferences.kt b/android/app/src/main/java/com/metallic/chiaki/common/Preferences.kt index c9691b5..d779c5a 100644 --- a/android/app/src/main/java/com/metallic/chiaki/common/Preferences.kt +++ b/android/app/src/main/java/com/metallic/chiaki/common/Preferences.kt @@ -68,11 +68,16 @@ class Preferences(context: Context) get() = sharedPreferences.getBoolean(onScreenControlsEnabledKey, true) set(value) { sharedPreferences.edit().putBoolean(onScreenControlsEnabledKey, value).apply() } - val touchpadOnlyEnabledKey get() = resources.getString(R.string.preferences_touchpad_only_key) + val touchpadOnlyEnabledKey get() = resources.getString(R.string.preferences_touchpad_only_enabled_key) var touchpadOnlyEnabled get() = sharedPreferences.getBoolean(touchpadOnlyEnabledKey, false) set(value) { sharedPreferences.edit().putBoolean(touchpadOnlyEnabledKey, value).apply() } + val rumbleEnabledKey get() = resources.getString(R.string.preferences_rumble_enabled_key) + var rumbleEnabled + get() = sharedPreferences.getBoolean(rumbleEnabledKey, true) + set(value) { sharedPreferences.edit().putBoolean(rumbleEnabledKey, value).apply() } + val logVerboseKey get() = resources.getString(R.string.preferences_log_verbose_key) var logVerbose get() = sharedPreferences.getBoolean(logVerboseKey, false) 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 0700a7c..023da90 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 @@ -289,6 +289,7 @@ sealed class Event object ConnectedEvent: Event() data class LoginPinRequestEvent(val pinIncorrect: Boolean): Event() data class QuitEvent(val reason: QuitReason, val reasonString: String?): Event() +data class RumbleEvent(val left: UByte, val right: UByte): Event() class CreateError(val errorCode: ErrorCode): Exception("Failed to create a native object: $errorCode") @@ -344,6 +345,11 @@ class Session(connectInfo: ConnectInfo, logFile: String?, logVerbose: Boolean) event(QuitEvent(QuitReason(reasonValue), reasonString)) } + private fun eventRumble(left: Int, right: Int) + { + event(RumbleEvent(left.toUByte(), right.toUByte())) + } + fun setSurface(surface: Surface) { ChiakiNative.sessionSetSurface(nativePtr, surface) diff --git a/android/app/src/main/java/com/metallic/chiaki/session/StreamSession.kt b/android/app/src/main/java/com/metallic/chiaki/session/StreamSession.kt index 059840c..d7f127f 100644 --- a/android/app/src/main/java/com/metallic/chiaki/session/StreamSession.kt +++ b/android/app/src/main/java/com/metallic/chiaki/session/StreamSession.kt @@ -25,6 +25,8 @@ class StreamSession(val connectInfo: ConnectInfo, val logManager: LogManager, va private val _state = MutableLiveData(StreamStateIdle) val state: LiveData get() = _state + private val _rumbleState = MutableLiveData(RumbleEvent(0U, 0U)) + val rumbleState: LiveData get() = _rumbleState var surfaceTexture: SurfaceTexture? = null @@ -86,6 +88,7 @@ class StreamSession(val connectInfo: ConnectInfo, val logManager: LogManager, va event.pinIncorrect ) ) + is RumbleEvent -> _rumbleState.postValue(event) } } diff --git a/android/app/src/main/java/com/metallic/chiaki/settings/SettingsFragment.kt b/android/app/src/main/java/com/metallic/chiaki/settings/SettingsFragment.kt index f38f619..7a4506e 100644 --- a/android/app/src/main/java/com/metallic/chiaki/settings/SettingsFragment.kt +++ b/android/app/src/main/java/com/metallic/chiaki/settings/SettingsFragment.kt @@ -26,6 +26,7 @@ class DataStore(val preferences: Preferences): PreferenceDataStore() { preferences.logVerboseKey -> preferences.logVerbose preferences.swapCrossMoonKey -> preferences.swapCrossMoon + preferences.rumbleEnabledKey -> preferences.rumbleEnabled else -> defValue } @@ -35,6 +36,7 @@ class DataStore(val preferences: Preferences): PreferenceDataStore() { preferences.logVerboseKey -> preferences.logVerbose = value preferences.swapCrossMoonKey -> preferences.swapCrossMoon = value + preferences.rumbleEnabledKey -> preferences.rumbleEnabled = value } } 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 903b408..d94fa0d 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 @@ -9,6 +9,8 @@ import android.content.res.Configuration import android.graphics.Matrix import android.os.Bundle import android.os.Handler +import android.os.VibrationEffect +import android.os.Vibrator import android.view.KeyEvent import android.view.MotionEvent import android.view.TextureView @@ -30,6 +32,7 @@ import com.metallic.chiaki.session.* import com.metallic.chiaki.touchcontrols.TouchpadOnlyFragment import com.metallic.chiaki.touchcontrols.TouchControlsFragment import kotlinx.android.synthetic.main.activity_stream.* +import kotlin.math.min private sealed class DialogContents private object StreamQuitDialog: DialogContents() @@ -105,6 +108,19 @@ class StreamActivity : AppCompatActivity(), View.OnSystemUiVisibilityChangeListe textureView.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> adjustTextureViewAspect() } + + if(Preferences(this).rumbleEnabled) + { + val vibrator = getSystemService(VIBRATOR_SERVICE) as Vibrator + viewModel.session.rumbleState.observe(this, Observer { + val amplitude = min(255, (it.left.toInt() + it.right.toInt()) / 2) + vibrator.cancel() + if(amplitude == 0) + return@Observer + if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) + vibrator.vibrate(VibrationEffect.createOneShot(1000, amplitude)) + }) + } } override fun onAttachFragment(fragment: Fragment) diff --git a/android/app/src/main/res/drawable/ic_rumble.xml b/android/app/src/main/res/drawable/ic_rumble.xml new file mode 100644 index 0000000..4ecdb61 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_rumble.xml @@ -0,0 +1,9 @@ + + + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 0f54522..0f7161d 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -88,6 +88,8 @@ H265 (PS5 only) Swap Cross/Moon and Box/Pyramid Buttons Swap face buttons if default mapping is incorrect (e.g. for 8BitDo controllers) + Rumble + Use phone vibration motor for rumble Are you sure you want to delete the registered console %s with ID %s? Are you sure you want to delete the console entry for %s? Keep @@ -101,7 +103,8 @@ discovery_enabled on_screen_controls_enabled - touchpad_only_enabled + touchpad_only_enabled + rumble_enabled log_verbose import_settings export_settings diff --git a/android/app/src/main/res/xml/preferences.xml b/android/app/src/main/res/xml/preferences.xml index 0416f6e..e87afdb 100644 --- a/android/app/src/main/res/xml/preferences.xml +++ b/android/app/src/main/res/xml/preferences.xml @@ -19,6 +19,12 @@ app:summary="@string/preferences_swap_cross_moon_summary" app:icon="@drawable/ic_gamepad" /> + +