Experimental support for focusing the game when launched through Steam. Problem, Steam launches in full screen. It's games launch behind it so they do not take focus. Second problem, even if they do take focus, the game is most likely not a winow Orca can see, so it thinks it's still in the Steam client meaning you can get orca trying to read Steam while playing the game. Solution, watch for Steam child window spawns. Create a small blank window that times out quickly and focus it. Move focus to the Steam child window which will be the game. It has worked for everything thus far.
This commit is contained in:
18
i38.sh
18
i38.sh
@@ -314,6 +314,9 @@ screenlockPinHash="$screenlockPinHash"
|
||||
# Personal mode
|
||||
personalModeEnabled="${personalModeEnabled:-1}"
|
||||
personalModeKey="$personalModeKey"
|
||||
|
||||
# WayTray configuration (0=use I38 config, 1=keep user config)
|
||||
waytrayUseI38Config="${waytrayUseI38Config:-0}"
|
||||
EOF
|
||||
|
||||
dialog --title "I38" --msgbox "Configuration saved to $configFile\n\nYou can edit this file manually or delete it to reconfigure from scratch." 0 0
|
||||
@@ -431,9 +434,16 @@ write_waytray_config() {
|
||||
|
||||
mkdir -p "${waytrayConfigDir}"
|
||||
|
||||
# Ask user if config already exists
|
||||
# Ask user if config already exists (unless preference already saved)
|
||||
if [[ -f "${waytrayConfig}" ]]; then
|
||||
if ! yesno "Existing waytray configuration detected. Replace with I38's minimal tray-only config?\n\n(Select 'No' to keep your existing waytray configuration with all modules enabled)"; then
|
||||
if [[ -z "$waytrayUseI38Config" ]]; then
|
||||
if yesno "Existing waytray configuration detected. Replace with I38's minimal tray-only config?\n\n(Select 'No' to keep your existing waytray configuration with all modules enabled)"; then
|
||||
waytrayUseI38Config=0
|
||||
else
|
||||
waytrayUseI38Config=1
|
||||
fi
|
||||
fi
|
||||
if [[ $waytrayUseI38Config -ne 0 ]]; then
|
||||
return 0 # User wants to keep existing config
|
||||
fi
|
||||
fi
|
||||
@@ -1141,6 +1151,10 @@ $(if [[ $sounds -eq 0 ]]; then
|
||||
echo "exec_always --no-startup-id ${i3Path}/scripts/sound.py"
|
||||
fi
|
||||
fi
|
||||
# Steam game focus handler (i3 only) - focuses games when they open behind Big Picture
|
||||
if [[ $usingSway -ne 0 ]] && command -v steam &> /dev/null; then
|
||||
echo "exec --no-startup-id ${i3Path}/scripts/steam_games.py"
|
||||
fi
|
||||
# i3 watchdog - monitors i3 responsiveness and auto-recovers from freezes
|
||||
if [[ $usingSway -ne 0 ]]; then
|
||||
echo "exec_always --no-startup-id ${i3Path}/scripts/i3_watchdog.sh"
|
||||
|
||||
120
scripts/steam_games.py
Executable file
120
scripts/steam_games.py
Executable file
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# This file is part of I38.
|
||||
|
||||
# I38 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation,
|
||||
# either version 3 of the License, or (at your option) any later version.
|
||||
|
||||
# I38 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
# PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License along with I38. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Steam game focus handler for i3.
|
||||
|
||||
This script listens for new windows and focuses Steam games when they appear.
|
||||
It uses process tree detection to reliably identify games launched by Steam,
|
||||
solving the issue where games open behind Big Picture without focus.
|
||||
"""
|
||||
|
||||
import re
|
||||
import subprocess
|
||||
import time
|
||||
import i3ipc
|
||||
|
||||
|
||||
def get_window_pid(windowId):
|
||||
"""Get PID from X11 window ID using xdotool."""
|
||||
if not windowId:
|
||||
return None
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['xdotool', 'getwindowpid', str(windowId)],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=1
|
||||
)
|
||||
if result.returncode == 0:
|
||||
return int(result.stdout.strip())
|
||||
except (subprocess.TimeoutExpired, ValueError, FileNotFoundError):
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def is_steam_child(pid):
|
||||
"""Check if PID is a descendant of Steam process."""
|
||||
try:
|
||||
while pid > 1:
|
||||
with open(f'/proc/{pid}/comm', 'r') as f:
|
||||
name = f.read().strip()
|
||||
if name in ('steam', 'steamwebhelper', 'steam.sh'):
|
||||
return True
|
||||
with open(f'/proc/{pid}/stat', 'r') as f:
|
||||
stat = f.read()
|
||||
# Format: pid (comm) state ppid ...
|
||||
# comm can contain spaces, so find the last ) and parse from there
|
||||
closeParenIdx = stat.rfind(')')
|
||||
fields = stat[closeParenIdx + 2:].split()
|
||||
pid = int(fields[1]) # PPID is the 2nd field after (comm)
|
||||
except (FileNotFoundError, PermissionError, ValueError, IndexError):
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def is_steam_client(windowClass):
|
||||
"""Check if window is the Steam client itself (not a game)."""
|
||||
if not windowClass:
|
||||
return False
|
||||
# Steam client windows have class "Steam" or "steam"
|
||||
return re.match(r'^steam$', windowClass, re.IGNORECASE) is not None
|
||||
|
||||
|
||||
def on_new_window(i3, event):
|
||||
"""Handle new window events - focus Steam games."""
|
||||
try:
|
||||
container = event.container
|
||||
windowClass = container.window_class
|
||||
windowId = container.window
|
||||
pid = get_window_pid(windowId)
|
||||
|
||||
# Skip if no PID available
|
||||
if not pid:
|
||||
return
|
||||
|
||||
# Skip Steam client windows
|
||||
if is_steam_client(windowClass):
|
||||
return
|
||||
|
||||
# Check if this is a Steam game (child of Steam process)
|
||||
if is_steam_child(pid):
|
||||
# Spawn a tiny yad window to reset Orca's focus state
|
||||
# This prevents Orca from thinking it's still in Steam
|
||||
# yad will auto-close after timeout
|
||||
subprocess.Popen(
|
||||
['yad', '--text=', '--no-buttons', '--undecorated',
|
||||
'--geometry=1x1+0+0', '--timeout=1', '--skip-taskbar'],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL
|
||||
)
|
||||
# Wait for yad window to appear then focus it
|
||||
time.sleep(0.15)
|
||||
i3.command('[class="Yad"] focus')
|
||||
# Let Orca register the yad window
|
||||
time.sleep(0.15)
|
||||
# Now focus the game window
|
||||
container.command('focus')
|
||||
|
||||
except Exception:
|
||||
# Silently ignore errors to prevent blocking i3
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
i3 = i3ipc.Connection()
|
||||
i3.on('window::new', on_new_window)
|
||||
i3.main()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user