#!/usr/bin/python3
"""Istalgan dasturdagi maydon matnini yangi alifboga o'giradi (X11 va Wayland).

Shift'ni ikki marta bosishni yangi-alifbo-shift xizmati kuzatadi va shu buyruqni foydalanuvchi
seansida ishga tushiradi. Ctrl+C / Ctrl+A / Ctrl+V yadro darajasidagi virtual klaviatura
(/dev/uinput) orqali yuboriladi — Wayland'da boshqa yo'l yo'q. Ruxsatni .deb paketidagi udev
qoidasi beradi (tizimga kirgan foydalanuvchiga).

    yangi-alifbo-ogir                          belgilangan matnni, bo'lmasa maydondagi hammasini o'giradi
    yangi-alifbo-ogir --eski-tugmani-olib-tashla  avvalgi versiyadagi GNOME Ctrl+Shift+Y ni o'chiradi
"""

import ast
import fcntl
import os
import shutil
import struct
import subprocess
import sys
import time

sys.path.insert(0, "/usr/share/ozbek-yangi-alifbo")
from ogirish import ogir  # noqa: E402

# linux/input-event-codes.h va linux/uinput.h
EV_SYN, EV_KEY, SYN_REPORT = 0, 1, 0
KEY_LEFTCTRL, KEY_A, KEY_C, KEY_V = 29, 30, 46, 47
UI_SET_EVBIT = 0x40045564
UI_SET_KEYBIT = 0x40045565
UI_DEV_SETUP = 0x405C5503
UI_DEV_CREATE = 0x5501
UI_DEV_DESTROY = 0x5502
BUS_VIRTUAL = 0x06

BELGI = "⁣yangi-alifbo⁣"  # buferda Ctrl+C ishlaganini bilish uchun


class VirtualKlaviatura:
    def __enter__(self):
        self._fd = os.open("/dev/uinput", os.O_WRONLY | os.O_NONBLOCK)
        fcntl.ioctl(self._fd, UI_SET_EVBIT, EV_KEY)
        for kod in (KEY_LEFTCTRL, KEY_A, KEY_C, KEY_V):
            fcntl.ioctl(self._fd, UI_SET_KEYBIT, kod)
        sozlama = struct.pack("HHHH80sI", BUS_VIRTUAL, 0x1209, 0x2026, 1, b"Yangi alifbo", 0)
        fcntl.ioctl(self._fd, UI_DEV_SETUP, sozlama)
        fcntl.ioctl(self._fd, UI_DEV_CREATE)
        # Kompozitor yangi qurilmani ko'rib ulgurishi kerak
        time.sleep(0.3)
        return self

    def __exit__(self, *_):
        fcntl.ioctl(self._fd, UI_DEV_DESTROY)
        os.close(self._fd)

    def _hodisa(self, turi: int, kod: int, qiymat: int) -> None:
        os.write(self._fd, struct.pack("llHHi", 0, 0, turi, kod, qiymat))

    def _tugma(self, kod: int, bosildi: bool) -> None:
        self._hodisa(EV_KEY, kod, int(bosildi))
        self._hodisa(EV_SYN, SYN_REPORT, 0)

    def birikma(self, kod: int) -> None:
        """Ctrl + tugma."""
        self._tugma(KEY_LEFTCTRL, True)
        self._tugma(kod, True)
        self._tugma(kod, False)
        self._tugma(KEY_LEFTCTRL, False)
        time.sleep(0.05)


def _wayland() -> bool:
    return bool(os.environ.get("WAYLAND_DISPLAY")) and shutil.which("wl-paste") is not None


def buferdan_ol() -> str | None:
    buyruq = ["wl-paste", "--no-newline"] if _wayland() else ["xclip", "-selection", "clipboard", "-o"]
    natija = subprocess.run(buyruq, capture_output=True, timeout=2)
    return natija.stdout.decode("utf-8", "replace") if natija.returncode == 0 else None


def buferga_yoz(matn: str) -> None:
    buyruq = ["wl-copy"] if _wayland() else ["xclip", "-selection", "clipboard", "-i"]
    # wl-copy va xclip buferni ushlab turish uchun fonda qoladi, shuning uchun chiqishini kutmaymiz
    subprocess.run(buyruq, input=matn.encode("utf-8"), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=2)


def _nusxalandimi(klaviatura: VirtualKlaviatura, oldin_hammasini_belgila: bool) -> bool:
    buferga_yoz(BELGI)
    if oldin_hammasini_belgila:
        klaviatura.birikma(KEY_A)
    klaviatura.birikma(KEY_C)
    muddat = time.monotonic() + 0.6
    while time.monotonic() < muddat:
        if buferdan_ol() not in (None, BELGI):
            return True
        time.sleep(0.05)
    return False


def maydonni_ogir() -> None:
    # Ikkinchi Shift qo'yib yuborilganda chaqiriladi; kompozitor ham buni ko'rib ulgursin
    time.sleep(0.1)
    eski = buferdan_ol()
    with VirtualKlaviatura() as klaviatura:
        try:
            if not (_nusxalandimi(klaviatura, False) or _nusxalandimi(klaviatura, True)):
                return
            matn = buferdan_ol()
            yangi = ogir(matn) if matn else matn
            if not matn or yangi == matn:
                return
            buferga_yoz(yangi)
            time.sleep(0.1)
            klaviatura.birikma(KEY_V)
            # Dastur buferni o'qib ulgurishi kerak, aks holda eski matn joylanadi
            time.sleep(0.5)
        finally:
            buferga_yoz(eski if eski is not None else "")


GNOME_ROYXAT = ("org.gnome.settings-daemon.plugins.media-keys", "custom-keybindings")
GNOME_YOL = "/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/yangi-alifbo/"
GNOME_SXEMA = "org.gnome.settings-daemon.plugins.media-keys.custom-keybinding"


def _gsettings(*args: str) -> str:
    return subprocess.run(["gsettings", *args], capture_output=True, text=True, check=True).stdout.strip()


def eski_tugmani_olib_tashla() -> None:
    """Oldingi versiya GNOME'ga Ctrl+Shift+Y ni qo'shgan edi; endi Shift'ni ikki marta bosish ishlatiladi."""
    if not shutil.which("gsettings"):
        return
    try:
        qiymat = _gsettings("get", *GNOME_ROYXAT)
    except subprocess.CalledProcessError:
        return
    royxat = [] if qiymat.startswith("@") else ast.literal_eval(qiymat)
    if GNOME_YOL not in royxat:
        return
    _gsettings("set", *GNOME_ROYXAT, str([yol for yol in royxat if yol != GNOME_YOL]))
    _gsettings("reset-recursively", f"{GNOME_SXEMA}:{GNOME_YOL}")


if __name__ == "__main__":
    if "--eski-tugmani-olib-tashla" in sys.argv:
        eski_tugmani_olib_tashla()
    else:
        maydonni_ogir()
