mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 02:51:10 +00:00
Merge remote-tracking branch 'origin/feat/desktop-steam' into feat/desktop-steam-dolphin
This commit is contained in:
commit
a28e2ffe72
7 files changed
+347
-21
No files matched your search
@@ -219,7 +219,20 @@ object Library {
|
||||
"armsx2" -> listOf("-batch", "-bigpicture", "-fullscreen", "--", guestPath)
|
||||
// DuckStation shares PCSX2's flags (src/duckstation-qt/qthost.cpp): the same launch and exit.
|
||||
"duckstation" -> listOf("-batch", "-bigpicture", "-fullscreen", "--", guestPath)
|
||||
"dolphin" -> listOf("-e", guestPath)
|
||||
// Dolphin: full screen, drawn inside its own main window, without its "stop the emulation?"
|
||||
// question; no warning boxes either, which wait for a click a controller cannot give
|
||||
// (they still go to Dolphin's log). The guide button is Dolphin's Toggle Fullscreen hotkey
|
||||
// (bannerlator-pad-defaults), which shows Dolphin's window and its settings - so no -b, which
|
||||
// hides that window; closing Dolphin ends the session. Under gamescope Dolphin does not
|
||||
// always see its window as focused, and by default both its hotkeys and the game's
|
||||
// controller then stop: HotkeysRequireFocus off, BackgroundInput on. -C sets a Dolphin.ini
|
||||
// value for this run only, so Dolphin started from the desktop keeps its own settings.
|
||||
"dolphin" -> listOf(
|
||||
"-C", "Dolphin.Display.Fullscreen=True", "-C", "Dolphin.Display.RenderToMain=True",
|
||||
"-C", "Dolphin.Interface.ConfirmStop=False", "-C", "Dolphin.Interface.UsePanicHandlers=False",
|
||||
"-C", "Dolphin.General.HotkeysRequireFocus=False", "-C", "Dolphin.Input.BackgroundInput=True",
|
||||
"-e", guestPath,
|
||||
)
|
||||
"cemu" -> listOf("-g", guestPath)
|
||||
else -> listOf(guestPath)
|
||||
}
|
||||
|
||||
@@ -16,6 +16,10 @@ import kotlin.math.abs
|
||||
* - two fingers up / down ............ scroll wheel
|
||||
* - hold still, then drag ............ left-button drag
|
||||
* - double-tap, hold, drag ........... left-button drag as well
|
||||
* - one finger held, another slides .. left-button drag, as on a laptop's pad: the held finger is
|
||||
* the button, the other moves the pointer (a window by its
|
||||
* title bar or edge, a desktop icon). The sliding finger may
|
||||
* lift and slide again; lifting the held one drops.
|
||||
*
|
||||
* Positions handed to the listener are the pointer's, in the view's pixels; the caller keeps them
|
||||
* inside the picture.
|
||||
@@ -45,6 +49,16 @@ class TouchpadGestures(
|
||||
private var twoFingers = false
|
||||
private var scrollAnchorY = 0f
|
||||
private var scrolled = false
|
||||
// Hold-and-slide: the first finger stays as the button, the second moves the pointer.
|
||||
private var anchorId = -1
|
||||
private var anchorStartX = 0f
|
||||
private var anchorStartY = 0f
|
||||
private var moverId = -1
|
||||
private var moverStartX = 0f
|
||||
private var moverStartY = 0f
|
||||
private var moverLastX = 0f
|
||||
private var moverLastY = 0f
|
||||
private var holdDrag = false
|
||||
private val longPress = Runnable {
|
||||
held = true
|
||||
dragging = true
|
||||
@@ -63,6 +77,7 @@ class TouchpadGestures(
|
||||
lastX = event.x; lastY = event.y
|
||||
downAt = event.eventTime
|
||||
travelled = 0f; held = false; twoFingers = false; scrolled = false; secondWasTap = false
|
||||
anchorId = event.getPointerId(0); moverId = -1; holdDrag = false
|
||||
// A tap shortly before this touch: holding now drags (double-tap-and-hold).
|
||||
if (event.eventTime - lastTapAt < DOUBLE_TAP_MS) {
|
||||
dragging = true
|
||||
@@ -75,6 +90,17 @@ class TouchpadGestures(
|
||||
}
|
||||
MotionEvent.ACTION_POINTER_DOWN -> {
|
||||
handler.removeCallbacks(longPress)
|
||||
val i = event.actionIndex
|
||||
if (moverId == -1 && event.getPointerId(i) != anchorId) {
|
||||
moverId = event.getPointerId(i)
|
||||
moverStartX = event.getX(i); moverStartY = event.getY(i)
|
||||
moverLastX = moverStartX; moverLastY = moverStartY
|
||||
event.findPointerIndex(anchorId).takeIf { it >= 0 }?.let {
|
||||
anchorStartX = event.getX(it); anchorStartY = event.getY(it)
|
||||
}
|
||||
}
|
||||
// Mid-drag, a finger coming back only picks the sliding up again.
|
||||
if (holdDrag) return true
|
||||
twoFingers = true
|
||||
secondDownAt = event.eventTime
|
||||
secondWasTap = true
|
||||
@@ -82,6 +108,38 @@ class TouchpadGestures(
|
||||
return true
|
||||
}
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
val ai = event.findPointerIndex(anchorId)
|
||||
val mi = if (moverId == -1) -1 else event.findPointerIndex(moverId)
|
||||
if (ai >= 0 && mi >= 0) {
|
||||
val mx = event.getX(mi); val my = event.getY(mi)
|
||||
// One finger still, the other on the move: that is a drag, not a scroll (which
|
||||
// moves both). Decided once, before anything has scrolled.
|
||||
if (!holdDrag && !scrolled) {
|
||||
val anchorMoved = abs(event.getX(ai) - anchorStartX) + abs(event.getY(ai) - anchorStartY)
|
||||
val moverMoved = abs(mx - moverStartX) + abs(my - moverStartY)
|
||||
if (moverMoved > slopPx && anchorMoved < slopPx) {
|
||||
holdDrag = true
|
||||
secondWasTap = false
|
||||
if (!dragging) {
|
||||
dragging = true
|
||||
listener.onButton(PointerGestures.BTN_LEFT, true, x, y)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (holdDrag) {
|
||||
val dx = mx - moverLastX
|
||||
val dy = my - moverLastY
|
||||
moverLastX = mx; moverLastY = my
|
||||
val speed = abs(dx) + abs(dy)
|
||||
val gain = sensitivity * (1f + minOf(speed / 40f, 1.5f))
|
||||
place(x + dx * gain, y + dy * gain)
|
||||
listener.onMove(x, y)
|
||||
return true
|
||||
}
|
||||
} else if (holdDrag) {
|
||||
// Only the held finger is down: the drag waits for the next slide.
|
||||
return true
|
||||
}
|
||||
if (twoFingers && event.pointerCount >= 2) {
|
||||
val mid = (event.getY(0) + event.getY(1)) / 2f
|
||||
val dy = mid - scrollAnchorY
|
||||
@@ -109,6 +167,30 @@ class TouchpadGestures(
|
||||
return true
|
||||
}
|
||||
MotionEvent.ACTION_POINTER_UP -> {
|
||||
val gone = event.getPointerId(event.actionIndex)
|
||||
if (holdDrag) {
|
||||
if (gone == anchorId) {
|
||||
// The held finger lifted: drop. The other finger carries on as the pointer.
|
||||
listener.onButton(PointerGestures.BTN_LEFT, false, x, y)
|
||||
dragging = false; holdDrag = false; twoFingers = false
|
||||
anchorId = moverId; moverId = -1
|
||||
event.findPointerIndex(anchorId).takeIf { it >= 0 }?.let {
|
||||
lastX = event.getX(it); lastY = event.getY(it)
|
||||
}
|
||||
travelled = slopPx + 1f
|
||||
} else if (gone == moverId) {
|
||||
moverId = -1
|
||||
}
|
||||
return true
|
||||
}
|
||||
if (gone == moverId) moverId = -1
|
||||
if (gone == anchorId) {
|
||||
// The first finger left a two-finger gesture: the other one is the pointer now.
|
||||
anchorId = event.getPointerId(if (event.actionIndex == 0) 1 else 0)
|
||||
event.findPointerIndex(anchorId).takeIf { it >= 0 }?.let {
|
||||
lastX = event.getX(it); lastY = event.getY(it)
|
||||
}
|
||||
}
|
||||
// The second finger lifting quickly, with nothing scrolled: a right click.
|
||||
if (twoFingers && secondWasTap && event.eventTime - secondDownAt < TAP_MS && !scrolled) {
|
||||
listener.onButton(PointerGestures.BTN_RIGHT, true, x, y)
|
||||
@@ -136,6 +218,7 @@ class TouchpadGestures(
|
||||
}
|
||||
}
|
||||
dragging = false; held = false; twoFingers = false
|
||||
holdDrag = false; anchorId = -1; moverId = -1
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,7 +170,8 @@ class SessionService : Service() {
|
||||
SessionState.steamUrl = intent?.getStringExtra(EXTRA_STEAM_URL)
|
||||
// Another Steam client on the device signs ours out seconds after every login; the one that
|
||||
// does it here runs from boot without being opened. Only the Steam session signs in.
|
||||
if (SessionState.mode == MODE_STEAM) RivalClients.stopBeforeSession(this)
|
||||
// The desktop too: its Steam launchers run the client right there (bannerlator-steam-launch).
|
||||
if (SessionState.mode == MODE_STEAM || SessionState.mode == MODE_DESKTOP) RivalClients.stopBeforeSession(this)
|
||||
SessionState.running = true
|
||||
SessionState.firstFrameSeen = false
|
||||
// This session's number, claimed here and not when its process starts: the session it
|
||||
@@ -303,7 +304,10 @@ class SessionService : Service() {
|
||||
if (SessionPrefs.glThread(this)) guest.add("mesa_glthread=true")
|
||||
if (SessionPrefs.noGlError(this)) guest.add("MESA_NO_ERROR=1")
|
||||
if (SessionState.mode == MODE_STEAM) guest.add("BL_STEAMDECK=" + (if (SessionPrefs.steamDeckMode(this)) "1" else "0"))
|
||||
if (SessionState.mode == MODE_STEAM) guest.add("BL_STEAM_CHANNEL=" + SessionPrefs.steamChannel(this))
|
||||
// The desktop's Steam launchers start the client there (bannerlator-steam-launch), through the
|
||||
// same set-up as a Steam session: it gets what the client and its games are started with.
|
||||
val steamHere = SessionState.mode == MODE_STEAM || SessionState.mode == MODE_DESKTOP
|
||||
if (steamHere) guest.add("BL_STEAM_CHANNEL=" + SessionPrefs.steamChannel(this))
|
||||
if (SessionState.mode == MODE_STEAM) {
|
||||
guest.add("BL_GAMESCOPE_FORCE_FULLSCREEN=" + (if (SessionPrefs.forceFullscreen(this)) "1" else "0"))
|
||||
SessionPrefs.writeForceFullscreenFlag(this)
|
||||
@@ -315,7 +319,7 @@ class SessionService : Service() {
|
||||
// The FEXCore preset for the x86 games the client launches: its FEX_* variables go in
|
||||
// here, before the script, so every game process inherits them from the client. The
|
||||
// default preset sets nothing, which is what every session ran on before.
|
||||
if (SessionState.mode == MODE_STEAM) {
|
||||
if (steamHere) {
|
||||
val preset = SessionPrefs.fexPreset(this)
|
||||
val vars = com.droiddeck.launcher.core.FexPreset.env(preset)
|
||||
vars.forEach { guest.add(it) }
|
||||
@@ -330,7 +334,7 @@ class SessionService : Service() {
|
||||
// on, even naming every core: it exists to undo the pin Steam applies to its own interface
|
||||
// renderer, and the scheduler's default is exactly what that pin takes away. A game's is
|
||||
// sent only when it is a real restriction - a game has no pin of its own to undo.
|
||||
if (SessionState.mode == MODE_STEAM && SessionPrefs.clientCpusOverride(this)) {
|
||||
if (steamHere && SessionPrefs.clientCpusOverride(this)) {
|
||||
guest.add("BL_CLIENT_CPUS=" + CpuCores.listOrAll(SessionPrefs.clientCpus(this)))
|
||||
}
|
||||
// The game cores also pin the desktop and a program from the rail; without a choice the
|
||||
@@ -432,7 +436,7 @@ class SessionService : Service() {
|
||||
}
|
||||
// The user's own games, for the runtime's shortcuts writer to put in the client's library
|
||||
// before the client starts (see frontend/AddedGames and bannerlator-steam-shortcuts).
|
||||
if (SessionState.mode == MODE_STEAM) {
|
||||
if (steamHere) {
|
||||
val added = com.droiddeck.launcher.frontend.AddedGames.scan(this)
|
||||
val listing = com.droiddeck.launcher.frontend.AddedGames.writeListing(this, added)
|
||||
guest.add("BL_ADDED_GAMES=" + listing.path)
|
||||
@@ -567,7 +571,7 @@ class SessionService : Service() {
|
||||
|
||||
// Whether the client signs in to Valve or starts offline: read once, while it starts, and
|
||||
// rewritten by the client when it exits, so it is set again here at every session start.
|
||||
if (SessionState.mode == MODE_STEAM) OfflineMode.apply(this, root)
|
||||
if (SessionState.mode == MODE_STEAM || SessionState.mode == MODE_DESKTOP) OfflineMode.apply(this, root)
|
||||
|
||||
val networkLink = LinuxNetworkLinkComponent(this, root)
|
||||
networkLink.attach(this)
|
||||
|
||||
@@ -383,7 +383,7 @@ Buttons/1 = `Button W`
|
||||
Buttons/2 = `Button N`
|
||||
Buttons/- = `Back`
|
||||
Buttons/+ = `Start`
|
||||
Buttons/Home = `Guide`
|
||||
Buttons/Home = `Shoulder R`
|
||||
D-Pad/Up = `Pad N`
|
||||
D-Pad/Down = `Pad S`
|
||||
D-Pad/Left = `Pad W`
|
||||
@@ -510,10 +510,188 @@ def armsx2_bios(ini):
|
||||
print(f"bannerlator-pad-defaults: ARMSX2: BIOS {pick}")
|
||||
|
||||
|
||||
# Dolphin (PC) has no controller menu, so the guide button is its Toggle Fullscreen hotkey: out of full
|
||||
# screen is Dolphin's own window with its Config / Graphics / Stop buttons. Hotkeys.ini is all-or-
|
||||
# nothing - a file with one line drops every other default - so a new one carries Dolphin's defaults,
|
||||
# generated from HotkeyManager.cpp's LoadDefaults (Linux branch, RetroAchievements on) plus the guide;
|
||||
# an existing one only gets the guide added to its Toggle Fullscreen. The Wii remote's Home, which
|
||||
# was on the guide button, is on the right shoulder button instead.
|
||||
DOLPHIN_GUIDE = "`SDL/0/Xbox 360 Controller:Guide`"
|
||||
DOLPHIN_HOTKEYS = """[Hotkeys]
|
||||
Device = XInput2/0/Virtual core pointer
|
||||
General/Open = @(Ctrl+O)
|
||||
General/Toggle Pause = F10
|
||||
General/Stop = Escape
|
||||
General/Toggle Fullscreen = @(Alt+Return) | `SDL/0/Xbox 360 Controller:Guide`
|
||||
General/Take Screenshot = F9
|
||||
General/Open Achievements = @(Alt+A)
|
||||
Emulation Speed/Disable Emulation Speed Limit = Tab
|
||||
Stepping/Step Into = F11
|
||||
Stepping/Step Over = @(Shift+F10)
|
||||
Stepping/Step Out = @(Shift+F11)
|
||||
Breakpoint/Toggle Breakpoint = @(Shift+F9)
|
||||
Wii/Connect Wii Remote 1 = @(Alt+F5)
|
||||
Wii/Connect Wii Remote 2 = @(Alt+F6)
|
||||
Wii/Connect Wii Remote 3 = @(Alt+F7)
|
||||
Wii/Connect Wii Remote 4 = @(Alt+F8)
|
||||
Wii/Connect Balance Board = @(Alt+F9)
|
||||
Load State/Load State Slot 1 = F1
|
||||
Load State/Load State Slot 2 = F2
|
||||
Load State/Load State Slot 3 = F3
|
||||
Load State/Load State Slot 4 = F4
|
||||
Load State/Load State Slot 5 = F5
|
||||
Load State/Load State Slot 6 = F6
|
||||
Load State/Load State Slot 7 = F7
|
||||
Load State/Load State Slot 8 = F8
|
||||
Save State/Save State Slot 1 = @(Shift+F1)
|
||||
Save State/Save State Slot 2 = @(Shift+F2)
|
||||
Save State/Save State Slot 3 = @(Shift+F3)
|
||||
Save State/Save State Slot 4 = @(Shift+F4)
|
||||
Save State/Save State Slot 5 = @(Shift+F5)
|
||||
Save State/Save State Slot 6 = @(Shift+F6)
|
||||
Save State/Save State Slot 7 = @(Shift+F7)
|
||||
Save State/Save State Slot 8 = @(Shift+F8)
|
||||
Other State Hotkeys/Undo Load State = F12
|
||||
Other State Hotkeys/Undo Save State = @(Shift+F12)
|
||||
GBA Core/Load ROM = @(`Ctrl`+`Shift`+`O`)
|
||||
GBA Core/Unload ROM = @(`Ctrl`+`Shift`+`W`)
|
||||
GBA Core/Reset = @(`Ctrl`+`Shift`+`R`)
|
||||
GBA Volume/Volume Down = `KP_Subtract`
|
||||
GBA Volume/Volume Up = `KP_Add`
|
||||
GBA Volume/Volume Toggle Mute = `M`
|
||||
GBA Window Size/1x = `KP_1`
|
||||
GBA Window Size/2x = `KP_2`
|
||||
GBA Window Size/3x = `KP_3`
|
||||
GBA Window Size/4x = `KP_4`
|
||||
USB Emulation Devices/Show Skylanders Portal = @(Ctrl+P)
|
||||
USB Emulation Devices/Show Infinity Base = @(Ctrl+I)
|
||||
"""
|
||||
|
||||
DOLPHIN_APPIMAGE = "/opt/appimages/dolphin.AppImage"
|
||||
DOLPHIN_DATA = "/usr/share/dolphin-emu"
|
||||
|
||||
|
||||
def dolphin_data():
|
||||
"""The Dolphin build in the catalog finds its own data - the GameCube fonts, the per-game fixes
|
||||
(GameSettings: GUT.ini puts Ultimate Spider-Man on the MMU), its icons and shaders - only at the
|
||||
fixed /usr/share/dolphin-emu it was built for; there is no setting or variable to move it. Here it
|
||||
runs from a copy unpacked under /tmp (no FUSE), so it found none of it: a GameCube game stopped
|
||||
on "Windows-1252 fonts are not loaded", another on an invalid write the missing game fix would
|
||||
have prevented. So the folder is unpacked from the AppImage to that path, once per AppImage."""
|
||||
import shutil, subprocess, tempfile
|
||||
try:
|
||||
st = os.stat(DOLPHIN_APPIMAGE)
|
||||
except OSError:
|
||||
return
|
||||
stamp = "%d:%d" % (st.st_size, int(st.st_mtime))
|
||||
marker = os.path.join(DOLPHIN_DATA, ".droiddeck-from")
|
||||
try:
|
||||
if open(marker).read() == stamp and os.path.isfile(os.path.join(DOLPHIN_DATA, "Sys/GC/font_western.bin")):
|
||||
return
|
||||
except OSError:
|
||||
pass
|
||||
work = tempfile.mkdtemp(prefix="dolphin-data-", dir="/tmp")
|
||||
try:
|
||||
subprocess.run([DOLPHIN_APPIMAGE, "--appimage-extract", "usr/share/dolphin-emu/*"], cwd=work,
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=600)
|
||||
src = os.path.join(work, "squashfs-root/usr/share/dolphin-emu")
|
||||
if not os.path.isfile(os.path.join(src, "Sys/GC/font_western.bin")):
|
||||
print("bannerlator-pad-defaults: Dolphin: could not unpack its data from the AppImage")
|
||||
return
|
||||
new = DOLPHIN_DATA + ".new"
|
||||
shutil.rmtree(new, ignore_errors=True)
|
||||
shutil.move(src, new)
|
||||
with open(os.path.join(new, ".droiddeck-from"), "w") as f:
|
||||
f.write(stamp)
|
||||
shutil.rmtree(DOLPHIN_DATA, ignore_errors=True)
|
||||
os.rename(new, DOLPHIN_DATA)
|
||||
print("bannerlator-pad-defaults: Dolphin: its data (fonts, per-game fixes) in " + DOLPHIN_DATA)
|
||||
except (OSError, subprocess.SubprocessError) as e:
|
||||
print("bannerlator-pad-defaults: Dolphin: unpacking its data failed: %s" % e)
|
||||
finally:
|
||||
shutil.rmtree(work, ignore_errors=True)
|
||||
|
||||
|
||||
def dolphin():
|
||||
if not os.path.isfile(DOLPHIN_APPIMAGE):
|
||||
return
|
||||
dolphin_data()
|
||||
d = os.path.join(CONFIG, "dolphin-emu")
|
||||
seed(os.path.join(d, "GCPadNew.ini"), DOLPHIN_GC, "Dolphin (GameCube)")
|
||||
seed(os.path.join(d, "WiimoteNew.ini"), DOLPHIN_WII, "Dolphin (Wii)")
|
||||
wii_pad = os.path.join(d, "WiimoteNew.ini")
|
||||
if ini_get(wii_pad, "Wiimote1", "Buttons/Home") == "`Guide`":
|
||||
ini_set(wii_pad, "Wiimote1", "Buttons/Home", "`Shoulder R`")
|
||||
print("bannerlator-pad-defaults: Dolphin (Wii): Home on the right shoulder button (guide = Dolphin's window)")
|
||||
hotkeys = os.path.join(d, "Hotkeys.ini")
|
||||
if not os.path.isfile(hotkeys):
|
||||
seed(hotkeys, DOLPHIN_HOTKEYS, "Dolphin", "the guide button shows Dolphin's window (Toggle Fullscreen)")
|
||||
else:
|
||||
current = ini_get(hotkeys, "Hotkeys", "General/Toggle Fullscreen")
|
||||
if current is not None and DOLPHIN_GUIDE not in current:
|
||||
ini_set(hotkeys, "Hotkeys", "General/Toggle Fullscreen",
|
||||
(current + " | " if current else "") + DOLPHIN_GUIDE)
|
||||
print("bannerlator-pad-defaults: Dolphin: the guide button shows Dolphin's window (Toggle Fullscreen)")
|
||||
# Games from the ROMs folder's gamecube and wii folders, subfolders included; Vulkan; no
|
||||
# NKit warning (it will not let a game start until a box is ticked - games run either way,
|
||||
# the warning is about loading times, netplay and save states); no statistics question.
|
||||
# Names from Dolphin's MainSettings.cpp / UISettings.cpp. Only what is not set yet is added,
|
||||
# except the game folders, which are added to whatever the user has.
|
||||
ini = os.path.join(d, "Dolphin.ini")
|
||||
gc = system_folder(("gamecube", "gc", "ngc"), "gamecube")
|
||||
wii = system_folder(("wii",), "wii")
|
||||
if not os.path.isfile(ini):
|
||||
os.makedirs(d, exist_ok=True)
|
||||
with open(ini, "w", encoding="utf-8") as f:
|
||||
f.write("[Analytics]\nEnabled = False\nPermissionAsked = True\n"
|
||||
"[Core]\nGFXBackend = Vulkan\n"
|
||||
"[General]\nISOPaths = 2\nISOPath0 = %s\nISOPath1 = %s\nRecursiveISOPaths = True\n"
|
||||
"[Interface]\nSkipNKitWarning = True\n" % (gc, wii))
|
||||
print("bannerlator-pad-defaults: Dolphin: first-time setup (Vulkan, games from %s and %s)" % (gc, wii))
|
||||
return
|
||||
try:
|
||||
count = int(ini_get(ini, "General", "ISOPaths") or "0")
|
||||
except ValueError:
|
||||
count = 0
|
||||
paths = [ini_get(ini, "General", "ISOPath%d" % i) for i in range(count)]
|
||||
changed = []
|
||||
# The whole ROMs folder as a game folder (what Dolphin's "set a games directory" was pointed at
|
||||
# before) would, searched with its subfolders, have Dolphin open every PS2 and PS3 image too;
|
||||
# the system folders below cover it.
|
||||
if any((p or "").rstrip("/") == ROMS for p in paths):
|
||||
paths = [p for p in paths if p and p.rstrip("/") != ROMS]
|
||||
for i, p in enumerate(paths):
|
||||
ini_set(ini, "General", "ISOPath%d" % i, p)
|
||||
for i in range(len(paths), count):
|
||||
ini_set(ini, "General", "ISOPath%d" % i, "")
|
||||
count = len(paths)
|
||||
ini_set(ini, "General", "ISOPaths", str(count))
|
||||
changed.append("not the whole ROMs folder")
|
||||
for folder in (gc, wii):
|
||||
if folder not in paths:
|
||||
ini_set(ini, "General", "ISOPath%d" % count, folder)
|
||||
count += 1
|
||||
paths.append(folder)
|
||||
changed.append(folder)
|
||||
if changed:
|
||||
ini_set(ini, "General", "ISOPaths", str(count))
|
||||
if ini_get(ini, "General", "RecursiveISOPaths") != "True":
|
||||
ini_set(ini, "General", "RecursiveISOPaths", "True")
|
||||
changed.append("subfolders")
|
||||
if changed:
|
||||
print("bannerlator-pad-defaults: Dolphin: games from " + ", ".join(changed))
|
||||
# Once, for a Dolphin set up before this: Vulkan unless it was chosen already, and the NKit box.
|
||||
marker = os.path.join(d, ".droiddeck-setup")
|
||||
if os.path.exists(marker):
|
||||
return
|
||||
if not ini_get(ini, "Core", "GFXBackend"):
|
||||
ini_set(ini, "Core", "GFXBackend", "Vulkan")
|
||||
ini_set(ini, "Interface", "SkipNKitWarning", "True")
|
||||
if ini_get(ini, "Analytics", "PermissionAsked") != "True":
|
||||
ini_set(ini, "Analytics", "Enabled", "False")
|
||||
ini_set(ini, "Analytics", "PermissionAsked", "True")
|
||||
open(marker, "w").close()
|
||||
print("bannerlator-pad-defaults: Dolphin: Vulkan, no NKit warning")
|
||||
|
||||
|
||||
# Cemu: an SDLController profile for the GamePad. The uuid is "<index>_<SDL GUID>" and SDL's GUID
|
||||
|
||||
@@ -350,7 +350,8 @@ case $mode in
|
||||
# The drawer's "Stretch games to fill the screen" works live: the app writes its choice to
|
||||
# ~/.droiddeck-fill, and each change goes to gamescope as GAMESCOPE_FORCE_WINDOWS_FULLSCREEN on
|
||||
# the root window, which it watches. What gamescope was started with is where this begins.
|
||||
(
|
||||
# Not on the desktop (BL_DESKTOP): no gamescope there, and the loop would outlive the client.
|
||||
[ -z "${BL_DESKTOP:-}" ] && (
|
||||
last=${BL_GAMESCOPE_FORCE_FULLSCREEN:-1}
|
||||
while :; do
|
||||
sleep 1
|
||||
@@ -515,7 +516,17 @@ BUSCONF
|
||||
# The URL arguments were acted on the first time; a restart must not repeat them.
|
||||
set --
|
||||
done
|
||||
reap_orphans
|
||||
if [ -n "${BL_DESKTOP:-}" ]; then
|
||||
# Started from the desktop (bannerlator-steam-launch): the desktop's own programs share this
|
||||
# script's parent, so reap_orphans - right under gamescope, where everything is the client's -
|
||||
# is not for here. The helpers this run started (the cores and redistributables beats, the
|
||||
# system bus and the NetworkManager stand-in) go with the client instead.
|
||||
_bl_jobs=$(jobs -p)
|
||||
[ -n "$_bl_jobs" ] && kill $_bl_jobs 2>/dev/null
|
||||
echo "== steam: the client on the desktop has exited"
|
||||
else
|
||||
reap_orphans
|
||||
fi
|
||||
exit "$rc"
|
||||
;;
|
||||
*)
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
#!/bin/bash
|
||||
# Asks the app to start the Steam client, from inside a session (the desktop's menu entries and
|
||||
# the per-game launchers bannerlator-desktop-games writes):
|
||||
# Starts the Steam client from inside a session (the desktop's menu entries and the per-game
|
||||
# launchers bannerlator-desktop-games writes):
|
||||
# bannerlator-steam-launch desktop [appid|steam-url] Steam's desktop UI, optionally a game
|
||||
# bannerlator-steam-launch bigpicture [appid|steam-url] Big Picture
|
||||
# The client cannot run on the desktop: labwc composites in software and offers no dma-buf, so a
|
||||
# game (or the client's own Vulkan window) would die at its first frame. It runs under gamescope,
|
||||
# which is a session of its own - so this writes a request into the directory the app watches,
|
||||
# and the app ends the desktop session and starts a Steam session in its place.
|
||||
#
|
||||
# On a desktop that draws with the GPU (labwc on vulkan or gles2 - the default since the GPU
|
||||
# desktop) the client runs right there: a window on the desktop, through the desktop's Xwayland,
|
||||
# with the same set-up a Steam session gives it (bannerlator-session steam, minus gamescope), and
|
||||
# its games open in windows of their own. A client already running there gets the request instead
|
||||
# of a second one being started.
|
||||
#
|
||||
# On a software desktop (pixman: the GPU renderers failed on this device) there is no dma-buf, so a
|
||||
# game - or the client's own Vulkan window - would die at its first frame. There the request goes
|
||||
# to the app, which ends the desktop and starts a Steam session under gamescope in its place.
|
||||
set -u
|
||||
ui=${1:-desktop}
|
||||
target=${2:-}
|
||||
@@ -18,6 +24,30 @@ case $target in
|
||||
*[!0-9]*) echo "not an app id or steam:// URL: $target" >&2; exit 64 ;;
|
||||
*) url=steam://rungameid/$target ;;
|
||||
esac
|
||||
|
||||
steam_root=${XDG_DATA_HOME:-${HOME:-/root}/.local/share}/Steam
|
||||
client=$steam_root/steamrtarm64/steam
|
||||
|
||||
if [ "${BL_DESKTOP:-}" = 1 ] && [ -n "${WLR_RENDERER:-}" ] && [ "$WLR_RENDERER" != pixman ]; then
|
||||
log=${BL_DEBUG_DIR:-$HOME/.cache/droiddeck}/steam-desktop.log
|
||||
mkdir -p "$(dirname "$log")" 2>/dev/null
|
||||
if pgrep -f 'steamrtarm64/steam( |$)' >/dev/null 2>&1 && [ -x "$client" ]; then
|
||||
# One client at a time: the running one takes the request (a second start hands its command
|
||||
# line over and exits). No game asked for = switch it to the UI that was clicked.
|
||||
[ -n "$url" ] || { [ "$ui" = bigpicture ] && url=steam://open/bigpicture || url=steam://open/main; }
|
||||
echo "bannerlator-steam-launch: the client is running on the desktop; handing it $url" | tee -a "$log"
|
||||
exec "$client" "$url"
|
||||
fi
|
||||
echo "bannerlator-steam-launch: starting the Steam client on the desktop ($ui${url:+, $url}); log: $log"
|
||||
# BL_INSIDE: the session script's Steam start-up without gamescope in front of it. The desktop UI
|
||||
# unless Big Picture was asked for; never Deck mode (its Quick Access Menu needs gamescope).
|
||||
export BL_INSIDE=1 BL_STEAMDECK=0
|
||||
if [ "$ui" = desktop ]; then export BL_STEAM_UI=desktop; else unset BL_STEAM_UI; fi
|
||||
setsid /usr/local/bin/bannerlator-session steam ${url:+"$url"} >>"$log" 2>&1 < /dev/null &
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# The software desktop, or outside the desktop: ask the app for a Steam session.
|
||||
dir=${BL_LAUNCH_DIR:-}
|
||||
if [ -z "$dir" ] || [ ! -d "$dir" ]; then
|
||||
echo "bannerlator-steam-launch: not inside a DroidDeck session (BL_LAUNCH_DIR unset)" >&2
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
#!/bin/bash
|
||||
# `steam` on the desktop. The client writes a menu entry per installed game that runs
|
||||
# `steam steam://rungameid/<id>`, and its own launchers say `steam`; neither can run the client
|
||||
# here (no dma-buf on the desktop), so on the desktop this hands off to the app for a Steam session
|
||||
# in the client's desktop UI under gamescope. Anywhere else it is the client itself.
|
||||
# `steam steam://rungameid/<id>`, and its own launchers say `steam`. On the desktop that goes
|
||||
# through bannerlator-steam-launch - which runs the client on the desktop itself when it draws with
|
||||
# the GPU, hands the URL to a client already running there, and asks the app for a Steam session
|
||||
# on a software desktop. Anywhere else it is the client itself.
|
||||
set -u
|
||||
if [ "${BL_DESKTOP:-}" = 1 ]; then
|
||||
url=
|
||||
for arg in "$@"; do case $arg in steam://*) url=$arg ;; esac; done
|
||||
exec /usr/local/bin/bannerlator-steam-launch desktop ${url:+"$url"}
|
||||
ui=desktop
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
steam://*) url=$arg ;;
|
||||
-gamepadui|-bigpicture) ui=bigpicture ;;
|
||||
esac
|
||||
done
|
||||
exec /usr/local/bin/bannerlator-steam-launch "$ui" ${url:+"$url"}
|
||||
fi
|
||||
exec "${HOME:-/root}/.local/share/Steam/steamrtarm64/steam" "$@"
|
||||
Reference in new issue
Block a user