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
1 change: 1 addition & 0 deletions build_files/50-create-user.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ cat > /etc/sudoers.d/armada-user <<'EOF'
%wheel ALL=(ALL) NOPASSWD: /usr/libexec/armada/session-control switch-desktop
%wheel ALL=(ALL) NOPASSWD: /usr/libexec/armada/session-control switch-gamemode
%wheel ALL=(ALL) NOPASSWD: /usr/libexec/armada/session-control default-gamemode
%wheel ALL=(ALL) NOPASSWD: /usr/libexec/armada/touchscreen-inhibit *
%wheel ALL=(ALL) NOPASSWD: /usr/libexec/armada/armada-installer *
EOF
chmod 0440 /etc/sudoers.d/armada-user
2 changes: 1 addition & 1 deletion system_files/etc/gamescope-session-plus/sessions.d/steam
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ fi
# touchscreen so fingers on the dark glass don't move Steam's cursor.
# The desktop session re-enables it (see desktop-bootstrap).
if [[ -n "${ARMADA_SECONDARY_TOUCHSCREEN:-}" ]]; then
/usr/libexec/armada/touchscreen-inhibit "$ARMADA_SECONDARY_TOUCHSCREEN" 1 2>/dev/null || true
sudo -n /usr/libexec/armada/touchscreen-inhibit "$ARMADA_SECONDARY_TOUCHSCREEN" 1 2>/dev/null || true
fi
USE_ROTATION_SHADER="${ARMADA_GAMESCOPE_USE_ROTATION_SHADER:-0}"
if [[ -n "${ARMADA_GAMESCOPE_FAKE_OUTPUT_MM:-}" ]]; then
Expand Down
6 changes: 6 additions & 0 deletions system_files/usr/lib/armada/devices/ayaneo-pocket-ds.conf
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ ARMADA_DEVICE_ID=ayaneo-pocket-ds
ARMADA_DEVICE_NAME='AYANEO Pocket DS'
ARMADA_SOC_CLASS=SM8550

# Dual screen: top panel is DSI-1 (mdss_dsi0), bottom panel is DSI-2
# (mdss_dsi1). Gamescope drives only the top panel; the desktop session
# uses both (see desktop-bootstrap).
ARMADA_PRIMARY_CONNECTOR=DSI-1
ARMADA_PRIMARY_TOUCHSCREEN='generic ft5x06 (44)'
ARMADA_SECONDARY_CONNECTOR=DSI-2
ARMADA_SECONDARY_TOUCHSCREEN='Goodix Capacitive TouchScreen'
ARMADA_PANEL_ORIENTATION=left
ARMADA_PANEL_NATIVE_WIDTH=1080
ARMADA_PANEL_NATIVE_HEIGHT=1920
Expand Down
24 changes: 16 additions & 8 deletions system_files/usr/libexec/armada/desktop-bootstrap
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ fi
# The gaming session inhibits the secondary touchscreen (the desktop is the
# only session that lights up the secondary panel); re-enable it every start.
if [[ -n "${ARMADA_SECONDARY_TOUCHSCREEN:-}" ]]; then
/usr/libexec/armada/touchscreen-inhibit "$ARMADA_SECONDARY_TOUCHSCREEN" 0 2>/dev/null || true
sudo -n /usr/libexec/armada/touchscreen-inhibit "$ARMADA_SECONDARY_TOUCHSCREEN" 0 2>/dev/null || true
fi

display_connector="${ARMADA_PRIMARY_CONNECTOR:-}"
Expand All @@ -46,8 +46,18 @@ else
scale_done="${config_dir}/desktop-scale.done"
fi
dual_done="${config_dir}/desktop-dual-screen.done"
dual_required=0
if [[ -n "${ARMADA_SECONDARY_CONNECTOR:-}" ]]; then
dual_required=1
# Keep dual-screen devices session-scoped: Game Mode may disable the
# secondary output before leaving Plasma, so desktop login must re-enable
# it even when the initial layout marker already exists.
dual_done="${config_dir}/desktop-dual-screen-${display_connector}-${ARMADA_SECONDARY_CONNECTOR}.done"
fi

[[ ! -e "${rotation_done}" || ! -e "${scale_done}" || ! -e "${dual_done}" ]] || exit 0
if [[ "${dual_required}" != 1 ]]; then
[[ ! -e "${rotation_done}" || ! -e "${scale_done}" || ! -e "${dual_done}" ]] || exit 0
fi
mkdir -p "${config_dir}"

for _ in {1..5}; do
Expand All @@ -72,10 +82,8 @@ if [[ ! -e "${scale_done}" ]] && kscreen-doctor "output.${display_connector}.sca
touch "${scale_done}"
fi

if [[ ! -e "${dual_done}" ]]; then
if [[ -z "${ARMADA_SECONDARY_CONNECTOR:-}" ]]; then
touch "${dual_done}"
elif /usr/libexec/armada/setup-dual-screen; then
touch "${dual_done}"
fi
if [[ -z "${ARMADA_SECONDARY_CONNECTOR:-}" ]]; then
[[ -e "${dual_done}" ]] || touch "${dual_done}"
elif /usr/libexec/armada/setup-dual-screen; then
touch "${dual_done}"
fi
36 changes: 36 additions & 0 deletions system_files/usr/libexec/armada/session-control
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
# zz- sorts after armada.conf so its Session= wins (User=/Relogin= stay there).
set -euo pipefail

eval "$(/usr/libexec/armada/device-env)"

case "${1:-}" in
switch-desktop) session=armada-plasma.desktop ;;
switch-gamemode) session=gamescope-session-steam.desktop ;;
Expand Down Expand Up @@ -35,9 +37,43 @@ logout_plasma() {
"${qdbus}" org.kde.Shutdown /Shutdown org.kde.Shutdown.logout
}

set_secondary_output() {
local state=$1
local user=armada
local uid=1000
local bus="/run/user/${uid}/bus"

[[ -n "${ARMADA_SECONDARY_CONNECTOR:-}" ]] || return 0
[[ -S "${bus}" ]] || return 0
command -v kscreen-doctor >/dev/null 2>&1 || return 0

sudo -Eu "${user}" env \
XDG_RUNTIME_DIR="/run/user/${uid}" \
DBUS_SESSION_BUS_ADDRESS="unix:path=${bus}" \
WAYLAND_DISPLAY="${WAYLAND_DISPLAY:-wayland-0}" \
kscreen-doctor "output.${ARMADA_SECONDARY_CONNECTOR}.${state}" >/dev/null 2>&1 || true
}

set_secondary_touchscreen() {
local inhibited=$1

[[ -n "${ARMADA_SECONDARY_TOUCHSCREEN:-}" ]] || return 0
command -v /usr/libexec/armada/touchscreen-inhibit >/dev/null 2>&1 || return 0

/usr/libexec/armada/touchscreen-inhibit "${ARMADA_SECONDARY_TOUCHSCREEN}" "${inhibited}" 2>/dev/null || true
}

printf '[Autologin]\nSession=%s\n' "${session}" > /etc/sddm.conf.d/zz-steamos-autologin.conf
systemctl reset-failed sddm.service 2>/dev/null || true

if [[ ${1:-} == "switch-gamemode" ]]; then
# Apply the Game Mode policy before logging Plasma out. The gamescope
# session repeats the touchscreen inhibit when Steam starts, but doing it
# here avoids a live lower digitizer during the transition.
set_secondary_touchscreen 1
set_secondary_output disable
fi

if [[ "${default_only:-0}" == "1" ]]; then
exit 0
fi
Expand Down
22 changes: 21 additions & 1 deletion system_files/usr/libexec/armada/setup-dual-screen
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def pin_touchscreen(name, connector, devices):
if name not in devices:
print(f"setup-dual-screen: input device not found: {name}",
file=sys.stderr)
return False
return True
vendor, product, event = devices[name]
# Persisted mapping, applied by KWin when the device is added.
persisted = subprocess.run(
Expand All @@ -89,6 +89,7 @@ def main():
env = device_env()
primary = env.get("ARMADA_PRIMARY_CONNECTOR")
secondary = env.get("ARMADA_SECONDARY_CONNECTOR")
orientation = env.get("ARMADA_PANEL_ORIENTATION") or "normal"
if not primary or not secondary:
return 0

Expand All @@ -105,6 +106,25 @@ def main():
file=sys.stderr)
return 1

commands = [
f"output.{primary}.enable",
f"output.{secondary}.enable",
]
if orientation in ("left", "right", "inverted", "normal"):
commands.extend([
f"output.{primary}.rotation.{orientation}",
f"output.{secondary}.rotation.{orientation}",
])
subprocess.run(["kscreen-doctor", *commands], check=True)

# Re-read after enable/rotation so placement uses the actual logical sizes.
doc = subprocess.run(
["kscreen-doctor", "-j"],
capture_output=True, text=True, check=True,
).stdout
outputs = json.JSONDecoder().raw_decode(doc)[0]["outputs"]
by_name = {o["name"]: o for o in outputs}

primary_size = logical_size(by_name[primary])
secondary_size = logical_size(by_name[secondary])
if primary_size is None or secondary_size is None:
Expand Down
55 changes: 55 additions & 0 deletions tests/pocket-ds-dual-screen-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/usr/bin/env bash
set -euo pipefail

ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"

pocket_ds_conf="$ROOT/system_files/usr/lib/armada/devices/ayaneo-pocket-ds.conf"
setup_dual="$ROOT/system_files/usr/libexec/armada/setup-dual-screen"
session_control="$ROOT/system_files/usr/libexec/armada/session-control"
device_env="$ROOT/system_files/usr/libexec/armada/device-env"

# Pocket DS advertises the lower panel to desktop-bootstrap, while gamescope
# still targets the primary connector through sessions.d/steam.
grep -q '^ARMADA_PRIMARY_CONNECTOR=DSI-1$' "$pocket_ds_conf"
grep -q '^ARMADA_SECONDARY_CONNECTOR=DSI-2$' "$pocket_ds_conf"
grep -q '^ARMADA_PANEL_ORIENTATION=left$' "$pocket_ds_conf"
grep -q "^ARMADA_PRIMARY_TOUCHSCREEN='generic ft5x06 (44)'$" "$pocket_ds_conf"
grep -q "^ARMADA_SECONDARY_TOUCHSCREEN='Goodix Capacitive TouchScreen'$" "$pocket_ds_conf"
grep -q 'ARMADA_SECONDARY_CONNECTOR' "$device_env"
grep -q 'ARMADA_PRIMARY_TOUCHSCREEN' "$device_env"
grep -q 'ARMADA_SECONDARY_TOUCHSCREEN' "$device_env"
grep -q 'ARMADA_PRIMARY_BACKLIGHT' "$device_env"

# Desktop setup must actively re-enable the second output, not just position it.
grep -q 'f"output.{primary}.enable"' "$setup_dual"
grep -q 'f"output.{secondary}.enable"' "$setup_dual"
grep -q 'f"output.{primary}.rotation.{orientation}"' "$setup_dual"
grep -q 'f"output.{secondary}.rotation.{orientation}"' "$setup_dual"
grep -q 'dual_required=1' "$ROOT/system_files/usr/libexec/armada/desktop-bootstrap"
grep -q 'elif /usr/libexec/armada/setup-dual-screen; then' "$ROOT/system_files/usr/libexec/armada/desktop-bootstrap"

# Switching back to game mode should explicitly disable the secondary output
# and inhibit lower touch before leaving Plasma, so Game Mode cannot scan out
# to the lower panel and the lower digitizer cannot steer Steam during handoff.
# Pocket DS live testing showed the visible lower backlight can remain lit even
# with DSI-2 disabled/DPMS Off; that requires a separate kernel/panel power fix
# and is not something this userspace policy test should overclaim.
grep -q 'set_secondary_touchscreen 1' "$session_control"
grep -q 'set_secondary_output disable' "$session_control"
grep -q 'touchscreen-inhibit "${ARMADA_SECONDARY_TOUCHSCREEN}" "${inhibited}"' "$session_control"
grep -q 'kscreen-doctor "output.${ARMADA_SECONDARY_CONNECTOR}.${state}"' "$session_control"

# Game Mode must never enable or lay out the secondary panel. It may only
# inhibit the secondary touchscreen as a belt-and-suspenders guard; screen and
# backlight availability is Desktop-only via desktop-bootstrap/setup-dual-screen.
grep -q 'sudo -n /usr/libexec/armada/touchscreen-inhibit "$ARMADA_SECONDARY_TOUCHSCREEN" 1' "$ROOT/system_files/etc/gamescope-session-plus/sessions.d/steam"
! grep -q 'ARMADA_SECONDARY_CONNECTOR' "$ROOT/system_files/etc/gamescope-session-plus/sessions.d/steam"
! grep -q 'setup-dual-screen' "$ROOT/system_files/etc/gamescope-session-plus/sessions.d/steam"

grep -q 'sudo -n /usr/libexec/armada/touchscreen-inhibit "$ARMADA_SECONDARY_TOUCHSCREEN" 0' "$ROOT/system_files/usr/libexec/armada/desktop-bootstrap"
grep -q 'NOPASSWD: /usr/libexec/armada/touchscreen-inhibit \*' "$ROOT/build_files/50-create-user.sh"

python3 -m py_compile "$setup_dual"
bash -n "$session_control"

printf 'Pocket DS desktop-only dual-screen checks passed\n'
16 changes: 16 additions & 0 deletions tests/setup-dual-screen-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
SETUP="$ROOT/system_files/usr/libexec/armada/setup-dual-screen"
python3 - <<'PY' "$SETUP"
import importlib.machinery
import importlib.util
import sys
loader = importlib.machinery.SourceFileLoader("setup_dual_screen", sys.argv[1])
spec = importlib.util.spec_from_loader(loader.name, loader)
mod = importlib.util.module_from_spec(spec)
loader.exec_module(mod)
ok = mod.pin_touchscreen("Missing Touch", "DSI-2", {})
assert ok is True, "missing touchscreen should not fail display layout"
print("missing touchscreen is non-fatal")
PY