mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-12 03:21:08 +00:00
Audio: keep at least 40 ms queued for the DirectAudio relay; logs: Share latest logs
- module-directaudio-sink keeps the relay's ring at least 40 ms deep. The relay starts at 8 ms, which the producer inside proot missed while the client loads (two underruns on the FIT's fresh install, steady stutter reported on another phone). - Setup > Session logs > Share latest logs zips the newest session folder, wherever it was written (Downloads, or app-private files when Downloads is not writable), and opens the share sheet via a FileProvider scoped to that zip.
This commit is contained in:
1 parent
fe3b9892a7
commit
019928e783
6 files changed
+81
No files matched your search
@@ -95,5 +95,15 @@
|
||||
android:name=".session.SessionService"
|
||||
android:exported="false"
|
||||
android:stopWithTask="false" />
|
||||
<!-- Hands a zipped session folder to the share sheet; nothing else is exposed. -->
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.logs"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/log_share_paths" />
|
||||
</provider>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -32,6 +32,7 @@ import com.steamdeck.launcher.ui.DesktopAppsDialog
|
||||
import com.steamdeck.launcher.ui.PackageRow
|
||||
import com.steamdeck.launcher.session.OfflineMode
|
||||
import com.steamdeck.launcher.session.ProtonExtras
|
||||
import com.steamdeck.launcher.session.SessionLogShare
|
||||
import com.steamdeck.launcher.session.SessionPrefs
|
||||
import com.steamdeck.launcher.ui.ProtonDialog
|
||||
import com.steamdeck.launcher.ui.ProtonRow
|
||||
@@ -240,6 +241,15 @@ class MainActivity : ComponentActivity() {
|
||||
SessionPrefs.setLogsEnabled(this, !SessionPrefs.logsEnabled(this))
|
||||
logsEnabled = SessionPrefs.logsEnabled(this)
|
||||
},
|
||||
onShareLogs = {
|
||||
Thread({
|
||||
val zip = runCatching { SessionLogShare.zipLatest(this) }.getOrNull()
|
||||
ui.post {
|
||||
if (zip == null) android.widget.Toast.makeText(this, "No session logs yet: run a session first.", android.widget.Toast.LENGTH_LONG).show()
|
||||
else startActivity(SessionLogShare.shareIntent(this, zip))
|
||||
}
|
||||
}, "share-logs").start()
|
||||
},
|
||||
onOffline = {
|
||||
OfflineMode.setEnabled(this, !OfflineMode.enabled(this))
|
||||
offline = OfflineMode.enabled(this)
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.steamdeck.launcher.session
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.core.content.FileProvider
|
||||
import com.steamdeck.launcher.runtime.LinuxRuntime
|
||||
import java.io.File
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
/**
|
||||
* Zips the most recent session's log folder and hands it to Android's share sheet. Works where
|
||||
* the folder could not be written to Downloads (no storage permission, or a device that refuses
|
||||
* it) and landed in the app's private files instead, which a user cannot otherwise reach.
|
||||
*/
|
||||
object SessionLogShare {
|
||||
/** The newest session folder in either place logs are written, or null if there is none. */
|
||||
fun latest(context: Context): File? =
|
||||
listOf(LinuxRuntime.debugLogDir(), File(context.filesDir, "logs"))
|
||||
.flatMap { dir -> dir.listFiles { f -> f.isDirectory && f.name.startsWith("session-") }?.toList() ?: emptyList() }
|
||||
.maxByOrNull { it.name }
|
||||
|
||||
/** Builds the zip (blocking). Returns null when there is no session to share. */
|
||||
fun zipLatest(context: Context): File? {
|
||||
val folder = latest(context) ?: return null
|
||||
val out = File(context.cacheDir, "shared-logs").apply { deleteRecursively(); mkdirs() }
|
||||
val zip = File(out, "SteamDeck-${folder.name}.zip")
|
||||
ZipOutputStream(zip.outputStream().buffered()).use { z ->
|
||||
folder.walkTopDown().filter { it.isFile }.forEach { f ->
|
||||
z.putNextEntry(ZipEntry(folder.name + "/" + f.relativeTo(folder).path))
|
||||
f.inputStream().use { it.copyTo(z) }
|
||||
z.closeEntry()
|
||||
}
|
||||
}
|
||||
return zip
|
||||
}
|
||||
|
||||
fun shareIntent(context: Context, zip: File): Intent {
|
||||
val uri = FileProvider.getUriForFile(context, context.packageName + ".logs", zip)
|
||||
val send = Intent(Intent.ACTION_SEND)
|
||||
.setType("application/zip")
|
||||
.putExtra(Intent.EXTRA_STREAM, uri)
|
||||
.putExtra(Intent.EXTRA_SUBJECT, zip.nameWithoutExtension)
|
||||
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
send.clipData = ClipData.newRawUri(zip.name, uri)
|
||||
return Intent.createChooser(send, "Share session logs").addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}
|
||||
}
|
||||
@@ -150,6 +150,7 @@ class FrontEndActions(
|
||||
val onRoms: () -> Unit,
|
||||
val onFiles: () -> Unit,
|
||||
val onLogs: () -> Unit,
|
||||
val onShareLogs: () -> Unit = {},
|
||||
val onOffline: () -> Unit,
|
||||
val onEmulatorHelp: () -> Unit,
|
||||
val onCredits: () -> Unit,
|
||||
@@ -363,6 +364,7 @@ private fun Rail(
|
||||
) {
|
||||
MenuItem("On", checked = s.logsEnabled) { if (!s.logsEnabled) a.onLogs(); menus.open = null }
|
||||
MenuItem("Off", checked = !s.logsEnabled) { if (s.logsEnabled) a.onLogs(); menus.open = null }
|
||||
MenuItem("Share latest logs", checked = false) { a.onShareLogs(); menus.open = null }
|
||||
}
|
||||
}
|
||||
Box {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths>
|
||||
<cache-path name="shared_logs" path="shared-logs/" />
|
||||
</paths>
|
||||
@@ -63,6 +63,10 @@ PA_MODULE_USAGE(
|
||||
#define STATS_EVERY_USEC (30 * PA_USEC_PER_SEC)
|
||||
/* Never queue more than this ahead of the relay's target, whatever it asks for. */
|
||||
#define MAX_AHEAD_MS 250
|
||||
/* Always keep at least this much queued. The relay starts its target at two bursts (8 ms), which
|
||||
* a producer inside proot misses while the client is loading: two underruns in the first seconds
|
||||
* on the FIT, steady stutter on a slower phone. 40 ms of slack costs 40 ms of latency at most. */
|
||||
#define MIN_AHEAD_MS 40
|
||||
|
||||
static const char* const valid_modargs[] = {
|
||||
"socket", "sink_name", "sink_properties", "volume", "performance_mode", "adaptive", NULL
|
||||
@@ -239,6 +243,8 @@ static int service_ring(struct userdata *u) {
|
||||
return -1;
|
||||
}
|
||||
target = (uint32_t) (r->target_frames > 0 ? r->target_frames : 2 * u->burst);
|
||||
if (target < u->ss.rate / 1000 * MIN_AHEAD_MS)
|
||||
target = u->ss.rate / 1000 * MIN_AHEAD_MS;
|
||||
if (target > (uint32_t) (u->ss.rate / 1000 * MAX_AHEAD_MS))
|
||||
target = (uint32_t) (u->ss.rate / 1000 * MAX_AHEAD_MS);
|
||||
avail = da_ring_avail(r);
|
||||
|
||||
Reference in new issue
Block a user