Merge pull request #2722 from psychon/dbus-gio-naughty
naughty.dbus: Switch to using Gio for DBus bindings
This commit is contained in:
commit
9163eef01c
|
@ -7,17 +7,16 @@
|
||||||
-- @module naughty.dbus
|
-- @module naughty.dbus
|
||||||
---------------------------------------------------------------------------
|
---------------------------------------------------------------------------
|
||||||
|
|
||||||
assert(dbus)
|
|
||||||
|
|
||||||
-- Package environment
|
-- Package environment
|
||||||
local pairs = pairs
|
local pairs = pairs
|
||||||
local type = type
|
local type = type
|
||||||
local string = string
|
local string = string
|
||||||
local capi = { awesome = awesome,
|
local capi = { awesome = awesome }
|
||||||
dbus = dbus }
|
|
||||||
local gtable = require("gears.table")
|
local gtable = require("gears.table")
|
||||||
local gsurface = require("gears.surface")
|
local gsurface = require("gears.surface")
|
||||||
local cairo = require("lgi").cairo
|
local protected_call = require("gears.protected_call")
|
||||||
|
local lgi = require("lgi")
|
||||||
|
local cairo, Gio, GLib, GObject = lgi.cairo, lgi.Gio, lgi.GLib, lgi.GObject
|
||||||
|
|
||||||
local schar = string.char
|
local schar = string.char
|
||||||
local sbyte = string.byte
|
local sbyte = string.byte
|
||||||
|
@ -32,6 +31,9 @@ local naction = require("naughty.action")
|
||||||
--- Notification library, dbus bindings
|
--- Notification library, dbus bindings
|
||||||
local dbus = { config = {} }
|
local dbus = { config = {} }
|
||||||
|
|
||||||
|
-- This is either nil or a Gio.DBusConnection for emitting signals
|
||||||
|
local bus_connection
|
||||||
|
|
||||||
-- DBUS Notification constants
|
-- DBUS Notification constants
|
||||||
-- https://developer.gnome.org/notification-spec/#urgency-levels
|
-- https://developer.gnome.org/notification-spec/#urgency-levels
|
||||||
local urgency = {
|
local urgency = {
|
||||||
|
@ -56,20 +58,21 @@ dbus.config.mapping = {
|
||||||
}
|
}
|
||||||
|
|
||||||
local function sendActionInvoked(notificationId, action)
|
local function sendActionInvoked(notificationId, action)
|
||||||
if capi.dbus then
|
if bus_connection then
|
||||||
capi.dbus.emit_signal("session", "/org/freedesktop/Notifications",
|
bus_connection:emit_signal(nil, "/org/freedesktop/Notifications",
|
||||||
"org.freedesktop.Notifications", "ActionInvoked",
|
"org.freedesktop.Notifications", "ActionInvoked",
|
||||||
"u", notificationId,
|
GLib.Variant("(us)", { notificationId, action }))
|
||||||
"s", action)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function sendNotificationClosed(notificationId, reason)
|
local function sendNotificationClosed(notificationId, reason)
|
||||||
if capi.dbus then
|
if reason <= 0 then
|
||||||
capi.dbus.emit_signal("session", "/org/freedesktop/Notifications",
|
reason = cst.notification_closed_reason.undefined
|
||||||
|
end
|
||||||
|
if bus_connection then
|
||||||
|
bus_connection:emit_signal(nil, "/org/freedesktop/Notifications",
|
||||||
"org.freedesktop.Notifications", "NotificationClosed",
|
"org.freedesktop.Notifications", "NotificationClosed",
|
||||||
"u", notificationId,
|
GLib.Variant("(uu)", { notificationId, reason }))
|
||||||
"u", reason)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -117,178 +120,207 @@ local function convert_icon(w, h, rowstride, channels, data)
|
||||||
return res
|
return res
|
||||||
end
|
end
|
||||||
|
|
||||||
capi.dbus.connect_signal("org.freedesktop.Notifications",
|
local function protected_method_call(_, sender, object_path, interface, method, parameters, invocation)
|
||||||
function (data, appname, replaces_id, icon, title, text, actions, hints, expire)
|
if method == "Notify" then
|
||||||
local args = { }
|
local appname, replaces_id, icon, title, text, actions, hints, expire =
|
||||||
if data.member == "Notify" then
|
unpack(parameters.value)
|
||||||
if text ~= "" then
|
local args = {}
|
||||||
args.message = text
|
if text ~= "" then
|
||||||
if title ~= "" then
|
args.message = text
|
||||||
args.title = title
|
if title ~= "" then
|
||||||
|
args.title = title
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if title ~= "" then
|
||||||
|
args.message = title
|
||||||
|
else
|
||||||
|
-- FIXME: We have to reply *something* to the DBus invocation.
|
||||||
|
-- Right now this leads to a memory leak, I think.
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if appname ~= "" then
|
||||||
|
args.appname = appname
|
||||||
|
end
|
||||||
|
for _, obj in pairs(dbus.config.mapping) do
|
||||||
|
local filter, preset = obj[1], obj[2]
|
||||||
|
if (not filter.urgency or filter.urgency == hints.urgency) and
|
||||||
|
(not filter.category or filter.category == hints.category) and
|
||||||
|
(not filter.appname or filter.appname == appname) then
|
||||||
|
args.preset = gtable.join(args.preset, preset)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local preset = args.preset or cst.config.defaults
|
||||||
|
local notification
|
||||||
|
if actions then
|
||||||
|
args.actions = {}
|
||||||
|
|
||||||
|
for i = 1,#actions,2 do
|
||||||
|
local action_id = actions[i]
|
||||||
|
local action_text = actions[i + 1]
|
||||||
|
|
||||||
|
if action_id == "default" then
|
||||||
|
args.run = function()
|
||||||
|
sendActionInvoked(notification.id, "default")
|
||||||
|
notification:destroy(cst.notification_closed_reason.dismissed_by_user)
|
||||||
|
end
|
||||||
|
elseif action_id ~= nil and action_text ~= nil then
|
||||||
|
local a = naction {
|
||||||
|
name = action_text,
|
||||||
|
position = action_id,
|
||||||
|
}
|
||||||
|
|
||||||
|
a:connect_signal("invoked", function()
|
||||||
|
sendActionInvoked(notification.id, action_id)
|
||||||
|
notification:destroy(cst.notification_closed_reason.dismissed_by_user)
|
||||||
|
end)
|
||||||
|
|
||||||
|
table.insert(args.actions, a)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
args.destroy = function(reason)
|
||||||
|
sendNotificationClosed(notification.id, reason)
|
||||||
|
end
|
||||||
|
local legacy_data = { -- This data used to be generated by AwesomeWM's C code
|
||||||
|
type = "method_call", interface = interface, path = object_path,
|
||||||
|
member = method, sender = sender, bus = "session"
|
||||||
|
}
|
||||||
|
if not preset.callback or (type(preset.callback) == "function" and
|
||||||
|
preset.callback(legacy_data, appname, replaces_id, icon, title, text, actions, hints, expire)) then
|
||||||
|
if icon ~= "" then
|
||||||
|
args.icon = icon
|
||||||
|
elseif hints.icon_data or hints.image_data then
|
||||||
|
-- Icon data is a bit complex and hence needs special care:
|
||||||
|
-- .value breaks with the array of bytes (ay) that we get here.
|
||||||
|
-- So, bypass it and look up the needed value differently
|
||||||
|
local icon_data
|
||||||
|
for k, v in parameters:get_child_value(7 - 1):pairs() do
|
||||||
|
if k == "icon_data" then
|
||||||
|
icon_data = v
|
||||||
|
elseif k == "image_data" and icon_data == nil then
|
||||||
|
icon_data = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- icon_data is an array:
|
||||||
|
-- 1 -> width
|
||||||
|
-- 2 -> height
|
||||||
|
-- 3 -> rowstride
|
||||||
|
-- 4 -> has alpha
|
||||||
|
-- 5 -> bits per sample
|
||||||
|
-- 6 -> channels
|
||||||
|
-- 7 -> data
|
||||||
|
|
||||||
|
-- Get the value as a GVariant and then use LGI's special
|
||||||
|
-- GVariant.data to get that as an LGI byte buffer. That one can
|
||||||
|
-- then by converted to a string via its __tostring metamethod.
|
||||||
|
local data = tostring(icon_data:get_child_value(7 - 1).data)
|
||||||
|
args.icon = convert_icon(icon_data[1], icon_data[2], icon_data[3], icon_data[6], data)
|
||||||
|
end
|
||||||
|
if replaces_id and replaces_id ~= "" and replaces_id ~= 0 then
|
||||||
|
args.replaces_id = replaces_id
|
||||||
|
end
|
||||||
|
if expire and expire > -1 then
|
||||||
|
args.timeout = expire / 1000
|
||||||
|
end
|
||||||
|
args.freedesktop_hints = hints
|
||||||
|
|
||||||
|
-- Try to update existing objects when possible
|
||||||
|
notification = naughty.get_by_id(replaces_id)
|
||||||
|
|
||||||
|
if notification then
|
||||||
|
for k, v in pairs(args) do
|
||||||
|
if k == "destroy" then k = "destroy_cb" end
|
||||||
|
notification[k] = v
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
if title ~= "" then
|
notification = nnotif(args)
|
||||||
args.message = title
|
|
||||||
else
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if appname ~= "" then
|
|
||||||
args.appname = appname
|
|
||||||
end
|
|
||||||
for _, obj in pairs(dbus.config.mapping) do
|
|
||||||
local filter, preset = obj[1], obj[2]
|
|
||||||
if (not filter.urgency or filter.urgency == hints.urgency) and
|
|
||||||
(not filter.category or filter.category == hints.category) and
|
|
||||||
(not filter.appname or filter.appname == appname) then
|
|
||||||
args.preset = gtable.join(args.preset, preset)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local preset = args.preset or cst.config.defaults
|
|
||||||
local notification
|
|
||||||
if actions then
|
|
||||||
args.actions = {}
|
|
||||||
|
|
||||||
for i = 1,#actions,2 do
|
|
||||||
local action_id = actions[i]
|
|
||||||
local action_text = actions[i + 1]
|
|
||||||
|
|
||||||
if action_id == "default" then
|
|
||||||
args.run = function()
|
|
||||||
sendActionInvoked(notification.id, "default")
|
|
||||||
notification:destroy(cst.notification_closed_reason.dismissed_by_user)
|
|
||||||
end
|
|
||||||
elseif action_id ~= nil and action_text ~= nil then
|
|
||||||
local a = naction {
|
|
||||||
name = action_text,
|
|
||||||
position = action_id,
|
|
||||||
}
|
|
||||||
|
|
||||||
a:connect_signal("invoked", function()
|
|
||||||
sendActionInvoked(notification.id, action_id)
|
|
||||||
notification:destroy(cst.notification_closed_reason.dismissed_by_user)
|
|
||||||
end)
|
|
||||||
|
|
||||||
table.insert(args.actions, a)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
args.destroy = function(reason)
|
|
||||||
sendNotificationClosed(notification.id, reason)
|
|
||||||
end
|
|
||||||
if not preset.callback or (type(preset.callback) == "function" and
|
|
||||||
preset.callback(data, appname, replaces_id, icon, title, text, actions, hints, expire)) then
|
|
||||||
if icon ~= "" then
|
|
||||||
args.icon = icon
|
|
||||||
elseif hints.icon_data or hints.image_data then
|
|
||||||
if hints.icon_data == nil then hints.icon_data = hints.image_data end
|
|
||||||
|
|
||||||
-- icon_data is an array:
|
|
||||||
-- 1 -> width
|
|
||||||
-- 2 -> height
|
|
||||||
-- 3 -> rowstride
|
|
||||||
-- 4 -> has alpha
|
|
||||||
-- 5 -> bits per sample
|
|
||||||
-- 6 -> channels
|
|
||||||
-- 7 -> data
|
|
||||||
local w, h, rowstride, _, _, channels, icon_data = unpack(hints.icon_data)
|
|
||||||
args.icon = convert_icon(w, h, rowstride, channels, icon_data)
|
|
||||||
end
|
|
||||||
if replaces_id and replaces_id ~= "" and replaces_id ~= 0 then
|
|
||||||
args.replaces_id = replaces_id
|
|
||||||
end
|
|
||||||
if expire and expire > -1 then
|
|
||||||
args.timeout = expire / 1000
|
|
||||||
end
|
|
||||||
args.freedesktop_hints = hints
|
|
||||||
|
|
||||||
-- Try to update existing objects when possible
|
|
||||||
notification = naughty.get_by_id(replaces_id)
|
|
||||||
|
|
||||||
if notification then
|
|
||||||
for k, v in pairs(args) do
|
|
||||||
if k == "destroy" then k = "destroy_cb" end
|
|
||||||
notification[k] = v
|
|
||||||
end
|
|
||||||
else
|
|
||||||
notification = nnotif(args)
|
|
||||||
end
|
|
||||||
|
|
||||||
return "u", notification.id
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return "u", nnotif._gen_next_id()
|
invocation:return_value(GLib.Variant("(u)", { notification.id }))
|
||||||
elseif data.member == "CloseNotification" then
|
return
|
||||||
local obj = naughty.get_by_id(appname)
|
|
||||||
if obj then
|
|
||||||
obj:destroy(cst.notification_closed_reason.dismissed_by_command)
|
|
||||||
end
|
|
||||||
elseif data.member == "GetServerInfo" or data.member == "GetServerInformation" then
|
|
||||||
-- name of notification app, name of vender, version, specification version
|
|
||||||
return "s", "naughty", "s", "awesome", "s", capi.awesome.version, "s", "1.0"
|
|
||||||
elseif data.member == "GetCapabilities" then
|
|
||||||
-- We actually do display the body of the message, we support <b>, <i>
|
|
||||||
-- and <u> in the body and we handle static (non-animated) icons.
|
|
||||||
return "as", { "s", "body", "s", "body-markup", "s", "icon-static", "s", "actions" }
|
|
||||||
end
|
end
|
||||||
end)
|
|
||||||
|
|
||||||
capi.dbus.connect_signal("org.freedesktop.DBus.Introspectable", function (data)
|
invocation:return_value(GLib.Variant("(u)", { nnotif._gen_next_id() }))
|
||||||
if data.member == "Introspect" then
|
elseif method == "CloseNotification" then
|
||||||
local xml = [=[<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object
|
local obj = naughty.get_by_id(parameters.value[1])
|
||||||
Introspection 1.0//EN"
|
if obj then
|
||||||
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
obj:destroy(cst.notification_closed_reason.dismissed_by_command)
|
||||||
<node>
|
end
|
||||||
<interface name="org.freedesktop.DBus.Introspectable">
|
invocation:return_value(GLib.Variant("()"))
|
||||||
<method name="Introspect">
|
elseif method == "GetServerInfo" or method == "GetServerInformation" then
|
||||||
<arg name="data" direction="out" type="s"/>
|
-- name of notification app, name of vender, version, specification version
|
||||||
</method>
|
invocation:return_value(GLib.Variant("(ssss)", {
|
||||||
</interface>
|
"naughty", "awesome", capi.awesome.version, "1.0"
|
||||||
<interface name="org.freedesktop.Notifications">
|
}))
|
||||||
<method name="GetCapabilities">
|
elseif method == "GetCapabilities" then
|
||||||
<arg name="caps" type="as" direction="out"/>
|
-- We actually do display the body of the message, we support <b>, <i>
|
||||||
</method>
|
-- and <u> in the body and we handle static (non-animated) icons.
|
||||||
<method name="CloseNotification">
|
invocation:return_value(GLib.Variant("(as)", {
|
||||||
<arg name="id" type="u" direction="in"/>
|
{ "body", "body-markup", "icon-static", "actions" }
|
||||||
</method>
|
}))
|
||||||
<method name="Notify">
|
|
||||||
<arg name="app_name" type="s" direction="in"/>
|
|
||||||
<arg name="id" type="u" direction="in"/>
|
|
||||||
<arg name="icon" type="s" direction="in"/>
|
|
||||||
<arg name="summary" type="s" direction="in"/>
|
|
||||||
<arg name="body" type="s" direction="in"/>
|
|
||||||
<arg name="actions" type="as" direction="in"/>
|
|
||||||
<arg name="hints" type="a{sv}" direction="in"/>
|
|
||||||
<arg name="timeout" type="i" direction="in"/>
|
|
||||||
<arg name="return_id" type="u" direction="out"/>
|
|
||||||
</method>
|
|
||||||
<method name="GetServerInformation">
|
|
||||||
<arg name="return_name" type="s" direction="out"/>
|
|
||||||
<arg name="return_vendor" type="s" direction="out"/>
|
|
||||||
<arg name="return_version" type="s" direction="out"/>
|
|
||||||
<arg name="return_spec_version" type="s" direction="out"/>
|
|
||||||
</method>
|
|
||||||
<method name="GetServerInfo">
|
|
||||||
<arg name="return_name" type="s" direction="out"/>
|
|
||||||
<arg name="return_vendor" type="s" direction="out"/>
|
|
||||||
<arg name="return_version" type="s" direction="out"/>
|
|
||||||
</method>
|
|
||||||
<signal name="NotificationClosed">
|
|
||||||
<arg name="id" type="u" direction="out"/>
|
|
||||||
<arg name="reason" type="u" direction="out"/>
|
|
||||||
</signal>
|
|
||||||
<signal name="ActionInvoked">
|
|
||||||
<arg name="id" type="u" direction="out"/>
|
|
||||||
<arg name="action_key" type="s" direction="out"/>
|
|
||||||
</signal>
|
|
||||||
</interface>
|
|
||||||
</node>]=]
|
|
||||||
return "s", xml
|
|
||||||
end
|
end
|
||||||
end)
|
end
|
||||||
|
|
||||||
-- listen for dbus notification requests
|
local function method_call(...)
|
||||||
capi.dbus.request_name("session", "org.freedesktop.Notifications")
|
protected_call(protected_method_call, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function on_bus_acquire(conn, _)
|
||||||
|
local function arg(name, signature)
|
||||||
|
return Gio.DBusArgInfo{ name = name, signature = signature }
|
||||||
|
end
|
||||||
|
local method = Gio.DBusMethodInfo
|
||||||
|
local signal = Gio.DBusSignalInfo
|
||||||
|
|
||||||
|
local interface_info = Gio.DBusInterfaceInfo {
|
||||||
|
name = "org.freedesktop.Notifications",
|
||||||
|
methods = {
|
||||||
|
method{ name = "GetCapabilities",
|
||||||
|
out_args = { arg("caps", "as") }
|
||||||
|
},
|
||||||
|
method{ name = "CloseNotification",
|
||||||
|
in_args = { arg("id", "u") }
|
||||||
|
},
|
||||||
|
method{ name = "GetServerInformation",
|
||||||
|
out_args = { arg("return_name", "s"), arg("return_vendor", "s"),
|
||||||
|
arg("return_version", "s"), arg("return_spec_version", "s")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
method{ name = "Notify",
|
||||||
|
in_args = { arg("app_name", "s"), arg("id", "u"),
|
||||||
|
arg("icon", "s"), arg("summary", "s"), arg("body", "s"),
|
||||||
|
arg("actions", "as"), arg("hints", "a{sv}"),
|
||||||
|
arg("timeout", "i")
|
||||||
|
},
|
||||||
|
out_args = { arg("return_id", "u") }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
signals = {
|
||||||
|
signal{ name = "NotificationClosed",
|
||||||
|
args = { arg("id", "u"), arg("reason", "u") }
|
||||||
|
},
|
||||||
|
signal{ name = "ActionInvoked",
|
||||||
|
args = { arg("id", "u"), arg("action_key", "s") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
conn:register_object("/org/freedesktop/Notifications", interface_info,
|
||||||
|
GObject.Closure(method_call))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function on_name_acquired(conn, _)
|
||||||
|
bus_connection = conn
|
||||||
|
end
|
||||||
|
|
||||||
|
local function on_name_lost(_, _)
|
||||||
|
bus_connection = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
Gio.bus_own_name(Gio.BusType.SESSION, "org.freedesktop.Notifications",
|
||||||
|
Gio.BusNameOwnerFlags.NONE, GObject.Closure(on_bus_acquire),
|
||||||
|
GObject.Closure(on_name_acquired), GObject.Closure(on_name_lost))
|
||||||
|
|
||||||
return dbus
|
return dbus
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue