#!/usr/bin/python3
#
# Run given command; switch override_redirect off on one new X11 window.
# Also change the title to $_NET_WM_NAME (or blank), and set a fixed size hint.
# Useful for working around https://github.com/QubesOS/qubes-issues/issues/2311
#
# Example: x11-unoverride-redirect dmenu -i

import os
import struct
import subprocess
import sys
import xcffib
from xcffib import xproto
from xcffib.xproto import Atom, CW, PropMode

XCB_ICCCM_NUM_WM_SIZE_HINTS_ELEMENTS = 18
XCB_ICCCM_SIZE_HINT_P_MIN_SIZE = 1 << 4
XCB_ICCCM_SIZE_HINT_P_MAX_SIZE = 1 << 5

def title_property():
    env = os.getenvb(b'_NET_WM_NAME', b'')
    return len(env), env

def fixed_size_property(width, height):
    hint = [0] * XCB_ICCCM_NUM_WM_SIZE_HINTS_ELEMENTS
    hint[0] = XCB_ICCCM_SIZE_HINT_P_MIN_SIZE | XCB_ICCCM_SIZE_HINT_P_MAX_SIZE
    hint[5], hint[6] = hint[7], hint[8] = width, height
    return XCB_ICCCM_NUM_WM_SIZE_HINTS_ELEMENTS, struct.pack(
        f'=I{XCB_ICCCM_NUM_WM_SIZE_HINTS_ELEMENTS - 2}iI', *hint)

def main():
    connection = xcffib.connect()
    core = connection.core

    for s in ('_NET_WM_NAME', 'UTF8_STRING'):
        setattr(Atom, s, core.InternAtom(0, len(s), s).reply().atom)

    core.ChangeWindowAttributesChecked(
        connection.setup.roots[0].root, CW.EventMask,
        [xproto.EventMask.SubstructureNotify]
    ).check()

    window = None

    with subprocess.Popen(sys.argv[1:]) as command:
        for ev in iter(connection.wait_for_event, None):
            if command.poll() is not None:  # command finished
                break

            if window is None:
                if isinstance(ev, xproto.CreateNotifyEvent) \
                   and ev.override_redirect:
                    window = ev.window

                    cookies = [
                        core.ChangeWindowAttributesChecked(
                            window, CW.OverrideRedirect, [0]),
                    ]
                    connection.flush()

                    cookies += [
                        core.ChangePropertyChecked(
                            PropMode.Replace, window,
                            # pylint: disable=no-member,protected-access
                            Atom._NET_WM_NAME, Atom.UTF8_STRING,
                            8, *title_property()),
                        core.ChangePropertyChecked(
                            PropMode.Replace, window,
                            Atom.WM_NORMAL_HINTS, Atom.WM_SIZE_HINTS,
                            32, *fixed_size_property(ev.width, ev.height)),
                    ]
                    connection.flush()
            elif window == ev.window:
                if isinstance(ev, xproto.MapNotifyEvent):
                    if ev.sequence < cookies[0].sequence:
                        cookies += [
                            core.UnmapWindowChecked(window),
                            core.MapWindowChecked(window),
                        ]
                        connection.flush()
                    break
                if isinstance(ev, xproto.DestroyNotifyEvent):
                    break

        for c in cookies:
            c.check()
        connection.disconnect()

    status = command.returncode
    if status < 0:
        status = 128 - status
    return status


if __name__ == '__main__':
    sys.exit(main())
