Search code examples
dbusawesome-wm

How to call dbus methods in Awesome WM?


I want to send dbus messages to other applications from Awesome WM. However, the documentation for Awesome's dbus interfact is pretty minimalistic, and I can't find any examples. How to do that? For example, I want to use the Inhibit function in org.freedesktop.login1.


Solution

  • Awesome's built-in poor man's DBus wrapper barely contains enough support for DBus to have its built-in notification daemon (naughty) work: https://github.com/awesomeWM/awesome/blob/259c4f716fbd08b4507ebb4cb4d40fe5cbabed0f/dbus.c#L858-L870

    This API allows you to implement an object available via DBus and to emit signals, but not to call DBus methods. Looking at some docs, it seems like the Inhibit interface is a method that you have to call. Even worse - it returns a file descriptor!

    With lots of reading of GIO's documentation, I came up with the following (hopefully self-explanatory; but also please read https://github.com/pavouk/lgi/blob/master/docs/gio.md) example:

    local lgi = require("lgi")
    local Gio = lgi.require("Gio")
    local GLib = lgi.require("GLib")
    
    -- Workaround for https://github.com/pavouk/lgi/issues/142
    local function bus_get_async(type)
        Gio.bus_get(type, nil, coroutine.running())
        local a, b = coroutine.yield()
        return Gio.bus_get_finish(b)
    end
    
    local function inhibit(bus, what, who, why, mode)
        local name = "org.freedesktop.login1"
        local object = "/org/freedesktop/login1"
        local interface = "org.freedesktop.login1.Manager"
        local message = Gio.DBusMessage.new_method_call(name, object, interface, "Inhibit")
        message:set_body(GLib.Variant("(ssss)",
            { what, who, why, mode }))
    
        local timeout = -1 -- Just use the default
        local result, err = bus:async_send_message_with_reply(message, Gio.DBusSendMessageFlags.NONE,
            timeout, nil)
    
        if err then
            print("error: " .. tostring(err))
            return
        end
    
        if result:get_message_type() == "ERROR" then
            local _, err = result:to_gerror()
            print("error: " .. tostring(err))
            return
        end
    
        local fd_list = result:get_unix_fd_list()
        local fd, err = fd_list:get(0)
        if err then
            print("error: " .. tostring(err))
            return
        end
    
        -- Now... somehow turn this fd into something we can close
        return Gio.UnixInputStream.new(fd, true)
    end
    
    Gio.Async.call(function()
        local bus = bus_get_async(Gio.BusType.SYSTEM)
        local a = inhibit(bus, "shutdown:sleep", "hi, it's me!", "Just because", "delay")
        print("got lock")
        io.popen("sleep 10"):read("*a")
        a:async_close()
        -- Speed up deletion of the GDBusMessage that still references the FD
        collectgarbage("collect")
        collectgarbage("collect")
    
        print("released lock")
        io.popen("sleep 10"):read("*a")
    end)()
    

    If you want, you can turn the above into something synchronous by replacing calls to async_foo with calls to foo_sync. That also allows to get rid of the hack to make bus_get_async() work and the Gio.Async.call wrapper around everything.