Files
DroidDeck/tools/linuxfs/desktop/droiddeck-desktop
Kurt HimebauchandK11MCH1 906e60a042 feat(desktop): show programs installed by Windows installers (#2146)
* Desktop: programs a Windows installer adds get a menu entry and desktop icon

Opening a Windows installer on the desktop runs it with Proton in a prefix
of its own (droiddeck-proton-run's "own" prefix). Proton's menu builder
turns the installer's Start Menu links into .desktop files, but keeps them
in the prefix's drive_c/proton_shortcuts with a Windows .lnk path as Exec,
and nothing outside the prefix pointed at the installed program: Nexon
Launcher installed fine on an AYANEO Pocket FIT Elite and could only be
found by browsing the hidden compatdata folder.

droiddeck-desktop-programs reads proton_shortcuts in every prefix that is
neither a Steam game's nor a current Steam shortcut's, resolves each link to
its program inside the prefix, and writes a menu entry that opens it with
droiddeck-open, so it runs in that same prefix, with the icon Proton
extracted. A program whose installer also put a link on the Windows desktop
gets a desktop icon once; one the user deletes is not put back. Entries go
away with their program; uninstallers get none. It runs when the desktop
starts and after droiddeck-open finishes a Windows program.

* Desktop programs: only programs that run in their prefix, Windows argument rules, one refresh at a time

A link resolved through Proton's Z: mapping could name a program outside
the scanned prefix, and droiddeck-open would then run it in another one.
A program is now listed only when droiddeck-proton-run's prefix_of puts it
in the prefix it was found in.

Shortcut arguments were split with shlex, so a Windows argument with an
apostrophe raised and stopped the whole refresh; they are now split by
the Windows command-line rules.

The desktop start and the end of a Windows program can refresh at the
same time and race on the same temporary files; refreshes now take turns
on a lock beside the placement record.

* Desktop programs: extract the script at session start

SessionFiles lists the overlay scripts the app copies into the runtime;
droiddeck-desktop-programs was missing, so a real build would never have
installed it and check_session_assets failed the build.

* Desktop programs: a compatdata folder it cannot read changes nothing

Any error listing steamapps/compatdata made the refresh see no prefixes,
so it removed every program entry and forgot which desktop icons it had
placed; the next good run then put back icons the user had deleted. Only a
missing compatdata now means there is nothing installed; any other read
error leaves the menu, the desktop and the placement record as they were.

* Desktop programs: an unreadable shortcut or desktop folder changes nothing

os.path.isdir and glob read an unreadable proton_shortcuts or Windows
desktop folder as empty, so the refresh dropped that prefix's entries and
placement records and a later run put back icons the user had deleted.
Those folders are now listed directly: a missing one is still empty, and
any other error leaves the menu, the desktop and the record as they were.

* Desktop programs: only a missing file reads as absent

read(), link_target() and host_path()'s letter-case lookup turned any
error into "absent", so an unreadable placement record read as empty and
brought back icons the user had deleted, and an unreadable shortcut or
link dropped its program's entry. A missing file, a wrong file type and a
malformed link still read as absent; any other error now reaches main(),
which leaves the menu, the desktop and the record as they were.

* Desktop programs: keep the placement record true when a run fails part way

The placement record was read after the menu was already rewritten, so an
unreadable record changed the menu although the run reported it had not.
It is now read first.

When writing desktop icons failed part way, the icons already written were
not recorded, and the next run recreated any of them the user had deleted.
The record is now written from the icons that exist after the attempt,
icons are written in a fixed order, and a failed write leaves no temporary
file on the desktop.

os.path.exists and isfile also read a program whose path cannot be reached
as missing; a stat that fails only because the file is absent now counts
as missing, and any other error leaves everything as it was.

* Desktop programs: leave the menu alone when Steam's shortcuts cannot be read

The list of Steam shortcuts comes from a reader that treats an unreadable
or damaged shortcuts.vdf as having no shortcuts, so a Steam shortcut's
prefix could get a menu entry and a desktop icon of its own. The file is
now checked first: a missing one still means no shortcuts, and any other
read or parse failure leaves the menu and the desktop as they were.

* Desktop programs: an unreadable Steam account or empty shortcuts file also stops the refresh

account_dir takes an unreadable loginusers.vdf or userdata folder as
missing, so the shortcuts check could be skipped and a Steam shortcut's
prefix still get its own entry. Both are now read first, and an access
error leaves the menu as it was.

An empty shortcuts.vdf is now parsed like any other, so it stops the
refresh too, the same way droiddeck-steam-shortcuts refuses to rewrite it.

---------

Co-authored-by: K11MCH1 <81425424+K11MCH1@users.noreply.github.com>
2026-11-05 18:33:15 -05:00

137 lines
7.1 KiB
Bash

#!/bin/bash
set -u
export XDG_SESSION_TYPE=wayland XDG_CURRENT_DESKTOP=KDE XDG_SESSION_DESKTOP=KDE DESKTOP_SESSION=plasma
export KDE_FULL_SESSION=true KDE_SESSION_VERSION=6 KDE_APPLICATIONS_AS_SCOPE=0
export QT_QPA_PLATFORM="wayland;xcb" GDK_BACKEND=wayland,x11 SDL_VIDEODRIVER=wayland,x11
unset QT_QPA_PLATFORMTHEME
export QT_FORCE_STDERR_LOGGING=1
export MOZ_ENABLE_WAYLAND=1 MOZ_USE_XINPUT2=1
export SHELL=/bin/bash
export MOZ_DISABLE_CONTENT_SANDBOX=1 MOZ_DISABLE_GMP_SANDBOX=1 MOZ_DISABLE_RDD_SANDBOX=1
export MOZ_DISABLE_SOCKET_PROCESS_SANDBOX=1 MOZ_DISABLE_UTILITY_SANDBOX=1
export MOZ_SHM_NO_SEALS=1
export KWIN_COMPOSE=Q QT_QUICK_BACKEND=software LIBGL_ALWAYS_SOFTWARE=1 GALLIUM_DRIVER=llvmpipe
export KWIN_USE_OVERLAYS=0
[ -e /etc/mpv/mpv.conf ] || { mkdir -p /etc/mpv && echo gpu-sw=yes >/etc/mpv/mpv.conf; } 2>/dev/null
for conf in client client-rt; do
mkdir -p "/etc/pipewire/$conf.conf.d" && printf 'context.properties = {\n module.rt = false\n}\n' >"/etc/pipewire/$conf.conf.d/droiddeck-rt.conf"
done 2>/dev/null
export DROIDDECK_NESTED_SCALE=1
export DROIDDECK_GLTHREAD=${mesa_glthread:-}
unset MESA_LOADER_DRIVER_OVERRIDE LIBGL_KOPPER_DRI2 mesa_glthread
if [ -n "${BL_DEBUG_DIR:-}" ]; then
mkdir -p "$BL_DEBUG_DIR" 2>/dev/null || true
exec >>"$BL_DEBUG_DIR/desktop.log" 2>&1
echo "== droiddeck-desktop $(date -u '+%Y-%m-%d %H:%M:%S UTC') ${BL_WIDTH:-1280}x${BL_HEIGHT:-720}"
fi
find "$XDG_RUNTIME_DIR" -mindepth 1 -maxdepth 1 ! -name 'wayland-0*' ! -name 'keymap.xkb' -exec rm -rf {} + 2>/dev/null
chmod 700 "$XDG_RUNTIME_DIR" 2>/dev/null
mkdir -p /tmp/.X11-unix /tmp/.ICE-unix && chmod 1777 /tmp/.X11-unix /tmp/.ICE-unix 2>/dev/null
export BL_DESKTOP=1
ln -sf /usr/local/bin/droiddeck-steam-launch /usr/local/bin/steamdeck-steam 2>/dev/null
ln -sf /usr/local/bin/droiddeck-steam-shim /usr/local/bin/steam 2>/dev/null
python3 /usr/local/bin/droiddeck-desktop-games 2>&1 | sed 's/^/[games] /'
python3 /usr/local/bin/droiddeck-desktop-programs 2>&1 | sed 's/^/[programs] /'
# startplasma applies this file over the session environment, and keeps what an earlier session wrote.
kwriteconfig6 --file plasma-localerc --group Formats --key LANG "${LANG:-C.UTF-8}"
if [ -n "${LANGUAGE:-}" ]; then
kwriteconfig6 --file plasma-localerc --group Translations --key LANGUAGE "$LANGUAGE"
else
kwriteconfig6 --file plasma-localerc --group Translations --key LANGUAGE --delete
fi
cat > /usr/share/applications/droiddeck-steam-x64.desktop <<'ENTRY'
[Desktop Entry]
Type=Application
Name=Steam x86-64 (FEX, experimental)
Comment=Valve's x86-64 Linux client under FEX, in its own home with its own login
Exec=konsole -e /usr/local/bin/droiddeck-steam-x64
Icon=steam
Categories=System;
ENTRY
python3 /usr/local/bin/droiddeck-pad-defaults 2>&1 | sed 's/^/[pads] /'
( while sleep 30; do python3 /usr/local/bin/droiddeck-pad-defaults 2>&1 | sed 's/^/[pads] /'; done ) &
python3 /usr/local/bin/droiddeck-desktop-gpu 2>&1 | sed 's/^/[gpu] /'
/usr/local/bin/droiddeck-desktop-window-rules 2>&1 | sed 's/^/[rules] /'
( python3 /usr/local/bin/droiddeck-library-perms 2>&1 | sed 's/^/[perms] /' ) &
mkdir -p /usr/local/share/applications
cat > /usr/local/share/applications/droiddeck-ime.desktop <<'ENTRY'
[Desktop Entry]
Type=Application
Name=Android keyboard
Exec=/usr/local/bin/droiddeck-ime
NoDisplay=true
X-KDE-Wayland-VirtualKeyboard=true
ENTRY
input_method=$(kreadconfig6 --file kwinrc --group Wayland --key InputMethod 2>/dev/null)
case $input_method in
''|*/droiddeck-ime.desktop) kwriteconfig6 --file kwinrc --group Wayland --key InputMethod /usr/local/share/applications/droiddeck-ime.desktop ;;
esac
python3 /usr/local/bin/droiddeck-open --install 2>&1 | sed 's/^/[open] /'
export APPIMAGE_EXTRACT_AND_RUN=1
[ -e /etc/xdg/kiorc ] || printf '[Executable scripts]\nbehaviourOnLaunch=execute\n' >/etc/xdg/kiorc 2>/dev/null
desktop_dir=$(xdg-user-dir DESKTOP 2>/dev/null); [ -n "$desktop_dir" ] && [ "$desktop_dir" != "$HOME" ] || desktop_dir=$HOME/Desktop
if [ ! -e "$HOME/.config/droiddeck-desktop-icons" ]; then
mkdir -p "$desktop_dir" "$HOME/.config"
cp -f /usr/share/applications/droiddeck-return-gaming.desktop "$desktop_dir/Return.desktop" 2>/dev/null
cp -f /usr/share/applications/steamdeck-steam.desktop "$desktop_dir/steam.desktop" 2>/dev/null
chmod +x "$desktop_dir"/*.desktop 2>/dev/null
touch "$HOME/.config/droiddeck-desktop-icons"
fi
[ -f "$desktop_dir/Return.desktop" ] && cp -f /usr/share/applications/droiddeck-return-gaming.desktop "$desktop_dir/Return.desktop" 2>/dev/null
if command -v flatpak >/dev/null 2>&1 && [ ! -d /var/lib/pacman ]; then
FLATPAK_BWRAP=/usr/local/bin/droiddeck-bwrap flatpak remote-add --user --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo >/dev/null 2>&1 &
fi
skel_done=$HOME/.local/share/droiddeck/skel-copied
if [ -d /etc/skel ] && [ ! -e "$skel_done" ]; then
cp -rn /etc/skel/. "$HOME/" 2>/dev/null
mkdir -p "${skel_done%/*}" && : > "$skel_done"
fi
if [ -x /usr/lib/snapd/snap-confine ] && [ ! -d /var/lib/pacman ]; then
/usr/local/bin/droiddeck-snapd >/dev/null 2>&1 &
export XDG_DATA_DIRS=/var/lib/snapd/desktop:${XDG_DATA_DIRS:-/usr/local/share:/usr/share}
export PATH=$PATH:/snap/bin
fi
update-mime-database /usr/share/mime >/dev/null 2>&1 &
[ -f /usr/share/icons/hicolor/icon-theme.cache ] || gtk-update-icon-cache -q -t /usr/share/icons/hicolor 2>/dev/null &
[ -f /usr/share/glib-2.0/schemas/gschemas.compiled ] || glib-compile-schemas /usr/share/glib-2.0/schemas 2>/dev/null
if [ -f "$HOME/.droiddeck-desktop-debug" ]; then
mkdir -p "$HOME/.cache/droiddeck"
export MOZ_LOG="nsHttp:3,nsSocketTransport:3,nsHostResolver:4,NetworkLinkService:5,NetworkConnectivityService:5,nsIOService:5"
export MOZ_LOG_FILE="$HOME/.cache/droiddeck/firefox-net.log"
export QT_LOGGING_RULES="kwin_*.debug=true"
fi
if [ -f "$HOME/.droiddeck-remote-run" ]; then
mkdir -p "$HOME/.cache/droiddeck/run"
(
while sleep 1; do
for f in "$HOME"/.cache/droiddeck/run/*.sh; do
[ -f "$f" ] || continue
mv -f "$f" "$f.running" || continue
( echo "[run] $(basename "$f") started"; WAYLAND_DISPLAY=wayland-1 DISPLAY=:1 bash "$f.running" 2>&1 | sed "s/^/[run] /"; echo "[run] $(basename "$f") exited"; rm -f "$f.running" ) &
done
done
) &
fi
if [ "$(cat /usr/share/droiddeck/kwin-compositor 2>/dev/null)" = opengl ]; then
unset LIBGL_ALWAYS_SOFTWARE QT_QUICK_BACKEND
export KWIN_COMPOSE=O2 MESA_LOADER_DRIVER_OVERRIDE=zink GALLIUM_DRIVER=zink LIBGL_KOPPER_DRI2=true
if [ -z "${KWIN_RENDER_NODES:-}" ]; then
for node in /dev/dri/renderD0 /dev/dri/renderD307 /dev/dri/renderD*; do
if [ -c "$node" ]; then export KWIN_RENDER_NODES="$node"; break; fi
done
fi
mkdir -p /run/droiddeck/kwin
printf '#!/bin/sh\nGALLIUM_THREAD=0 exec /usr/bin/kwin_wayland "$@"\n' > /run/droiddeck/kwin/kwin_wayland
chmod 755 /run/droiddeck/kwin/kwin_wayland
export PATH=/run/droiddeck/kwin:$PATH
fi
echo "== droiddeck-desktop: Plasma on $WAYLAND_DISPLAY"
dbus-run-session -- startplasma-wayland
rc=$?
echo "== droiddeck-desktop: Plasma ended ($rc)"
if [ "$rc" -eq 0 ] && [ -z "${BL_BOTTOM_DESKTOP:-}" ]; then
/usr/local/bin/droiddeck-steam-launch bigpicture
sleep 30
fi
exit $rc