mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-10-14 19:27:09 -04:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77fd380bb2 | ||
|
|
933ba953a8 | ||
|
|
408233b8e5 | ||
|
|
3c4dd564fc | ||
|
|
522a2d7ed2 |
No files matched your search
@@ -121,6 +121,7 @@ class MainActivity : ComponentActivity() {
|
||||
private var steamDeckMode by mutableStateOf(false)
|
||||
private var steamRepairQueued by mutableStateOf(false)
|
||||
private var mangoapp by mutableStateOf(true)
|
||||
private var dualXwayland by mutableStateOf(true)
|
||||
private var steamController by mutableStateOf(SessionPrefs.CONTROLLER_DECK)
|
||||
private var showProtons by mutableStateOf(false)
|
||||
// Components page: FEX / DXVK / VKD3D-Proton per Proton (ComponentsManager).
|
||||
@@ -1182,6 +1183,7 @@ class MainActivity : ComponentActivity() {
|
||||
steamDeckMode = mode == SessionService.MODE_STEAM && steamDeckMode,
|
||||
steamRepairQueued = steamRepairQueued,
|
||||
mangoapp = mangoapp,
|
||||
dualXwayland = dualXwayland,
|
||||
steamController = if (mode == SessionService.MODE_STEAM) steamController else null,
|
||||
runSteamAtStartup = mode == SessionService.MODE_STEAM && runSteamAtStartup,
|
||||
wifiDiscovery = if (mode == SessionService.MODE_STEAM) wifiDiscovery else null,
|
||||
@@ -1265,6 +1267,7 @@ class MainActivity : ComponentActivity() {
|
||||
steamChannel = SessionPrefs.steamChannel(this)
|
||||
},
|
||||
onMangoapp = { on -> SessionPrefs.setMangoapp(this, on); mangoapp = on },
|
||||
onDualXwayland = { on -> SessionPrefs.setDualXwayland(this, on); dualXwayland = on },
|
||||
onSteamController = { id -> SessionPrefs.setSteamController(this, id); steamController = id },
|
||||
onRunSteamAtStartup = { on ->
|
||||
SessionPrefs.setRunSteamAtStartup(this, on)
|
||||
@@ -1380,6 +1383,7 @@ class MainActivity : ComponentActivity() {
|
||||
steamDeckMode = SessionPrefs.steamDeckMode(this)
|
||||
steamRepairQueued = SteamRepair.queued(this)
|
||||
mangoapp = SessionPrefs.mangoapp(this)
|
||||
dualXwayland = SessionPrefs.dualXwayland(this)
|
||||
steamController = SessionPrefs.steamController(this)
|
||||
runSteamAtStartup = SessionPrefs.runSteamAtStartup(this)
|
||||
addedGamesDirs = SessionPrefs.addedGamesDirs(this)
|
||||
|
||||
@@ -154,6 +154,7 @@ object DeviceReport {
|
||||
k("No GL error checks", SessionPrefs.noGlError(context))
|
||||
k("Steam Deck mode", SessionPrefs.steamDeckMode(context))
|
||||
k("Steam controller", SessionPrefs.steamController(context))
|
||||
k("Two Xwayland servers", SessionPrefs.dualXwayland(context))
|
||||
k("Upscaler", (SessionPrefs.upscalerChoices.firstOrNull { it.first == SessionPrefs.upscaler(context) }?.second ?: "Off") +
|
||||
" (sharpness ${SessionPrefs.upscaleSharpness(context)}%)")
|
||||
k("FEX preset", SessionPrefs.fexPreset(context).ifEmpty { "FEX defaults" })
|
||||
|
||||
@@ -41,6 +41,7 @@ object SessionFiles {
|
||||
"libblfastpath.so" to "usr/local/lib/libblfastpath.so",
|
||||
"libssbs.so" to "usr/local/lib/libssbs.so",
|
||||
"usr/local/bin/droiddeck-session" to "usr/local/bin/droiddeck-session",
|
||||
"usr/local/bin/droiddeck-xwindows" to "usr/local/bin/droiddeck-xwindows",
|
||||
"usr/local/bin/steam-compatibility" to "usr/local/bin/steam-compatibility",
|
||||
"usr/local/bin/droiddeck-clipboard" to "usr/local/bin/droiddeck-clipboard",
|
||||
"usr/local/bin/droiddeck-steam-install" to "usr/local/bin/droiddeck-steam-install",
|
||||
|
||||
@@ -418,6 +418,14 @@ object SessionPrefs {
|
||||
fun mangoapp(context: Context): Boolean = prefs(context).getBoolean("mangoapp", true)
|
||||
fun setMangoapp(context: Context, on: Boolean) { prefs(context).edit().putBoolean("mangoapp", on).apply() }
|
||||
|
||||
/**
|
||||
* Steam on two Xwayland servers, as SteamOS runs it: the client on one, its games on the other
|
||||
* (droiddeck-session, BL_XWAYLAND_COUNT). Off puts both back on one server, for a game whose
|
||||
* window opens behind the client and never takes focus. On by default.
|
||||
*/
|
||||
fun dualXwayland(context: Context): Boolean = prefs(context).getBoolean("dualXwayland", true)
|
||||
fun setDualXwayland(context: Context, on: Boolean) { prefs(context).edit().putBoolean("dualXwayland", on).apply() }
|
||||
|
||||
/**
|
||||
* Zink's lazy descriptor mode (ZINK_DESCRIPTORS=lazy) with its compact set layout
|
||||
* (ZINK_DEBUG=compact) for the client's GL-on-Vulkan UI, as WinNative runs it. On by default.
|
||||
|
||||
@@ -614,6 +614,7 @@ class SessionService : Service() {
|
||||
guest.add("GLIBC_TUNABLES=glibc.pthread.rseq=0:glibc.malloc.top_pad=16777216")
|
||||
if (SessionState.mode == MODE_STEAM) guest.add("BL_STEAMDECK=" + (if (SessionPrefs.steamDeckMode(this)) "1" else "0"))
|
||||
if (SessionState.mode == MODE_STEAM) guest.add("BL_MANGOAPP=" + (if (SessionPrefs.mangoapp(this)) "1" else "0"))
|
||||
if (SessionState.mode == MODE_STEAM) guest.add("BL_XWAYLAND_COUNT=" + (if (SessionPrefs.dualXwayland(this)) "2" else "1"))
|
||||
if (steamHere) guest.add("BL_STEAM_CHANNEL=" + SessionPrefs.steamChannel(this))
|
||||
if (SessionState.mode == MODE_STEAM) {
|
||||
guest.add("BL_GAMESCOPE_STRETCH_16X9=" + (if (SessionPrefs.stretch16x9(this)) "1" else "0"))
|
||||
|
||||
@@ -94,6 +94,8 @@ class ModeSettings(
|
||||
val steamRepairQueued: Boolean = false,
|
||||
/** Steam, Deck mode: the QAM's performance overlay (mangoapp) is started. */
|
||||
val mangoapp: Boolean = true,
|
||||
/** Steam only: the client and its games on separate Xwayland servers. */
|
||||
val dualXwayland: Boolean = true,
|
||||
/** Steam only: what the pad is to the client (SessionPrefs.CONTROLLER_*); null outside Steam. */
|
||||
val steamController: String? = null,
|
||||
/** Steam only: start a Steam session when DroidDeck opens. */
|
||||
@@ -152,6 +154,7 @@ class ModeSettingsActions(
|
||||
val onSteamRepair: () -> Unit = {},
|
||||
val onSteamDeckMode: (Boolean) -> Unit = {},
|
||||
val onMangoapp: (Boolean) -> Unit = {},
|
||||
val onDualXwayland: (Boolean) -> Unit = {},
|
||||
val onSteamController: (String) -> Unit = {},
|
||||
val onRunSteamAtStartup: (Boolean) -> Unit = {},
|
||||
val onWifiDiscovery: (Boolean) -> Unit = {},
|
||||
@@ -390,6 +393,11 @@ fun ModeSettingsPage(s: ModeSettings, a: ModeSettingsActions) {
|
||||
stringResource(R.string.mode_mangoapp_hint),
|
||||
s.mangoapp, onChange = a.onMangoapp,
|
||||
)
|
||||
ToggleRow(
|
||||
host, "dualxwayland", stringResource(R.string.mode_dual_xwayland),
|
||||
stringResource(R.string.mode_dual_xwayland_hint),
|
||||
s.dualXwayland, onChange = a.onDualXwayland,
|
||||
)
|
||||
// Deck mode fixes the branch (SessionPrefs.steamChannel); the choice is for Deck mode off.
|
||||
if (s.steamDeckMode) SettingsRow(stringResource(R.string.mode_branch), stringResource(R.string.mode_branch_deck)) {}
|
||||
else ChoiceRow(
|
||||
|
||||
@@ -289,6 +289,8 @@
|
||||
<string name="mode_deck_mode_hint">La interfaz Deck de Steam y los controles de rendimiento de Quick Access. Se aplica en la próxima sesión.</string>
|
||||
<string name="mode_mangoapp">Overlay de rendimiento</string>
|
||||
<string name="mode_mangoapp_hint">Desactívalo si los juegos fallan o se quedan en negro en modo Deck. Se aplica en la próxima sesión.</string>
|
||||
<string name="mode_dual_xwayland">Dos servidores Xwayland</string>
|
||||
<string name="mode_dual_xwayland_hint">Ejecuta los juegos en su propio servidor X, como SteamOS. Desactívalo si un juego se abre detrás de Steam y nunca toma el foco. Se aplica en la próxima sesión.</string>
|
||||
<string name="mode_branch">Rama del cliente</string>
|
||||
<string name="mode_branch_deck">Beta pública de Steam Deck (requerida por el modo Deck).</string>
|
||||
<string name="mode_branch_hint">Se aplica en la próxima sesión. Steam puede actualizarse una vez.</string>
|
||||
|
||||
@@ -263,6 +263,8 @@
|
||||
<string name="mode_deck_mode_hint">Interface Steam Deck et contrôles de performance à accès rapide. Prendra effet à la prochaine session.</string>
|
||||
<string name="mode_mangoapp">Superposition de performance</string>
|
||||
<string name="mode_mangoapp_hint">Désactivez cette option si les jeux plantent ou affichent un écran noir en mode Deck. Prendra effet à la prochaine session.</string>
|
||||
<string name="mode_dual_xwayland">Deux serveurs Xwayland</string>
|
||||
<string name="mode_dual_xwayland_hint">Lance les jeux sur leur propre serveur X, comme SteamOS. Désactivez cette option si un jeu s’ouvre derrière Steam et ne prend jamais le focus. Prendra effet à la prochaine session.</string>
|
||||
<string name="mode_branch">Branche du client</string>
|
||||
<string name="mode_branch_deck">Bêta Steam Deck (requis pour le mode Deck).</string>
|
||||
<string name="mode_branch_hint">Prendra effet à la prochaine session. Steam peut se mettre à jour une fois de lui-même.</string>
|
||||
|
||||
@@ -289,6 +289,8 @@
|
||||
<string name="mode_deck_mode_hint">Steam DeckのUIとクイックアクセスパフォーマンスコントロール。次回のセッションで有効になります。</string>
|
||||
<string name="mode_mangoapp">パフォーマンスオーバーレイ</string>
|
||||
<string name="mode_mangoapp_hint">Deckモードでゲームがクラッシュしたり画面が真っ暗になる場合は、これをオフにしてください。次回のセッションで有効になります。</string>
|
||||
<string name="mode_dual_xwayland">2つのXwaylandサーバー</string>
|
||||
<string name="mode_dual_xwayland_hint">SteamOSと同様に、ゲームを専用のXサーバーで実行します。ゲームがSteamの背後で開き、フォーカスされない場合はオフにしてください。次回のセッションで有効になります。</string>
|
||||
<string name="mode_branch">クライアントブランチ</string>
|
||||
<string name="mode_branch_deck">Steam Deckベータ版(Deckモードに必要)。</string>
|
||||
<string name="mode_branch_hint">次回のセッションで有効になります。Steamが自動的に一度アップデートされる場合があります。</string>
|
||||
|
||||
@@ -289,6 +289,8 @@
|
||||
<string name="mode_deck_mode_hint">Steam Deck UI 및 빠른 액세스 성능 제어. 다음 세션에 적용됩니다.</string>
|
||||
<string name="mode_mangoapp">성능 오버레이</string>
|
||||
<string name="mode_mangoapp_hint">Deck 모드에서 게임이 충돌하거나 화면이 검게 변하면 이 옵션을 끄세요. 다음 세션에 적용됩니다.</string>
|
||||
<string name="mode_dual_xwayland">Xwayland 서버 2개</string>
|
||||
<string name="mode_dual_xwayland_hint">SteamOS처럼 게임을 별도의 X 서버에서 실행합니다. 게임이 Steam 뒤에서 열리고 포커스를 받지 못하면 이 옵션을 끄세요. 다음 세션에 적용됩니다.</string>
|
||||
<string name="mode_branch">클라이언트 브랜치</string>
|
||||
<string name="mode_branch_deck">Steam Deck 베타(Deck 모드에 필요).</string>
|
||||
<string name="mode_branch_hint">다음 세션에 적용됩니다. Steam이 자체적으로 한 번 업데이트될 수 있습니다.</string>
|
||||
|
||||
@@ -263,6 +263,8 @@
|
||||
<string name="mode_deck_mode_hint">Интерфейс Steam Deck и управление производительностью через быстрый доступ. Вступит в силу в следующей сессии.</string>
|
||||
<string name="mode_mangoapp">Оверлей производительности</string>
|
||||
<string name="mode_mangoapp_hint">Отключите, если игры падают или появляется черный экран в режиме Deck. Вступит в силу в следующей сессии.</string>
|
||||
<string name="mode_dual_xwayland">Два сервера Xwayland</string>
|
||||
<string name="mode_dual_xwayland_hint">Запускает игры на отдельном X-сервере, как в SteamOS. Отключите, если игра открывается позади Steam и не получает фокус. Вступит в силу в следующей сессии.</string>
|
||||
<string name="mode_branch">Ветка клиента</string>
|
||||
<string name="mode_branch_deck">Бета-версия Steam Deck (требуется для режима Deck).</string>
|
||||
<string name="mode_branch_hint">Вступит в силу в следующей сессии. Steam может обновиться самостоятельно один раз.</string>
|
||||
|
||||
@@ -289,6 +289,8 @@
|
||||
<string name="mode_deck_mode_hint">Steam 的 Deck 界面和快捷访问性能控制。将在下次会话生效。</string>
|
||||
<string name="mode_mangoapp">性能覆盖层</string>
|
||||
<string name="mode_mangoapp_hint">若游戏在 Deck 模式下崩溃或黑屏,请关闭性能覆盖层。将在下次会话生效。</string>
|
||||
<string name="mode_dual_xwayland">双 Xwayland 服务器</string>
|
||||
<string name="mode_dual_xwayland_hint">像 SteamOS 一样在独立的 X 服务器上运行游戏。若游戏在 Steam 后面打开且始终无法获得焦点,请关闭此项。将在下次会话生效。</string>
|
||||
<string name="mode_branch">客户端分支</string>
|
||||
<string name="mode_branch_deck">Steam Deck 公开测试版(Deck 模式所必需)。</string>
|
||||
<string name="mode_branch_hint">将在下次会话生效。Steam 可能会自行更新一次。</string>
|
||||
|
||||
@@ -289,6 +289,8 @@
|
||||
<string name="mode_deck_mode_hint">Steam 的 Deck 介面及快速存取效能控制。下次會話生效。</string>
|
||||
<string name="mode_mangoapp">效能疊加層</string>
|
||||
<string name="mode_mangoapp_hint">如果在 Deck 模式下遊戲當機或黑畫面,請關閉此項。下次會話生效。</string>
|
||||
<string name="mode_dual_xwayland">雙 Xwayland 伺服器</string>
|
||||
<string name="mode_dual_xwayland_hint">像 SteamOS 一樣在獨立的 X 伺服器上執行遊戲。如果遊戲在 Steam 後面開啟且一直無法取得焦點,請關閉此項。下次會話生效。</string>
|
||||
<string name="mode_branch">用戶端分支</string>
|
||||
<string name="mode_branch_deck">Steam Deck 公開測試版(Deck 模式需要)。</string>
|
||||
<string name="mode_branch_hint">下次會話生效。Steam 可能會自行更新一次。</string>
|
||||
|
||||
@@ -289,6 +289,8 @@
|
||||
<string name="mode_deck_mode_hint">Steam Deck 介面和快速存取效能控制。下次工作階段生效。</string>
|
||||
<string name="mode_mangoapp">效能重疊層</string>
|
||||
<string name="mode_mangoapp_hint">如果 Deck 模式下遊戲崩潰或黑畫面,請關閉此項。下次工作階段生效。</string>
|
||||
<string name="mode_dual_xwayland">雙 Xwayland 伺服器</string>
|
||||
<string name="mode_dual_xwayland_hint">像 SteamOS 一樣在獨立的 X 伺服器上執行遊戲。如果遊戲在 Steam 後面開啟且一直無法取得焦點,請關閉此項。下次工作階段生效。</string>
|
||||
<string name="mode_branch">用戶端分支</string>
|
||||
<string name="mode_branch_deck">Steam Deck 公開測試版(Deck 模式所需)。</string>
|
||||
<string name="mode_branch_hint">下次工作階段生效。Steam 可能會自行更新一次。</string>
|
||||
|
||||
@@ -289,6 +289,8 @@
|
||||
<string name="mode_deck_mode_hint">Steam\'s Deck interface and Quick Access performance controls. Applies next session.</string>
|
||||
<string name="mode_mangoapp">Performance overlay</string>
|
||||
<string name="mode_mangoapp_hint">Turn off if games crash or go black in Deck mode. Applies next session.</string>
|
||||
<string name="mode_dual_xwayland">Two Xwayland servers</string>
|
||||
<string name="mode_dual_xwayland_hint">Runs games on their own X server, as SteamOS does. Turn off if a game opens behind Steam and never takes focus. Applies next session.</string>
|
||||
<string name="mode_branch">Client branch</string>
|
||||
<string name="mode_branch_deck">Steam Deck public beta (required by Deck mode).</string>
|
||||
<string name="mode_branch_hint">Applies next session. Steam may update itself once.</string>
|
||||
|
||||
@@ -182,7 +182,8 @@ if [ -z "${BL_INSIDE:-}" ]; then
|
||||
# Resolution never reaches its display (docs/development/steam-display-scaling.md). The client's
|
||||
# overlay is also on another server than the game, so opening it no longer takes the game's X
|
||||
# keyboard focus, which made games like FlatOut shrink their window.
|
||||
# BL_XWAYLAND_COUNT=1 in Download/droiddeck-env goes back to one.
|
||||
# The app sends BL_XWAYLAND_COUNT from Steam settings (Two Xwayland servers); 1 goes back to one,
|
||||
# and a line in Download/droiddeck-env still wins.
|
||||
if [ "$mode" = steam ] && [ "${BL_XWAYLAND_COUNT:-2}" -gt 1 ]; then
|
||||
args+=(--xwayland-count "${BL_XWAYLAND_COUNT:-2}")
|
||||
export STEAM_MULTIPLE_XWAYLANDS=1
|
||||
@@ -515,6 +516,26 @@ case $mode in
|
||||
v=${v# }
|
||||
echo "${v:-$2}"
|
||||
}
|
||||
# What gamescope had to choose from when focus moved, and again as late windows map:
|
||||
# the apps and windows it counts as focusable (window, app id, pid; on the client's
|
||||
# server, for every server), the one it picked, and the client's base-layer order; then
|
||||
# each server's top-level windows with the app id they carry (STEAM_GAME) and their
|
||||
# WM_STATE (droiddeck-xwindows). A game window that opens behind the client and never
|
||||
# takes focus shows here as missing from the focusable list, or listed with no app id.
|
||||
focus_snapshot() {
|
||||
local d
|
||||
echo "== focus state (after $1 at $2s):" \
|
||||
$(xprop -root GAMESCOPE_FOCUSED_APP GAMESCOPE_FOCUSED_WINDOW GAMESCOPE_FOCUSABLE_APPS \
|
||||
GAMESCOPE_FOCUSABLE_WINDOWS GAMESCOPECTRL_BASELAYER_APPID 2>/dev/null | sed 's/(CARDINAL)//' | tr '\n' ' ')
|
||||
for d in $(bl_displays); do droiddeck-xwindows "$d" 2>/dev/null; done
|
||||
}
|
||||
focus_snapshots() {
|
||||
local at=0 t
|
||||
for t in 0 5 15 30; do
|
||||
nap $((t - at)); at=$t
|
||||
focus_snapshot "$1" "$t"
|
||||
done
|
||||
}
|
||||
tmode= app=
|
||||
xprop -spy -root STEAM_TOUCH_CLICK_MODE GAMESCOPE_FOCUSED_APP 2>/dev/null | while read -r line; do
|
||||
case $line in
|
||||
@@ -529,6 +550,7 @@ case $mode in
|
||||
[ "$v" = "$app" ] && continue
|
||||
app=$v
|
||||
echo "== focused app: $app (touch mode ${tmode:-unset}) at $(date +%T)"
|
||||
focus_snapshots "$app" &
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
#!/usr/bin/python3
|
||||
# The top-level windows of one Xwayland server, one line each, for the session log: what gamescope
|
||||
# had to pick focus from. Each line has the window, whether it is mapped, override-redirect, its
|
||||
# geometry, the app id it carries (STEAM_GAME, which gamescope focuses by), its WM_STATE, pid and
|
||||
# class. Then whatever is set of what else gamescope weighs before it counts a mapped window as
|
||||
# focusable: an InputOnly class, _NET_WM_WINDOW_OPACITY (0 is never focusable), STEAM_OVERLAY,
|
||||
# GAMESCOPE_EXTERNAL_OVERLAY, STEAM_INPUT_FOCUS and _NET_WM_STATE (skip taskbar and pager without
|
||||
# fullscreen is left out of the focusable list the client reads). Windows that are neither mapped
|
||||
# nor tagged with an app id are left out - wine keeps many hidden ones. The rootfs has xprop but no xwininfo, so this speaks the X protocol itself.
|
||||
#
|
||||
# droiddeck-xwindows :1
|
||||
import os
|
||||
import socket
|
||||
import struct
|
||||
import sys
|
||||
|
||||
MAP_STATE = ("unmapped", "unviewable", "viewable")
|
||||
WM_STATES = {0: "Withdrawn", 1: "Normal", 3: "Iconic"}
|
||||
|
||||
|
||||
class X:
|
||||
def __init__(self, display):
|
||||
num = display.lstrip(":").split(".")[0]
|
||||
self.s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
self.s.settimeout(2)
|
||||
self.s.connect("/tmp/.X11-unix/X" + num)
|
||||
self.s.sendall(struct.pack("<BxHHHHxx", 0x6C, 11, 0, 0, 0))
|
||||
head = self.read(8)
|
||||
if head[0] != 1:
|
||||
raise OSError("X connection refused")
|
||||
setup = self.read(struct.unpack_from("<H", head, 6)[0] * 4)
|
||||
vendor_len = struct.unpack_from("<H", setup, 16)[0]
|
||||
formats = setup[21]
|
||||
self.root = struct.unpack_from("<I", setup, 32 + (vendor_len + 3) // 4 * 4 + formats * 8)[0]
|
||||
|
||||
def read(self, n):
|
||||
buf = b""
|
||||
while len(buf) < n:
|
||||
chunk = self.s.recv(n - len(buf))
|
||||
if not chunk:
|
||||
raise OSError("X connection closed")
|
||||
buf += chunk
|
||||
return buf
|
||||
|
||||
def request(self, data):
|
||||
"""Send one request and return its reply, or None for an X error (a window that went)."""
|
||||
self.s.sendall(data)
|
||||
while True:
|
||||
head = self.read(32)
|
||||
if head[0] == 0:
|
||||
return None
|
||||
if head[0] == 1:
|
||||
return head + self.read(struct.unpack_from("<I", head, 4)[0] * 4)
|
||||
# an event; none are selected, but skip anything that arrives
|
||||
|
||||
def atom(self, name):
|
||||
n = name.encode()
|
||||
pad = (4 - len(n) % 4) % 4
|
||||
r = self.request(struct.pack("<BBHHxx", 16, 1, 2 + (len(n) + pad) // 4, len(n)) + n + b"\0" * pad)
|
||||
return struct.unpack_from("<I", r, 8)[0] if r else 0
|
||||
|
||||
def children(self, win):
|
||||
r = self.request(struct.pack("<BxHI", 15, 2, win))
|
||||
if not r:
|
||||
return []
|
||||
n = struct.unpack_from("<H", r, 16)[0]
|
||||
return list(struct.unpack_from("<%dI" % n, r, 32))
|
||||
|
||||
def attributes(self, win):
|
||||
r = self.request(struct.pack("<BxHI", 3, 2, win))
|
||||
return (r[26], r[27], struct.unpack_from("<H", r, 12)[0]) if r else None
|
||||
|
||||
def atom_name(self, atom):
|
||||
r = self.request(struct.pack("<BxHI", 17, 2, atom))
|
||||
if not r:
|
||||
return str(atom)
|
||||
return r[32:32 + struct.unpack_from("<H", r, 8)[0]].decode(errors="replace")
|
||||
|
||||
def geometry(self, win):
|
||||
r = self.request(struct.pack("<BxHI", 14, 2, win))
|
||||
return struct.unpack_from("<hhHH", r, 12) if r else None
|
||||
|
||||
def prop(self, win, atom):
|
||||
if not atom:
|
||||
return None
|
||||
r = self.request(struct.pack("<BBHIIIII", 20, 0, 6, win, atom, 0, 0, 64))
|
||||
if not r or r[1] == 0:
|
||||
return None
|
||||
fmt, n = r[1], struct.unpack_from("<I", r, 16)[0]
|
||||
data = r[32:32 + n * fmt // 8]
|
||||
if fmt == 32:
|
||||
return list(struct.unpack("<%dI" % n, data))
|
||||
return data
|
||||
|
||||
|
||||
def main():
|
||||
display = sys.argv[1] if len(sys.argv) > 1 else os.environ.get("DISPLAY", ":0")
|
||||
try:
|
||||
x = X(display)
|
||||
except OSError as e:
|
||||
print("== %s: %s" % (display, e))
|
||||
return
|
||||
atoms = {name: x.atom(name) for name in (
|
||||
"STEAM_GAME", "WM_STATE", "_NET_WM_PID", "WM_CLASS", "_NET_WM_WINDOW_OPACITY", "STEAM_OVERLAY",
|
||||
"GAMESCOPE_EXTERNAL_OVERLAY", "STEAM_INPUT_FOCUS", "_NET_WM_STATE")}
|
||||
for win in x.children(x.root):
|
||||
attrs = x.attributes(win)
|
||||
if not attrs:
|
||||
continue
|
||||
game = x.prop(win, atoms["STEAM_GAME"])
|
||||
if attrs[0] != 2 and not game:
|
||||
continue
|
||||
geo = x.geometry(win)
|
||||
state = x.prop(win, atoms["WM_STATE"])
|
||||
pid = x.prop(win, atoms["_NET_WM_PID"])
|
||||
cls = x.prop(win, atoms["WM_CLASS"])
|
||||
extra = []
|
||||
if attrs[2] == 2:
|
||||
extra.append("InputOnly")
|
||||
for label, name in (("opacity", "_NET_WM_WINDOW_OPACITY"), ("overlay", "STEAM_OVERLAY"),
|
||||
("external-overlay", "GAMESCOPE_EXTERNAL_OVERLAY"),
|
||||
("input-focus", "STEAM_INPUT_FOCUS")):
|
||||
value = x.prop(win, atoms[name])
|
||||
if value:
|
||||
extra.append("%s 0x%x" % (label, value[0]) if label == "opacity" else "%s %d" % (label, value[0]))
|
||||
net_state = x.prop(win, atoms["_NET_WM_STATE"])
|
||||
if net_state:
|
||||
extra.append("net-state " + ",".join(
|
||||
x.atom_name(a).replace("_NET_WM_STATE_", "") for a in net_state))
|
||||
print("== %s window 0x%x: %s%s %s app %s state %s pid %s class %s" % (
|
||||
display, win, MAP_STATE[attrs[0]] if attrs[0] < 3 else attrs[0],
|
||||
" override-redirect" if attrs[1] else "",
|
||||
"%dx%d+%d+%d" % (geo[2], geo[3], geo[0], geo[1]) if geo else "?",
|
||||
game[0] if game else "-",
|
||||
WM_STATES.get(state[0], state[0]) if state else "-",
|
||||
pid[0] if pid else "-",
|
||||
cls.split(b"\0")[-2].decode(errors="replace") if cls and b"\0" in cls else "-",
|
||||
) + "".join(" " + e for e in extra))
|
||||
|
||||
|
||||
main()
|
||||
Reference in new issue
Block a user