Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Includes:
* Latest FEX
* CachyOS Proton 11
* Desktop mode (KDE)
* Nested Desktop inside Gaming Mode
* Bazaar App Store
* Over-the-air updates
* Install to internal storage (alongside Android)
Expand Down Expand Up @@ -174,6 +175,23 @@ desktop. The **Bazaar** app store and the **Armada Installer**
([Install to internal storage](#install-to-internal-storage)) live here. Use the
**Return to Gaming Mode** shortcut on the desktop to switch back.

To add a desktop application, executable, AppImage, script, or Windows program
to the Gaming Mode library, right-click its file in Dolphin and choose **Add to
Steam**.

### Nested Desktop

Armada includes a **Nested Desktop** launcher for running KDE Plasma without
leaving Gaming Mode. In Desktop Mode, find **Nested Desktop** in the application
menu, right-click it, and choose **Add to Steam**. Back in Gaming Mode, open its
properties, select **Armada Native** under **Compatibility**, and set **Game
Resolution** to **Native**. Then launch it from the **Non-Steam** library.

The nested session uses the same fullscreen KWin path as SteamOS: Gamescope
controls its surface and input mapping, while the launcher normalizes Armada's
rotated panel to a 150% landscape Plasma workspace. It also shares the parent
session's PipeWire audio.

### Power button and sleep

Pressing the power button does a "fake suspend" (inspired by ROCKNIX) rather than
Expand Down
1 change: 1 addition & 0 deletions build_files/10-base-packages.sh
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ dnf5 -y install --setopt=install_weak_deps=False \
plasma-pa \
plasma-nm \
bluedevil \
upower \
maliit-keyboard \
libappindicator-gtk3 \
libdbusmenu-gtk3 \
Expand Down
1 change: 1 addition & 0 deletions build_files/50-create-user.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ done
install -d -m 0700 -o armada -g armada /var/home/armada
chown -R armada:armada /var/home/armada
chmod 0700 /var/home/armada
install -d -m 0755 -o armada -g armada /var/home/armada/Desktop
install -Dpm 0755 -o armada -g armada \
/usr/share/applications/armada-return-to-gamemode.desktop \
/var/home/armada/Desktop/armada-return-to-gamemode.desktop
Expand Down
9 changes: 9 additions & 0 deletions system_files/etc/xdg/autostart/armada-desktop-steam.desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[Desktop Entry]
Type=Application
Name=Steam Desktop Controls
Comment=Start Steam to provide gamepad controls in Desktop Mode
Exec=/usr/libexec/armada/desktop-steam-autostart
Terminal=false
OnlyShowIn=KDE;
X-KDE-autostart-after=panel
X-KDE-StartupNotify=false
70 changes: 70 additions & 0 deletions system_files/usr/bin/steamos-add-to-steam
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#!/usr/bin/bash
# SPDX-License-Identifier: GPL-2.0-or-later
# Adapted from Valve's steamdeck-kde-presets helper for Armada's ARM client.
set -euo pipefail

show_dialog=0

usage() {
echo "Usage: steamos-add-to-steam [-ui] <file>" >&2
}

show_error() {
if (( show_dialog )) && command -v kdialog >/dev/null 2>&1 &&
[[ -n ${DISPLAY:-}${WAYLAND_DISPLAY:-} ]]; then
kdialog --title "Add to Steam" --error "$1"
else
echo "steamos-add-to-steam: $1" >&2
fi
}

if [[ ${1:-} == -ui ]]; then
show_dialog=1
shift
fi

if (( EUID == 0 )); then
show_error "This command cannot be run as root."
exit 1
fi
if (( $# != 1 )); then
usage
exit 2
fi

if ! file=$(realpath -e -- "$1") || [[ ! -f ${file} ]]; then
show_error "File not found: $1"
exit 1
fi

mime=$(kmimetypefinder "${file}")
case ${mime} in
application/x-desktop|application/x-ms-dos-executable|application/x-msdownload|application/vnd.microsoft.portable-executable)
;;
application/x-executable|application/vnd.appimage|application/x-shellscript|application/x-sh|text/x-shellscript)
if [[ ! -x ${file} ]]; then
show_error "The selected file is not executable."
exit 1
fi
;;
*)
show_error "Unsupported file type: ${mime}"
exit 1
;;
esac

if [[ ${XDG_SESSION_TYPE:-tty} == tty ]] &&
! pgrep -u "$(id -u)" -f '/steamrtarm64/steam([[:space:]]|$)' >/dev/null 2>&1; then
show_error "Steam must be running when this command is used from a terminal session."
exit 1
fi

encoded_url=$(python3 -c \
'import sys, urllib.parse; print("steam://addnonsteamgame/" + urllib.parse.quote(sys.argv[1], safe=""))' \
"${file}")

# Steam watches this marker while processing the addnonsteamgame URI. The
# launcher forwards the URI to an existing client or starts the ARM client.
touch /tmp/addnonsteamgamefile
nohup /usr/libexec/armada/launch-steam --desktop "${encoded_url}" \
</dev/null >/dev/null 2>&1 &
119 changes: 119 additions & 0 deletions system_files/usr/bin/steamos-nested-desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
#!/usr/bin/bash
# SPDX-License-Identifier: GPL-2.0-or-later
set -euo pipefail

if [[ ${XDG_SESSION_DESKTOP:-} == KDE || ${KDE_FULL_SESSION:-} == true ]]; then
echo "steamos-nested-desktop: refusing to launch inside KDE" >&2
if [[ ! -t 1 ]] && command -v kdialog >/dev/null 2>&1; then
kdialog --title "Nested Desktop" --sorry \
"Nested Desktop should be launched from Gaming Mode."
fi
exit 0
fi

state_dir="${XDG_STATE_HOME:-${HOME}/.local/state}/armada"
install -d -m 0700 "${state_dir}"
exec >>"${state_dir}/nested-desktop.log" 2>&1
echo "=== Nested Desktop $(date --iso-8601=seconds) ==="

# Gamescope's overlay and session-specific portals must not leak into Plasma.
unset LD_PRELOAD XDG_DESKTOP_PORTAL_DIR XDG_SEAT_PATH XDG_SESSION_PATH
cd "${HOME}"

parent_runtime=${XDG_RUNTIME_DIR:?XDG_RUNTIME_DIR is not set}
nested_runtime=$(mktemp -d "${parent_runtime}/nested-desktop.XXXXXXXX")

cleanup() {
umount --recursive "${nested_runtime}" >/dev/null 2>&1 || true
rm -rf --one-file-system -- "${nested_runtime}" || true
}
trap cleanup EXIT

# Keep audio connected to the parent Gaming Mode PipeWire session.
if [[ -S "${parent_runtime}/pulse/native" ]]; then
install -d -m 0700 "${nested_runtime}/pulse"
ln -s "${parent_runtime}/pulse/native" "${nested_runtime}/pulse/native"
fi
shopt -s nullglob
for socket in "${parent_runtime}"/pipewire-*; do
ln -s "${socket}" "${nested_runtime}/${socket##*/}"
done
shopt -u nullglob

# Match SteamOS: let Gamescope define the surface, input region, orientation,
# and pixel geometry instead of imposing a second resolution on nested KWin.
install -d -m 0700 "${nested_runtime}/bin"
cat > "${nested_runtime}/bin/kwin_wayland_wrapper" <<'EOF'
#!/usr/bin/bash
exec /usr/bin/kwin_wayland_wrapper --fullscreen true --no-lockscreen "$@"
EOF
chmod 0755 "${nested_runtime}/bin/kwin_wayland_wrapper"

# Armada changes between a rotated handheld panel and Gamescope's landscape
# surface. Once KScreen has attached the nested output, use 150% logical scaling
# on Gamescope's native surface, then remap Plasma Shell so the panel is created
# on the final screen geometry.
cat > "${nested_runtime}/bin/start-nested-plasma" <<EOF
#!/usr/bin/bash
set -u

/usr/libexec/armada/start-plasma &
session_pid=\$!

(
for _ in {1..60}; do
if gdbus call --session --dest org.kde.plasmashell \
--object-path /PlasmaShell \
--method org.freedesktop.DBus.Peer.Ping \
>/dev/null 2>&1; then
shell_pid=\$(pgrep -n -x plasmashell)
for _ in {1..40}; do
if env -u DISPLAY QT_QPA_PLATFORM=wayland \
kscreen-doctor \
output.X11-0.rotation.normal \
output.X11-0.position.0,0 \
output.X11-0.scale.1.5 \
>>"${state_dir}/nested-desktop.log" 2>&1; then
break
fi
sleep 0.25
done
gdbus call --session --dest org.kde.plasmashell \
--object-path /PlasmaShell \
--method org.kde.PlasmaShell.evaluateScript \
'panels().forEach(function (panel) { var geometry = screenGeometry(panel.screen); panel.lengthMode = "fill"; panel.minimumLength = geometry.width; panel.maximumLength = geometry.width; });' \
>>"${state_dir}/nested-desktop.log" 2>&1 || true
sleep 0.5
(
while IFS= read -r -d '' entry; do
[[ \${entry} == BASH_FUNC_* ]] && continue
export "\${entry}"
done < "/proc/\${shell_pid}/environ"
unset DISPLAY
export QT_QPA_PLATFORM=wayland
exec /usr/bin/plasmashell --replace
) >>"${state_dir}/nested-desktop.log" 2>&1 &
exit 0
fi
sleep 0.25
done
) &
bootstrap_pid=\$!

wait "\${session_pid}"
status=\$?
kill "\${bootstrap_pid}" >/dev/null 2>&1 || true
wait "\${bootstrap_pid}" 2>/dev/null || true
exit "\${status}"
EOF
chmod 0755 "${nested_runtime}/bin/start-nested-plasma"

export PATH="${nested_runtime}/bin:${PATH}"
export XDG_RUNTIME_DIR="${nested_runtime}"
export DESKTOP_SESSION=plasma
export XDG_CURRENT_DESKTOP=KDE
export XDG_SESSION_DESKTOP=KDE
export XDG_SESSION_TYPE=wayland
export ARMADA_NESTED_DESKTOP=1

dbus-run-session "${nested_runtime}/bin/start-nested-plasma"
26 changes: 14 additions & 12 deletions system_files/usr/libexec/armada/desktop-bootstrap
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
set -euo pipefail

[[ ${XDG_SESSION_TYPE:-} == wayland ]] || exit 0
[[ ${ARMADA_NESTED_DESKTOP:-0} != 1 ]] || exit 0

activation_env=(
DESKTOP_SESSION
Expand All @@ -24,7 +25,6 @@ config_dir="${XDG_CONFIG_HOME:-${HOME}/.config}/armada"
rotation_done="${config_dir}/desktop-rotation.done"
scale_done="${config_dir}/desktop-scale.done"

[[ ! -e "${rotation_done}" || ! -e "${scale_done}" ]] || exit 0
mkdir -p "${config_dir}"

if command -v /usr/libexec/armada/device-env >/dev/null 2>&1; then
Expand All @@ -41,18 +41,20 @@ for _ in {1..5}; do
sleep 2
done

if [[ ! -e "${rotation_done}" ]]; then
case "${panel_orientation}" in
left|right|inverted)
if kscreen-doctor "output.${display_connector}.rotation.${panel_orientation}" >/dev/null 2>&1; then
touch "${rotation_done}"
fi
;;
*)
# KScreen profiles can be rewritten by Gamescope or a nested session. Reapply
# the physical panel's orientation on every full desktop login.
case "${panel_orientation}" in
left|right|inverted)
if kscreen-doctor "output.${display_connector}.rotation.${panel_orientation}" >/dev/null 2>&1; then
touch "${rotation_done}"
;;
esac
fi
fi
;;
*)
if kscreen-doctor "output.${display_connector}.rotation.normal" >/dev/null 2>&1; then
touch "${rotation_done}"
fi
;;
esac

if [[ ! -e "${scale_done}" ]] && kscreen-doctor "output.${display_connector}.scale.1.5" >/dev/null 2>&1; then
touch "${scale_done}"
Expand Down
47 changes: 47 additions & 0 deletions system_files/usr/libexec/armada/desktop-steam-autostart
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#!/usr/bin/bash
set -euo pipefail

# Gaming Mode already owns its Steam process while Nested Desktop is running.
[[ ${ARMADA_NESTED_DESKTOP:-0} != 1 ]] || exit 0

# Plasma's systemd autostart generator can run applications before plasmashell
# and Xwayland have attached to the display. Steam's ARM client crashes if it
# reaches its legacy UI initialization during that window.
shell_pid=""
for _ in {1..60}; do
shell_pid=$(pgrep -n -u "$(id -u)" -x plasmashell 2>/dev/null || true)
[[ -n "${shell_pid}" ]] && break
sleep 0.25
done

# Use the exact display credentials from Plasma rather than the user manager's
# possibly incomplete early-login environment.
if [[ -n "${shell_pid}" && -r "/proc/${shell_pid}/environ" ]]; then
while IFS= read -r -d '' entry; do
[[ ${entry} == BASH_FUNC_* ]] && continue
export "${entry}"
done < "/proc/${shell_pid}/environ"
fi

if command -v xprop >/dev/null 2>&1; then
for _ in {1..240}; do
xprop -root >/dev/null 2>&1 && break
sleep 0.25
done
else
sleep 10
fi
sleep 2

# Avoid opening a second client if Steam was started manually during login.
# Confirm it survives startup before yielding; a competing early autostart may
# briefly create the process and then crash.
if pgrep -u "$(id -u)" -x steam >/dev/null 2>&1; then
sleep 5
pgrep -u "$(id -u)" -x steam >/dev/null 2>&1 && exit 0
fi

state_dir="${XDG_STATE_HOME:-${HOME}/.local/state}/armada"
install -d -m 0700 "${state_dir}"
exec /usr/libexec/armada/launch-steam --desktop \
>>"${state_dir}/desktop-steam.log" 2>&1
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[Desktop Entry]
Type=Application
Name=Nested Desktop
Comment=Run the KDE Plasma desktop inside Gaming Mode
Exec=/usr/bin/bash /usr/bin/steamos-nested-desktop
Icon=preferences-desktop-display
Terminal=false
Categories=System;
X-Steam-Controller-Template=Desktop
11 changes: 11 additions & 0 deletions system_files/usr/share/kio/servicemenus/steam.desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[Desktop Entry]
Type=Service
X-KDE-ServiceTypes=KonqPopupMenu/Plugin
MimeType=application/x-desktop;application/x-executable;application/vnd.appimage;application/x-shellscript;application/x-sh;application/x-ms-dos-executable;application/x-msdownload;application/vnd.microsoft.portable-executable;text/x-shellscript;
Actions=addToSteam;

[Desktop Action addToSteam]
Name=Add to Steam
Icon=steam
TryExec=steamos-add-to-steam
Exec=steamos-add-to-steam -ui %f
9 changes: 9 additions & 0 deletions system_files/usr/share/plasma/kickeractions/steam.desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[Desktop Entry]
Type=Service
Actions=addToSteam;

[Desktop Action addToSteam]
Name=Add to Steam
Icon=steam
TryExec=steamos-add-to-steam
Exec=steamos-add-to-steam -ui %u
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/bash
set -euo pipefail

verb=${1:-}
[[ -n "$verb" ]] || exit 2
shift

case "$verb" in
run|waitforexitandrun)
exec "$@"
;;
destroyprefix)
exit 0
;;
*)
echo "Unsupported Armada Native verb: $verb" >&2
exit 2
;;
esac
Loading