local gdebug = require "gears.debug" local naughty = require "naughty" local ndbus = require "naughty.dbus" local qdelegate = require "quarrel.delegate" local wicked = require "ui.wicked" ---@diagnostic disable-next-line:redundant-parameter ndbus._notif_methods.Notify = qdelegate(function(env, sender, object_path, interface, method, parameters, invocation) local appname, replaces_id, app_icon, title, text, actions, hints, expire = env.unpack(parameters.value) local args = {} if text ~= "" then args.message = text 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 --TODO v6 Remove this. args.app_name = appname end local preset = args.preset or env.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() env.sendActionInvoked(notification.id, "default") notification:destroy(env.cst.notification_closed_reason.dismissed_by_user) end elseif action_id ~= nil and action_text ~= nil then local a = env.naction { name = action_text, id = action_id, position = (i - 1) / 2 + 1, } -- Right now `gears` doesn't have a great icon implementation -- and `naughty` doesn't depend on `menubar`, so delegate the -- icon "somewhere" using a request. if hints["action-icons"] and action_id ~= "" then naughty.emit_signal("request::action_icon", a, "dbus", { id = action_id }) end a:connect_signal("invoked", function() env.sendActionInvoked(notification.id, action_id) if not notification.resident then notification:destroy(env.cst.notification_closed_reason.dismissed_by_user) end end) table.insert(args.actions, a) end end end args.destroy = function(reason) env.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, app_icon, title, text, actions, hints, expire) ) then if app_icon ~= "" then args.app_icon = app_icon end if hints.icon_data or hints.image_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_condidates = {} for k, v in parameters:get_child_value(7 - 1):pairs() do if k == "image-data" then icon_condidates[1] = v -- not deprecated break elseif k == "image_data" then -- deprecated icon_condidates[2] = v elseif k == "icon_data" then -- deprecated icon_condidates[3] = v end end -- The order is mandated by the spec. local icon_data = icon_condidates[1] or icon_condidates[2] or icon_condidates[3] -- 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.image = env.convert_icon(icon_data[1], icon_data[2], icon_data[3], icon_data[6], data) -- Convert all animation frames. if naughty.image_animations_enabled then args.images = { args.image } if #icon_data > 7 then for frame = 8, #icon_data do data = tostring(icon_data:get_child_value(frame - 1).data) table.insert( args.images, env.convert_icon(icon_data[1], icon_data[2], icon_data[3], icon_data[6], data) ) end end end end -- Alternate ways to set the icon. The specs recommends to allow both -- the icon and image to co-exist since they serve different purpose. -- However in case the icon isn't specified, use the image. args.image = args.image or hints["image-path"] -- not deprecated or hints["image_path"] -- deprecated if naughty.image_animations_enabled then args.images = args.images or {} 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 -- Not very pretty, but given the current format is documented in the -- public API... well, whatever... if hints and hints.urgency then for name, key in pairs(env.urgency) do local b = string.char(hints.urgency) if key == b then args.urgency = name end end end args.urgency = args.urgency or "normal" -- Try to update existing objects when possible notification = naughty.get_by_id(replaces_id) if notification then if not notification._private._unique_sender then -- If this happens, the notification is either trying to -- highjack content created within AwesomeWM or it is garbage -- to begin with. gdebug.print_warning( "A notification has been received, but tried to update " .. "the content of a notification it does not own." ) elseif notification._private._unique_sender ~= sender then -- Nothing says you cannot and some scripts may do it -- accidentally, but this is rather unexpected. gdebug.print_warning( "Notification " .. notification.title .. " is being updated" .. "by a different DBus connection (" .. sender .. "), this is " .. "suspicious. The original connection was " .. notification._private._unique_sender ) end for k, v in pairs(args) do if k == "destroy" then k = "destroy_cb" end notification[k] = v end -- Update the icon if necessary. if app_icon ~= notification._private.app_icon then notification._private.app_icon = app_icon naughty._emit_signal_if("request::icon", function() if notification._private.icon then return true end end, notification, "dbus_clear", {}) end -- Even if no property changed, restart the timeout. notification:reset_timeout() else -- Only set the sender for new notifications. args._unique_sender = sender args._foreign = true notification = env.nnotif(args) notification:connect_signal("destroyed", function(_, r) args.destroy(r) end) end invocation:return_value(env.GLib.Variant("(u)", { notification.id })) return end invocation:return_value(env.GLib.Variant("(u)", { env.nnotif._gen_next_id() })) end, ndbus._notif_methods.Notify) naughty.connect_signal("request::display", wicked) naughty.connect_signal("request::display_error", function(message, startup) naughty.notification { urgency = "critical", title = "Oops, an error happened" .. (startup and " during startup!" or "!"), message = message, } gdebug.print_error("[Error" .. (startup and "during startup" or "") .. "]: " .. message) end)