foxygit / infodisplay Log in
commits tags

/kiosk/utils/display.py · 5.14 KB

raw
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