commits
tags
import os
import re
import shutil
import subprocess
from pathlib import Path
import db
def sway_env():
"""Returnerar env-dict med SWAYSOCK satt, eller None om sway inte körs."""
uid = os.getuid()
xdg_runtime = os.environ.get('XDG_RUNTIME_DIR', f'/run/user/{uid}')
sockets = sorted(Path(xdg_runtime).glob(f'sway-ipc.{uid}.*.sock'))
if not sockets:
return None
env = os.environ.copy()
env['SWAYSOCK'] = str(sockets[0])
return env
def audio_device_id():
"""Returnerar PipeWire interface-id för första Audio/Device."""
import json as _json
try:
out = subprocess.check_output(
['pw-dump'], timeout=5,
env={**os.environ, 'XDG_RUNTIME_DIR': f'/run/user/{os.getuid()}'}
).decode()
for node in _json.loads(out):
if node.get('type') != 'PipeWire:Interface:Device':
continue
if node.get('info', {}).get('props', {}).get('media.class') == 'Audio/Device':
return node.get('id')
except Exception:
pass
return None
def parse_profiles(text):
"""Tolkar pw-cli enum-params EnumProfile-utdata till en lista med dicts."""
profiles, cur, last_key = [], {}, None
for line in text.splitlines():
s = line.strip()
if s.startswith('Object:') and 'Profile' in s:
if 'index' in cur:
profiles.append(cur)
cur, last_key = {}, None
elif 'Profile:index' in s: last_key = 'index'
elif 'Profile:description' in s: last_key = 'description'
elif 'Profile:name' in s and 'description' not in s: last_key = 'name'
elif 'Profile:available' in s: last_key = 'available'
elif last_key == 'index' and s.startswith('Int '):
cur['index'] = int(s.split()[1]); last_key = None
elif last_key in ('name', 'description') and s.startswith('String '):
m = re.match(r'String "(.+)"', s)
if m: cur[last_key] = m.group(1)
last_key = None
elif last_key == 'available' and 'Availability' in s:
cur['available'] = 'yes' in s; last_key = None
if 'index' in cur:
profiles.append(cur)
skip = {'off', 'pro-audio'}
return [p for p in profiles
if p.get('name') not in skip
and not p.get('name', '').startswith('input:')]
def apply_saved_audio():
"""Tillämpar sparad ljudprofil från DB. Best-effort, icke-fatal."""
try:
state = db.get_state()
idx = int(state.get('audio_profile_index', 1))
dev_id = audio_device_id()
pw_env = {**os.environ, 'XDG_RUNTIME_DIR': f'/run/user/{os.getuid()}'}
if dev_id is not None:
subprocess.run(
['pw-cli', 's', str(dev_id), 'Profile', f'{{ index: {idx} }}'],
timeout=5, env=pw_env
)
except Exception:
pass
def apply_display_mode(mode_str, target_output=None):
"""Tillämpar skärmupplösning best-effort. Provar wlr-randr, swaymsg, xrandr."""
m = re.search(r"(\d+)x(\d+)", mode_str)
if not m:
return False
width, height = m.group(1), m.group(2)
out_name = None
if target_output:
out_name = target_output
else:
try:
for conn in sorted(Path('/sys/class/drm').iterdir()):
status_file = conn / 'status'
if not status_file.exists():
continue
if status_file.read_text().strip() == 'connected':
parts = conn.name.split('-', 1)
if len(parts) == 2:
out_name = parts[1]
break
except Exception:
out_name = None
env = os.environ.copy()
if not env.get('WAYLAND_DISPLAY'):
xdg_runtime = env.get('XDG_RUNTIME_DIR', f'/run/user/{os.getuid()}')
try:
for sock in sorted(Path(xdg_runtime).glob('wayland-[0-9]*')):
if sock.suffix != '.lock':
env['WAYLAND_DISPLAY'] = sock.name
break
except Exception:
pass
if shutil.which('wlr-randr') and out_name:
try:
token = mode_str.split('@', 1)[0]
subprocess.run(['wlr-randr', '--output', out_name, '--mode', token], timeout=5, check=True, env=env)
return True
except Exception:
pass
if shutil.which('swaymsg') and out_name:
try:
subprocess.run(['swaymsg', 'output', out_name, 'mode', width, height], timeout=5, check=True, env=env)
return True
except Exception:
pass
if os.environ.get('XDG_SESSION_TYPE', '').lower() == 'wayland' or os.environ.get('WAYLAND_DISPLAY'):
return False
if shutil.which('xrandr') and out_name:
for disp in (None, ':0', ':0.0', ':1'):
try:
env2 = os.environ.copy()
if disp:
env2['DISPLAY'] = disp
subprocess.run(['xrandr', '--output', out_name, '--mode', f'{width}x{height}'], env=env2, timeout=5, check=True)
return True
except Exception:
continue
return False