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

-## 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" />
+
+