From 6526fffc023fa409276aba38ccc70d694229dad9 Mon Sep 17 00:00:00 2001 From: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> Date: Wed, 14 Oct 2026 16:09:40 -0400 Subject: [PATCH] fix: Steam touch controls load the layout Steam picked for DroidDeck's touch controller, including workshop layouts (#840) --- .../launcher/input/SteamTouchConfig.kt | 31 +++++++++- .../input/SteamTouchConfigFilesTest.kt | 60 +++++++++++++++++++ 2 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 app/src/test/java/com/droiddeck/launcher/input/SteamTouchConfigFilesTest.kt diff --git a/app/src/main/java/com/droiddeck/launcher/input/SteamTouchConfig.kt b/app/src/main/java/com/droiddeck/launcher/input/SteamTouchConfig.kt index 882a3a3dd..8f9d7a844 100644 --- a/app/src/main/java/com/droiddeck/launcher/input/SteamTouchConfig.kt +++ b/app/src/main/java/com/droiddeck/launcher/input/SteamTouchConfig.kt @@ -158,7 +158,8 @@ object SteamTouchConfig { fun load(context: Context, appId: Int): Config { val dir = configDir(context) - val file = dir?.let { autosaveConfig(it, appId) } + val file = dir?.let { deviceConfig(context, it, appId) } + ?: dir?.let { autosaveConfig(it, appId) } ?: loggedConfig(context, appId)?.takeIf { isTouchConfig(it) } ?: dir?.let { configsetConfig(context, it, appId) } if (file == null) { @@ -207,11 +208,16 @@ object SteamTouchConfig { null } - private fun configsetConfig(context: Context, dir: File, appId: Int): File? = try { - val set = KeyValues.parse(File(dir, CONFIGSET).readText()).child("controller_config")?.child(appId.toString()) + private fun configsetConfig(context: Context, dir: File, appId: Int): File? = entryConfig(context, dir, CONFIGSET, appId) + + private fun deviceConfig(context: Context, dir: File, appId: Int): File? = entryConfig(context, dir, DEVICE_CONFIGSET, appId) + + private fun entryConfig(context: Context, dir: File, configset: String, appId: Int): File? = try { + val set = KeyValues.parse(File(dir, configset).readText()).child("controller_config")?.child(appId.toString()) when { set == null -> null set.string("autosave") == "1" -> File(dir, "$appId/controller_mobile_touch.vdf") + set.string("workshop") != null -> workshopConfig(context, set.string("workshop")!!) set.string("template") != null -> File(steamRoot(context), "controller_base/templates/" + set.string("template")) else -> null }?.takeIf { it.isFile } @@ -219,7 +225,16 @@ object SteamTouchConfig { null } + internal fun workshopConfig(context: Context, id: String): File? { + if (!id.all(Char::isDigit)) return null + return File(steamRoot(context), "steamapps/workshop/content/$CONFIGS_APP/$id").listFiles() + ?.filter { it.isFile && (it.name.endsWith(".vdf") || it.name.endsWith(".bin")) } + ?.maxByOrNull { it.lastModified() } + } + private const val CONFIGSET = "configset_controller_mobile_touch.vdf" + private const val DEVICE_CONFIGSET = "configset_MT-DROIDDECK0001.vdf" + private const val CONFIGS_APP = 241100 private fun availability(mappings: KeyValues.Node): Map> { @@ -363,10 +378,20 @@ object SteamTouchConfig { tmp.writeText(text) if (!tmp.renameTo(target)) return null selectAutosave(dir, appId) + dropDeviceEntry(dir, appId) Log.i(TAG, "steam touch: saved layouts ${sets.keys} for app $appId to ${target.path}") return target } + private fun dropDeviceEntry(dir: File, appId: Int) { + val file = File(dir, DEVICE_CONFIGSET) + if (!file.isFile) return + val text = file.readText() + val block = Regex("""\n\s*"$appId"\s*\{[^}]*\}""") + if (!block.containsMatchIn(text)) return + File(file.path + ".tmp").apply { writeText(text.replace(block, "")) }.renameTo(file) + } + private fun selectAutosave(dir: File, appId: Int) { val file = File(dir, CONFIGSET) val text = if (file.isFile) file.readText() else "\"controller_config\"\n{\n}\n" diff --git a/app/src/test/java/com/droiddeck/launcher/input/SteamTouchConfigFilesTest.kt b/app/src/test/java/com/droiddeck/launcher/input/SteamTouchConfigFilesTest.kt new file mode 100644 index 000000000..eb48d0964 --- /dev/null +++ b/app/src/test/java/com/droiddeck/launcher/input/SteamTouchConfigFilesTest.kt @@ -0,0 +1,60 @@ +package com.droiddeck.launcher.input + +import com.droiddeck.launcher.runtime.LinuxRuntime +import java.io.File +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [33]) +class SteamTouchConfigFilesTest { + private val context = RuntimeEnvironment.getApplication() + private val steam = File(LinuxRuntime.rootDir(context), "root/.local/share/Steam") + private val dir = File(steam, "steamapps/common/Steam Controller Configs/123/config").apply { mkdirs() } + + @After fun tearDown() { steam.deleteRecursively() } + + private fun touch(title: String) = "\"controller_mappings\"\n{\n\t\"title\"\t\t\"$title\"\n\t\"controller_type\"\t\t\"controller_mobile_touch\"\n}\n" + + private fun configset(name: String, vararg entries: Pair) = File(dir, name).writeText( + "\"controller_config\"\n{\n" + entries.joinToString("") { (app, line) -> "\t\"$app\"\n\t{\n\t\t$line\n\t}\n" } + "}\n", + ) + + private fun autosave(app: Int, title: String) = File(dir, "$app/controller_mobile_touch.vdf").apply { parentFile!!.mkdirs(); writeText(touch(title)) } + + private fun workshop(id: String, title: String) = + File(steam, "steamapps/workshop/content/241100/$id/123_legacy.bin").apply { parentFile!!.mkdirs(); writeText(touch(title)) } + + @Test fun theDevicesWorkshopPickWinsOverAnOlderAutosave() { + autosave(204360, "old autosave") + val picked = workshop("1764689049", "official") + configset("configset_controller_mobile_touch.vdf", "204360" to "\"autosave\"\t\t\"1\"") + configset("configset_MT-DROIDDECK0001.vdf", "204360" to "\"workshop\"\t\t\"1764689049\"") + assertEquals(picked, SteamTouchConfig.load(context, 204360).file) + } + + @Test fun aWorkshopPickInTheTypeConfigsetIsRead() { + val picked = workshop("42", "community") + configset("configset_controller_mobile_touch.vdf", "413150" to "\"workshop\"\t\t\"42\"") + assertEquals(picked, SteamTouchConfig.load(context, 413150).file) + } + + @Test fun savingAnEditDropsTheDevicePickSoTheEditLoads() { + workshop("1764689049", "official") + configset("configset_MT-DROIDDECK0001.vdf", "204360" to "\"workshop\"\t\t\"1764689049\"", "8400" to "\"workshop\"\t\t\"7\"") + val config = SteamTouchConfig.load(context, 204360) + val saved = SteamTouchConfig.saveLayouts(context, 204360, config, mapOf(1 to emptyList())) + assertEquals(File(dir, "204360/controller_mobile_touch.vdf"), saved) + val device = File(dir, "configset_MT-DROIDDECK0001.vdf").readText() + assertFalse(device.contains("\"204360\"")) + assertTrue(device.contains("\"8400\"")) + assertEquals(saved, SteamTouchConfig.load(context, 204360).file) + } +}