Add Rumble to Android

This commit is contained in:
Florian Märkl 2021-01-13 15:00:53 +01:00
commit 3b85e147b6
No known key found for this signature in database
GPG key ID: 125BC8A5A6A1E857
11 changed files with 65 additions and 12 deletions

View file

@ -12,17 +12,9 @@ for Linux, FreeBSD, OpenBSD, Android, macOS, Windows, Nintendo Switch and potent
![Screenshot](assets/screenshot.png) ![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 ## 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 ### Downloading a Release
@ -48,7 +40,7 @@ make
``` ```
For more detailed platform-specific instructions, see [doc/platform-build.md](doc/platform-build.md). For more detailed platform-specific instructions, see [doc/platform-build.md](doc/platform-build.md).
in
## Usage ## 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. 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.

View file

@ -5,6 +5,7 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.VIBRATE" />
<application <application
android:allowBackup="true" android:allowBackup="true"

View file

@ -133,6 +133,7 @@ typedef struct android_chiaki_session_t
jmethodID java_session_event_connected_meth; jmethodID java_session_event_connected_meth;
jmethodID java_session_event_login_pin_request_meth; jmethodID java_session_event_login_pin_request_meth;
jmethodID java_session_event_quit_meth; jmethodID java_session_event_quit_meth;
jmethodID java_session_event_rumble_meth;
jfieldID java_controller_state_buttons; jfieldID java_controller_state_buttons;
jfieldID java_controller_state_l2_state; jfieldID java_controller_state_l2_state;
jfieldID java_controller_state_r2_state; jfieldID java_controller_state_r2_state;
@ -192,6 +193,14 @@ static void android_chiaki_event_cb(ChiakiEvent *event, void *user)
free(reason_str); free(reason_str);
break; break;
} }
case CHIAKI_EVENT_RUMBLE:
E->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); (*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_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_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_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"); jclass controller_state_class = E->FindClass(env, BASE_PACKAGE"/ControllerState");
session->java_controller_state_buttons = E->GetFieldID(env, controller_state_class, "buttons", "I"); session->java_controller_state_buttons = E->GetFieldID(env, controller_state_class, "buttons", "I");

View file

@ -68,11 +68,16 @@ class Preferences(context: Context)
get() = sharedPreferences.getBoolean(onScreenControlsEnabledKey, true) get() = sharedPreferences.getBoolean(onScreenControlsEnabledKey, true)
set(value) { sharedPreferences.edit().putBoolean(onScreenControlsEnabledKey, value).apply() } 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 var touchpadOnlyEnabled
get() = sharedPreferences.getBoolean(touchpadOnlyEnabledKey, false) get() = sharedPreferences.getBoolean(touchpadOnlyEnabledKey, false)
set(value) { sharedPreferences.edit().putBoolean(touchpadOnlyEnabledKey, value).apply() } 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) val logVerboseKey get() = resources.getString(R.string.preferences_log_verbose_key)
var logVerbose var logVerbose
get() = sharedPreferences.getBoolean(logVerboseKey, false) get() = sharedPreferences.getBoolean(logVerboseKey, false)

View file

@ -289,6 +289,7 @@ sealed class Event
object ConnectedEvent: Event() object ConnectedEvent: Event()
data class LoginPinRequestEvent(val pinIncorrect: Boolean): Event() data class LoginPinRequestEvent(val pinIncorrect: Boolean): Event()
data class QuitEvent(val reason: QuitReason, val reasonString: String?): 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") 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)) event(QuitEvent(QuitReason(reasonValue), reasonString))
} }
private fun eventRumble(left: Int, right: Int)
{
event(RumbleEvent(left.toUByte(), right.toUByte()))
}
fun setSurface(surface: Surface) fun setSurface(surface: Surface)
{ {
ChiakiNative.sessionSetSurface(nativePtr, surface) ChiakiNative.sessionSetSurface(nativePtr, surface)

View file

@ -25,6 +25,8 @@ class StreamSession(val connectInfo: ConnectInfo, val logManager: LogManager, va
private val _state = MutableLiveData<StreamState>(StreamStateIdle) private val _state = MutableLiveData<StreamState>(StreamStateIdle)
val state: LiveData<StreamState> get() = _state val state: LiveData<StreamState> get() = _state
private val _rumbleState = MutableLiveData<RumbleEvent>(RumbleEvent(0U, 0U))
val rumbleState: LiveData<RumbleEvent> get() = _rumbleState
var surfaceTexture: SurfaceTexture? = null var surfaceTexture: SurfaceTexture? = null
@ -86,6 +88,7 @@ class StreamSession(val connectInfo: ConnectInfo, val logManager: LogManager, va
event.pinIncorrect event.pinIncorrect
) )
) )
is RumbleEvent -> _rumbleState.postValue(event)
} }
} }

View file

@ -26,6 +26,7 @@ class DataStore(val preferences: Preferences): PreferenceDataStore()
{ {
preferences.logVerboseKey -> preferences.logVerbose preferences.logVerboseKey -> preferences.logVerbose
preferences.swapCrossMoonKey -> preferences.swapCrossMoon preferences.swapCrossMoonKey -> preferences.swapCrossMoon
preferences.rumbleEnabledKey -> preferences.rumbleEnabled
else -> defValue else -> defValue
} }
@ -35,6 +36,7 @@ class DataStore(val preferences: Preferences): PreferenceDataStore()
{ {
preferences.logVerboseKey -> preferences.logVerbose = value preferences.logVerboseKey -> preferences.logVerbose = value
preferences.swapCrossMoonKey -> preferences.swapCrossMoon = value preferences.swapCrossMoonKey -> preferences.swapCrossMoon = value
preferences.rumbleEnabledKey -> preferences.rumbleEnabled = value
} }
} }

View file

@ -9,6 +9,8 @@ import android.content.res.Configuration
import android.graphics.Matrix import android.graphics.Matrix
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.VibrationEffect
import android.os.Vibrator
import android.view.KeyEvent import android.view.KeyEvent
import android.view.MotionEvent import android.view.MotionEvent
import android.view.TextureView import android.view.TextureView
@ -30,6 +32,7 @@ import com.metallic.chiaki.session.*
import com.metallic.chiaki.touchcontrols.TouchpadOnlyFragment import com.metallic.chiaki.touchcontrols.TouchpadOnlyFragment
import com.metallic.chiaki.touchcontrols.TouchControlsFragment import com.metallic.chiaki.touchcontrols.TouchControlsFragment
import kotlinx.android.synthetic.main.activity_stream.* import kotlinx.android.synthetic.main.activity_stream.*
import kotlin.math.min
private sealed class DialogContents private sealed class DialogContents
private object StreamQuitDialog: DialogContents() private object StreamQuitDialog: DialogContents()
@ -105,6 +108,19 @@ class StreamActivity : AppCompatActivity(), View.OnSystemUiVisibilityChangeListe
textureView.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> textureView.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
adjustTextureViewAspect() 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) override fun onAttachFragment(fragment: Fragment)

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?android:attr/textColorPrimary"
android:pathData="M0,15h2L2,9L0,9v6zM3,17h2L5,7L3,7v10zM22,9v6h2L24,9h-2zM19,17h2L21,7h-2v10zM16.5,3h-9C6.67,3 6,3.67 6,4.5v15c0,0.83 0.67,1.5 1.5,1.5h9c0.83,0 1.5,-0.67 1.5,-1.5v-15c0,-0.83 -0.67,-1.5 -1.5,-1.5zM16,19L8,19L8,5h8v14z"/>
</vector>

View file

@ -88,6 +88,8 @@
<string name="preferences_codec_title_h265">H265 (PS5 only)</string> <string name="preferences_codec_title_h265">H265 (PS5 only)</string>
<string name="preferences_swap_cross_moon_title">Swap Cross/Moon and Box/Pyramid Buttons</string> <string name="preferences_swap_cross_moon_title">Swap Cross/Moon and Box/Pyramid Buttons</string>
<string name="preferences_swap_cross_moon_summary">Swap face buttons if default mapping is incorrect (e.g. for 8BitDo controllers)</string> <string name="preferences_swap_cross_moon_summary">Swap face buttons if default mapping is incorrect (e.g. for 8BitDo controllers)</string>
<string name="preferences_rumble_enabled_title">Rumble</string>
<string name="preferences_rumble_enabled_summary">Use phone vibration motor for rumble</string>
<string name="alert_message_delete_registered_host">Are you sure you want to delete the registered console %s with ID %s?</string> <string name="alert_message_delete_registered_host">Are you sure you want to delete the registered console %s with ID %s?</string>
<string name="alert_message_delete_manual_host">Are you sure you want to delete the console entry for %s?</string> <string name="alert_message_delete_manual_host">Are you sure you want to delete the console entry for %s?</string>
<string name="action_keep">Keep</string> <string name="action_keep">Keep</string>
@ -101,7 +103,8 @@
<!-- Don't localize these --> <!-- Don't localize these -->
<string name="preferences_discovery_enabled_key">discovery_enabled</string> <string name="preferences_discovery_enabled_key">discovery_enabled</string>
<string name="preferences_on_screen_controls_enabled_key">on_screen_controls_enabled</string> <string name="preferences_on_screen_controls_enabled_key">on_screen_controls_enabled</string>
<string name="preferences_touchpad_only_key">touchpad_only_enabled</string> <string name="preferences_touchpad_only_enabled_key">touchpad_only_enabled</string>
<string name="preferences_rumble_enabled_key">rumble_enabled</string>
<string name="preferences_log_verbose_key">log_verbose</string> <string name="preferences_log_verbose_key">log_verbose</string>
<string name="preferences_import_settings_key">import_settings</string> <string name="preferences_import_settings_key">import_settings</string>
<string name="preferences_export_settings_key">export_settings</string> <string name="preferences_export_settings_key">export_settings</string>

View file

@ -19,6 +19,12 @@
app:summary="@string/preferences_swap_cross_moon_summary" app:summary="@string/preferences_swap_cross_moon_summary"
app:icon="@drawable/ic_gamepad" /> app:icon="@drawable/ic_gamepad" />
<SwitchPreference
app:key="@string/preferences_rumble_enabled_key"
app:title="@string/preferences_rumble_enabled_title"
app:summary="@string/preferences_rumble_enabled_summary"
app:icon="@drawable/ic_rumble" />
<SwitchPreference <SwitchPreference
app:key="@string/preferences_log_verbose_key" app:key="@string/preferences_log_verbose_key"
app:title="@string/preferences_log_verbose_title" app:title="@string/preferences_log_verbose_title"