#!/usr/bin/python3
"""Moodtop Bluetooth pairing agent: answers BlueZ's pairing questions so Settings → Bluetooth can pair
phones, headphones, keyboards and mice. Codes a device must show or have typed are sent as notifications."""
import subprocess
from gi.repository import Gio, GLib

PATH = "/org/mood/btagent"
XML = """<node><interface name="org.bluez.Agent1">
<method name="Release"/>
<method name="RequestPinCode"><arg type="o" direction="in"/><arg type="s" direction="out"/></method>
<method name="DisplayPinCode"><arg type="o" direction="in"/><arg type="s" direction="in"/></method>
<method name="RequestPasskey"><arg type="o" direction="in"/><arg type="u" direction="out"/></method>
<method name="DisplayPasskey"><arg type="o" direction="in"/><arg type="u" direction="in"/><arg type="q" direction="in"/></method>
<method name="RequestConfirmation"><arg type="o" direction="in"/><arg type="u" direction="in"/></method>
<method name="RequestAuthorization"><arg type="o" direction="in"/></method>
<method name="AuthorizeService"><arg type="o" direction="in"/><arg type="s" direction="in"/></method>
<method name="Cancel"/>
</interface></node>"""

bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None)


def name(dev):
    try:
        v = bus.call_sync("org.bluez", dev, "org.freedesktop.DBus.Properties", "Get",
                          GLib.Variant("(ss)", ("org.bluez.Device1", "Alias")), None, 0, 2000, None)
        return v.unpack()[0]
    except GLib.Error:
        return "Bluetooth device"


def trust(dev):
    try:
        bus.call_sync("org.bluez", dev, "org.freedesktop.DBus.Properties", "Set",
                      GLib.Variant("(ssv)", ("org.bluez.Device1", "Trusted", GLib.Variant("b", True))), None, 0, 2000, None)
    except GLib.Error:
        pass


def notify(title, body):
    subprocess.Popen(["notify-send", "-a", "Bluetooth", "-i", "bluetooth", title, body],
                     stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)


def call(conn, sender, path, iface, method, params, inv):
    a = params.unpack()
    if method == "RequestPinCode":
        notify(f"Pairing with {name(a[0])}", "If it asks for a PIN, type 0000")
        return inv.return_value(GLib.Variant("(s)", ("0000",)))
    if method == "RequestPasskey":
        return inv.return_value(GLib.Variant("(u)", (0,)))
    if method == "DisplayPinCode":
        notify(f"Pair {name(a[0])}", f"Type {a[1]} on the device, then press Enter")
    elif method == "DisplayPasskey":
        if a[2] == 0:
            notify(f"Pair {name(a[0])}", f"Type {a[1]:06d} on the device, then press Enter")
    elif method == "RequestConfirmation":
        notify(f"Pairing with {name(a[0])}", f"Code {a[1]:06d} (accept it on the device if asked)")
        trust(a[0])
    elif method in ("RequestAuthorization", "AuthorizeService"):
        trust(a[0])
    inv.return_value(None)


def register():
    try:
        bus.call_sync("org.bluez", "/org/bluez", "org.bluez.AgentManager1", "RegisterAgent",
                      GLib.Variant("(os)", (PATH, "KeyboardDisplay")), None, 0, 5000, None)
    except GLib.Error as e:
        if "AlreadyExists" not in e.message:
            return
    try:
        bus.call_sync("org.bluez", "/org/bluez", "org.bluez.AgentManager1", "RequestDefaultAgent",
                      GLib.Variant("(o)", (PATH,)), None, 0, 5000, None)
    except GLib.Error:
        pass


node = Gio.DBusNodeInfo.new_for_xml(XML)
bus.register_object(PATH, node.interfaces[0], call, None, None)
# bluetoothd may start after us or restart (rfkill, resume): re-register whenever it appears
Gio.bus_watch_name_on_connection(bus, "org.bluez", Gio.BusNameWatcherFlags.NONE, lambda *_: register(), None)
GLib.MainLoop().run()
