mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-10-14 19:27:09 -04:00
Type into the session: hardware keys to the compositor, and a log path that always exists
The session script redirects its own output into BL_LOG with exec, and a redirection a non-interactive shell cannot open ends that shell — so a denied storage permission would have killed the session rather than just losing the log. Downloads is used once proven writable, files/logs otherwise. Keyboard events now reach wl_keyboard, which is what the client's first-run login needs on a device with a keyboard attached.
This commit is contained in:
1 parent
9b3dd05f4f
commit
c22cbc9f43
3 files changed
+115
-3
No files matched your search
@@ -22,6 +22,7 @@ import com.the412banner.steamdeck.core.EnvironmentComponent
|
||||
import com.the412banner.steamdeck.core.FileUtils
|
||||
import com.the412banner.steamdeck.core.ProcessHelper
|
||||
import com.the412banner.steamdeck.gpu.TurnipDriver
|
||||
import com.the412banner.steamdeck.input.EvdevKeys
|
||||
import com.the412banner.steamdeck.input.FakeInputWriter
|
||||
import com.the412banner.steamdeck.input.PadBridge
|
||||
import com.the412banner.steamdeck.runtime.LinuxNetworkLinkComponent
|
||||
@@ -173,7 +174,7 @@ class SessionActivity : Activity(), SurfaceHolder.Callback {
|
||||
val sessionRoot = LinuxRuntime.sessionRoot(this).apply { mkdirs() }
|
||||
stageSessionFiles(root)
|
||||
|
||||
val logDir = LinuxRuntime.debugLogDir().apply { mkdirs() }
|
||||
val logDir = logDirectory()
|
||||
val stamp = SimpleDateFormat("yyyyMMdd-HHmmss", Locale.US).format(Date())
|
||||
sessionLog = File(logDir, "session-$stamp.log")
|
||||
|
||||
@@ -273,6 +274,29 @@ class SessionActivity : Activity(), SurfaceHolder.Callback {
|
||||
Log.i(TAG, "session pid $sessionPid, log ${sessionLog.path}")
|
||||
}
|
||||
|
||||
/**
|
||||
* Where the session writes its log. Downloads is the point — a failed run is handed over as a
|
||||
* folder rather than dug out of app-private storage — but the session script redirects its own
|
||||
* output there with `exec`, and a redirection a non-interactive shell cannot open ends that
|
||||
* shell. So a public directory is used only once it is proven writable; otherwise the app's
|
||||
* own files directory, which is bound into the session anyway, stands in.
|
||||
*/
|
||||
private fun logDirectory(): File {
|
||||
val public = LinuxRuntime.debugLogDir()
|
||||
if (public.isDirectory || public.mkdirs()) {
|
||||
val probe = File(public, ".writable")
|
||||
try {
|
||||
if (probe.createNewFile() || probe.isFile) {
|
||||
probe.delete()
|
||||
return public
|
||||
}
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
}
|
||||
Log.w(TAG, "$public is not writable (storage permission?); logging to files/logs")
|
||||
return File(filesDir, "logs").apply { mkdirs() }
|
||||
}
|
||||
|
||||
/**
|
||||
* The libraries and scripts the session runs, refreshed from the apk at every launch.
|
||||
*
|
||||
@@ -361,12 +385,28 @@ class SessionActivity : Activity(), SurfaceHolder.Callback {
|
||||
}
|
||||
|
||||
private fun sessionLogName(): String =
|
||||
if (::sessionLog.isInitialized) "${LinuxRuntime.DEBUG_LOG_DIR}/${sessionLog.name}" else "-"
|
||||
if (::sessionLog.isInitialized) sessionLog.path else "-"
|
||||
|
||||
// ── Input ───────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||
if (padBridge?.onKeyEvent(event) == true) return true
|
||||
// A hardware keyboard, forwarded to the compositor's wl_keyboard. This is how an account
|
||||
// name and password get typed on the client's first run; the client's own on-screen
|
||||
// keyboard covers a device without one, driven by touch or the pad.
|
||||
if (compositorStarted && event.device != null && !PadBridge.isFromController(event.device)) {
|
||||
val down = event.action == KeyEvent.ACTION_DOWN
|
||||
if (down || event.action == KeyEvent.ACTION_UP) {
|
||||
// An unmapped key may still be a real one on a foreign layout: its scan code is
|
||||
// the evdev code the kernel gave Android in the first place.
|
||||
var evdev = EvdevKeys.fromKeyCode(event.keyCode)
|
||||
if (evdev < 0 && event.scanCode > 0) evdev = event.scanCode
|
||||
if (evdev > 0) {
|
||||
WaylandCompositor.nativeSendKey(evdev, if (down) 1 else 0)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.dispatchKeyEvent(event)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.the412banner.steamdeck.input;
|
||||
|
||||
import android.view.KeyEvent;
|
||||
|
||||
/**
|
||||
* Android key codes to Linux evdev codes, for the keys we inject into the compositor's
|
||||
* wl_keyboard. A hardware keyboard is how an account name and password are typed on the client's
|
||||
* first run, so this matters before any game does.
|
||||
*
|
||||
* <p>evdev's order is not alphabetical: the numbers are the physical positions of a PC keyboard,
|
||||
* which is what the compositor's xkb keymap is written against. Table taken from Bannerlator's
|
||||
* Wayland path unchanged.
|
||||
*/
|
||||
public final class EvdevKeys {
|
||||
private EvdevKeys() {}
|
||||
|
||||
public static int fromKeyCode(int kc) {
|
||||
switch (kc) {
|
||||
// Letters (evdev order is NOT alphabetical)
|
||||
case KeyEvent.KEYCODE_A: return 30; case KeyEvent.KEYCODE_B: return 48;
|
||||
case KeyEvent.KEYCODE_C: return 46; case KeyEvent.KEYCODE_D: return 32;
|
||||
case KeyEvent.KEYCODE_E: return 18; case KeyEvent.KEYCODE_F: return 33;
|
||||
case KeyEvent.KEYCODE_G: return 34; case KeyEvent.KEYCODE_H: return 35;
|
||||
case KeyEvent.KEYCODE_I: return 23; case KeyEvent.KEYCODE_J: return 36;
|
||||
case KeyEvent.KEYCODE_K: return 37; case KeyEvent.KEYCODE_L: return 38;
|
||||
case KeyEvent.KEYCODE_M: return 50; case KeyEvent.KEYCODE_N: return 49;
|
||||
case KeyEvent.KEYCODE_O: return 24; case KeyEvent.KEYCODE_P: return 25;
|
||||
case KeyEvent.KEYCODE_Q: return 16; case KeyEvent.KEYCODE_R: return 19;
|
||||
case KeyEvent.KEYCODE_S: return 31; case KeyEvent.KEYCODE_T: return 20;
|
||||
case KeyEvent.KEYCODE_U: return 22; case KeyEvent.KEYCODE_V: return 47;
|
||||
case KeyEvent.KEYCODE_W: return 17; case KeyEvent.KEYCODE_X: return 45;
|
||||
case KeyEvent.KEYCODE_Y: return 21; case KeyEvent.KEYCODE_Z: return 44;
|
||||
// Digit row
|
||||
case KeyEvent.KEYCODE_1: return 2; case KeyEvent.KEYCODE_2: return 3;
|
||||
case KeyEvent.KEYCODE_3: return 4; case KeyEvent.KEYCODE_4: return 5;
|
||||
case KeyEvent.KEYCODE_5: return 6; case KeyEvent.KEYCODE_6: return 7;
|
||||
case KeyEvent.KEYCODE_7: return 8; case KeyEvent.KEYCODE_8: return 9;
|
||||
case KeyEvent.KEYCODE_9: return 10; case KeyEvent.KEYCODE_0: return 11;
|
||||
// Whitespace / edit
|
||||
case KeyEvent.KEYCODE_ENTER: return 28; case KeyEvent.KEYCODE_NUMPAD_ENTER: return 28;
|
||||
case KeyEvent.KEYCODE_SPACE: return 57; case KeyEvent.KEYCODE_TAB: return 15;
|
||||
case KeyEvent.KEYCODE_DEL: return 14; /* backspace */
|
||||
case KeyEvent.KEYCODE_FORWARD_DEL: return 111; case KeyEvent.KEYCODE_ESCAPE: return 1;
|
||||
// Modifiers
|
||||
case KeyEvent.KEYCODE_SHIFT_LEFT: return 42; case KeyEvent.KEYCODE_SHIFT_RIGHT: return 54;
|
||||
case KeyEvent.KEYCODE_CTRL_LEFT: return 29; case KeyEvent.KEYCODE_CTRL_RIGHT: return 97;
|
||||
case KeyEvent.KEYCODE_ALT_LEFT: return 56; case KeyEvent.KEYCODE_ALT_RIGHT: return 100;
|
||||
case KeyEvent.KEYCODE_CAPS_LOCK: return 58;
|
||||
// Arrows / nav
|
||||
case KeyEvent.KEYCODE_DPAD_UP: return 103; case KeyEvent.KEYCODE_DPAD_DOWN: return 108;
|
||||
case KeyEvent.KEYCODE_DPAD_LEFT: return 105; case KeyEvent.KEYCODE_DPAD_RIGHT: return 106;
|
||||
case KeyEvent.KEYCODE_MOVE_HOME: return 102; case KeyEvent.KEYCODE_MOVE_END: return 107;
|
||||
case KeyEvent.KEYCODE_PAGE_UP: return 104; case KeyEvent.KEYCODE_PAGE_DOWN: return 109;
|
||||
case KeyEvent.KEYCODE_INSERT: return 110;
|
||||
// Punctuation
|
||||
case KeyEvent.KEYCODE_GRAVE: return 41; case KeyEvent.KEYCODE_MINUS: return 12;
|
||||
case KeyEvent.KEYCODE_EQUALS: return 13; case KeyEvent.KEYCODE_LEFT_BRACKET: return 26;
|
||||
case KeyEvent.KEYCODE_RIGHT_BRACKET: return 27; case KeyEvent.KEYCODE_BACKSLASH: return 43;
|
||||
case KeyEvent.KEYCODE_SEMICOLON: return 39; case KeyEvent.KEYCODE_APOSTROPHE: return 40;
|
||||
case KeyEvent.KEYCODE_SLASH: return 53; case KeyEvent.KEYCODE_COMMA: return 51;
|
||||
case KeyEvent.KEYCODE_PERIOD: return 52;
|
||||
// Function row
|
||||
case KeyEvent.KEYCODE_F1: return 59; case KeyEvent.KEYCODE_F2: return 60;
|
||||
case KeyEvent.KEYCODE_F3: return 61; case KeyEvent.KEYCODE_F4: return 62;
|
||||
case KeyEvent.KEYCODE_F5: return 63; case KeyEvent.KEYCODE_F6: return 64;
|
||||
case KeyEvent.KEYCODE_F7: return 65; case KeyEvent.KEYCODE_F8: return 66;
|
||||
case KeyEvent.KEYCODE_F9: return 67; case KeyEvent.KEYCODE_F10: return 68;
|
||||
case KeyEvent.KEYCODE_F11: return 87; case KeyEvent.KEYCODE_F12: return 88;
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,5 +17,5 @@
|
||||
<string name="catalog_unreachable">Could not reach the runtime catalog</string>
|
||||
<string name="install_failed">Install failed — nothing was changed</string>
|
||||
<string name="session_starting">Starting the session…</string>
|
||||
<string name="session_ended">The session ended (%1$d). Log: Download/%2$s</string>
|
||||
<string name="session_ended">The session ended (%1$d)\n%2$s</string>
|
||||
</resources>
|
||||
Reference in new issue
Block a user