#!/usr/bin/env python3
"""An icon-only popup for minimized X11 windows. Gtk3, wmctrl and xprop."""
import configparser
import os
from pathlib import Path
import re
import subprocess
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('Gdk', '3.0')
from gi.repository import Gdk, GLib, Gtk


def command(*args):
    try:
        return subprocess.run(args, capture_output=True, text=True, timeout=3).stdout
    except (OSError, subprocess.TimeoutExpired):
        return ''


def icon_lookup():
    roots = [Path.home() / '.local/share/applications']
    for d in os.environ.get('XDG_DATA_DIRS', '/usr/local/share:/usr/share').split(':'):
        roots.append(Path(d) / 'applications')
    roots.extend([Path('/var/lib/flatpak/exports/share/applications'),
                  Path.home() / '.local/share/flatpak/exports/share/applications',
                  Path('/var/lib/snapd/desktop/applications')])
    lookup = {}
    for root in roots:
        if not root.exists():
            continue
        for file in root.rglob('*.desktop'):
            cfg = configparser.ConfigParser(interpolation=None, strict=False)
            cfg.optionxform = str
            try:
                cfg.read(file, encoding='utf-8')
                ent = cfg['Desktop Entry']
                icon = ent.get('Icon', '')
                if not icon:
                    continue
                keys = [file.stem, file.name, ent.get('StartupWMClass', ''), ent.get('Name', '')]
                for key in keys:
                    if key:
                        lookup.setdefault(key.casefold(), icon)
            except (KeyError, ValueError, OSError, configparser.Error):
                continue
    return lookup


class Minibar:
    def __init__(self):
        self.lookup = icon_lookup()
        self.theme = Gtk.IconTheme.get_default()
        self.window = Gtk.Window(type=Gtk.WindowType.TOPLEVEL)
        self.window.set_title('AppendHub Desktop · Minimized Windows')
        self.window.set_decorated(False)
        self.window.set_resizable(False)
        self.window.set_keep_above(True)
        self.window.set_skip_taskbar_hint(True)
        self.window.set_skip_pager_hint(True)
        self.window.set_type_hint(Gdk.WindowTypeHint.DOCK)
        self.window.set_name('brack-minibar')
        self.window.set_accept_focus(False)
        css = Gtk.CssProvider()
        css.load_from_data(b'''#brack-minibar { background-color: #241039; border-radius: 12px; border: 1px solid #7851A9; } button { background: transparent; border: 0; padding: 7px; } button:hover { background: #7851A9; }''')
        Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(), css, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
        self.box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=3)
        self.window.add(self.box)
        self.window.show_all()
        self.window.hide()
        self.previous = None
        GLib.timeout_add(900, self.refresh)

    def minimized(self):
        result = []
        for line in command('wmctrl', '-lxp').splitlines():
            parts = line.split(None, 4)
            if len(parts) < 5:
                continue
            wid, desktop, pid, wmclass, title = parts
            if title.startswith('AppendHub Desktop') or title == 'AppendHub Desktop · Minimized Windows':
                continue
            properties = command('xprop', '-id', wid, '_NET_WM_STATE', 'WM_STATE')
            if '_NET_WM_STATE_HIDDEN' not in properties and 'Iconic' not in properties:
                continue
            result.append((wid, wmclass, title))
        return result

    def icon(self, wmclass):
        for key in re.split(r'[.,]', wmclass.casefold()):
            key = key.strip()
            name = self.lookup.get(key, key)
            if name.startswith('/') and Path(name).exists():
                try:
                    return Gtk.Image.new_from_file(name)
                except Exception:
                    pass
            if self.theme.has_icon(name):
                return Gtk.Image.new_from_icon_name(name, Gtk.IconSize.DND)
        return Gtk.Image.new_from_icon_name('application-x-executable', Gtk.IconSize.DND)

    def refresh(self):
        items = self.minimized()
        signature = tuple(items)
        if signature == self.previous:
            return True
        self.previous = signature
        for child in self.box.get_children():
            self.box.remove(child)
        for wid, wmclass, title in items:
            button = Gtk.Button()
            button.set_tooltip_text(title)
            button.add(self.icon(wmclass))
            button.connect('clicked', lambda _button, window_id=wid: subprocess.Popen(
                ['wmctrl', '-ia', window_id], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL))
            self.box.pack_start(button, False, False, 0)
        if items:
            self.window.show_all()
            display = Gdk.Display.get_default()
            monitor = display.get_primary_monitor() or display.get_monitor(0)
            area = monitor.get_geometry()
            width = max(64, len(items) * 55 + 15)
            self.window.resize(width, 57)
            self.window.move(area.x + (area.width - width) // 2, area.y + area.height - 77)
        else:
            self.window.hide()
        return True


if __name__ == '__main__':
    Minibar()
    Gtk.main()
