--------------------------------------------------------------------------- -- DBUS/Notification support -- Notify -- -- @author koniu <gkusnierz@gmail.com> -- @copyright 2008 koniu -- @release @AWESOME_VERSION@ -- @module naughty.dbus --------------------------------------------------------------------------- assert(dbus) -- Package environment local pairs = pairs local type = type local string = string local capi = { awesome = awesome, dbus = dbus } local util = require("awful.util") local cairo = require("lgi").cairo local schar = string.char local sbyte = string.byte local tcat = table.concat local tins = table.insert local unpack = unpack or table.unpack -- v5.1: unpack, v5.2: table.unpack local naughty = require("naughty.core") --- Notification library, dbus bindings local dbus = { config = {} } -- DBUS Notification constants local urgency = { low = "\0", normal = "\1", critical = "\2" } --- DBUS notification to preset mapping -- The first element is an object containing the filter -- If the rules in the filter matches the associated preset will be applied -- The rules object can contain: urgency, category, appname -- The second element is the preset dbus.config.mapping = { {{urgency = urgency.low}, naughty.config.presets.low}, {{urgency = urgency.normal}, naughty.config.presets.normal}, {{urgency = urgency.critical}, naughty.config.presets.critical} } local function sendActionInvoked(notificationId, action) if capi.dbus then capi.dbus.emit_signal("session", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", "ActionInvoked", "i", notificationId, "s", action) end end local function sendNotificationClosed(notificationId, reason) if capi.dbus then capi.dbus.emit_signal("session", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", "NotificationClosed", "i", notificationId, "i", reason) end end local function convert_icon(w, h, rowstride, channels, data) -- Do the arguments look sane? (e.g. we have enough data) local expected_length = rowstride * (h - 1) + w * channels if w < 0 or h < 0 or rowstride < 0 or (channels ~= 3 and channels ~= 4) or string.len(data) < expected_length then w = 0 h = 0 end local format = cairo.Format[channels == 4 and 'ARGB32' or 'RGB24'] -- Figure out some stride magic (cairo dictates rowstride) local stride = cairo.Format.stride_for_width(format, w) local append = schar(0):rep(stride - 4 * w) local offset = 0 -- Now convert each row on its own local rows = {} for y = 1, h do local this_row = {} for i = 1 + offset, w * channels + offset, channels do local R, G, B, A = sbyte(data, i, i + channels - 1) tins(this_row, schar(B, G, R, A or 255)) end -- Handle rowstride, offset is stride for the input, append for output tins(this_row, append) tins(rows, tcat(this_row)) offset = offset + rowstride end return cairo.ImageSurface.create_for_data(tcat(rows), format, w, h, stride) end capi.dbus.connect_signal("org.freedesktop.Notifications", function (data, appname, replaces_id, icon, title, text, actions, hints, expire) local args = { } if data.member == "Notify" then if text ~= "" then args.text = text if title ~= "" then args.title = title end else if title ~= "" then args.text = title else return end end if appname ~= "" then args.appname = appname end for i, obj in pairs(dbus.config.mapping) do local filter, preset, s = obj[1], obj[2], 0 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 = util.table.join(args.preset, preset) end end local preset = args.preset or naughty.config.defaults local notification if actions then args.actions = {} local actionid -- create actions callbacks for i , v in ipairs(actions) do if i % 2 == 1 then actionid = v elseif actionid == "default" then args.run = function() sendActionInvoked(notification.id, "default") naughty.destroy(notification, naughty.notificationClosedReason.dismissedByUser) end actionid = nil elseif actionid ~= nil then local action = actionid args.actions[actionid] = function() sendActionInvoked(notification.id, action) naughty.destroy(notification, naughty.notificationClosedReason.dismissedByUser) end actionid = nil 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, data = unpack(hints.icon_data) args.icon = convert_icon(w, h, rowstride, channels, 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 notification = naughty.notify(args) return "u", notification.id end return "u", "0" elseif data.member == "CloseNotification" then local obj = naughty.getById(appname) if obj then naughty.destroy(obj, naughty.notificationClosedReason.dismissedByCommand) end elseif data.member == "GetServerInfo" or data.member == "GetServerInformation" then -- name of notification app, name of vender, version return "s", "naughty", "s", "awesome", "s", capi.awesome.version:match("%d.%d"), "s", "1.0" elseif data.member == "GetCapabilities" then -- We actually do display the body of the message, we support , -- and in the body and we handle static (non-animated) icons. return "as", { "s", "body", "s", "body-markup", "s", "icon-static", "s", "actions" } end end) capi.dbus.connect_signal("org.freedesktop.DBus.Introspectable", function (data, text) if data.member == "Introspect" then local xml = [=[ ]=] return "s", xml end end) -- listen for dbus notification requests capi.dbus.request_name("session", "org.freedesktop.Notifications") return dbus -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80