fix: Steam touch controls load the layout Steam picked for DroidDeck's touch controller, including workshop layouts (#840)

This commit is contained in:
Kurt Himebauch authored and GitHub committed 2026-10-14 16:09:40 -04:00
1 parent eda1e9b9c6
commit f635e92498
2 files changed
+88 -3

No files matched your search

@@ -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<Int, Set<Int>> {
@@ -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"
@@ -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<String, String>) = 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)
}
}