--------------------------------------------------------------------------- --- Notification manipulation class. -- -- This class creates individual notification objects that can be manipulated -- to extend the default behavior. -- -- This class doesn't define the actual widget, but is rather intended as a data -- object to hold the properties. All examples assume the default widgets, but -- the whole implementation can be replaced. -- --@DOC_naughty_actions_EXAMPLE@ -- -- @author Emmanuel Lepage Vallee -- @copyright 2008 koniu -- @copyright 2017 Emmanuel Lepage Vallee -- @coreclassmod naughty.notification --------------------------------------------------------------------------- local capi = { screen = screen } local gobject = require("gears.object") local gtable = require("gears.table") local timer = require("gears.timer") local gfs = require("gears.filesystem") local cst = require("naughty.constants") local naughty = require("naughty.core") local gdebug = require("gears.debug") local pcommon = require("awful.permissions._common") local notification = {} --luacheck: no max line length --- Notifications font. -- @beautiful beautiful.notification_font -- @tparam string|lgi.Pango.FontDescription notification_font --- Notifications background color. -- @beautiful beautiful.notification_bg -- @tparam color notification_bg --- Notifications foreground color. -- @beautiful beautiful.notification_fg -- @tparam color notification_fg --- Notifications border width. -- @beautiful beautiful.notification_border_width -- @tparam int notification_border_width --- Notifications border color. -- @beautiful beautiful.notification_border_color -- @tparam color notification_border_color --- Notifications shape. -- @beautiful beautiful.notification_shape -- @tparam[opt] gears.shape notification_shape -- @see gears.shape --- Notifications opacity. -- @beautiful beautiful.notification_opacity -- @tparam[opt] int notification_opacity --- The margins inside of the notification widget (or popup). -- @beautiful beautiful.notification_margin -- @tparam int notification_margin --- Notifications width. -- @beautiful beautiful.notification_width -- @tparam int notification_width --- Notifications height. -- @beautiful beautiful.notification_height -- @tparam int notification_height --- The spacing between the notifications. -- @beautiful beautiful.notification_spacing -- @param[opt=2] number -- @see gears.surface -- Unique identifier of the notification. -- This is the equivalent to a PID as allows external applications to select -- notifications. -- @property id -- @tparam number id -- @propertydefault This is autogenerated. -- @readonly -- @propemits true false --- Text of the notification. -- -- This exists only for the pre-AwesomeWM v4.4 new notification implementation. -- Please always use `title`. -- -- @deprecatedproperty text -- @param string -- @see title --- Title of the notification. --@DOC_naughty_helloworld_EXAMPLE@ -- @property title -- @tparam[opt=""] string title -- @propemits true false --- Time in seconds after which popup expires. -- Set 0 for no timeout. -- @property timeout -- @tparam number timeout -- @propertydefault If unspecified, it can come from either the notification -- itself, the presets or the `ruled.notification` rules. -- @negativeallowed false -- @propertyunit second -- @propemits true false --- The notification urgency level. -- -- @property urgency -- @tparam[opt="normal"] string urgency -- @propertyvalue "low" -- @propertyvalue "normal" -- @propertyvalue "critical" -- @propemits true false --- The notification category. -- -- The category should be named using the `x-vendor.class.name` naming scheme or -- use one of the default categories: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
NameDescription
deviceA generic device-related notification that -- doesn't fit into any other category.
device.addedA device, such as a USB device, was added to the system.
device.errorA device had some kind of error.
device.removedA device, such as a USB device, was removed from the system.
emailA generic e-mail-related notification that doesn't fit into -- any other category.
email.arrivedA new e-mail notification.
email.bouncedA notification stating that an e-mail has bounced.
imA generic instant message-related notification that doesn't fit into -- any other category.
im.errorAn instant message error notification.
im.receivedA received instant message notification.
networkA generic network notification that doesn't fit into any other -- category.
network.connectedA network connection notification, such as successful -- sign-on to a network service.
-- This should not be confused with device.added for new network devices.
network.disconnectedA network disconnected notification. This should not -- be confused with
-- device.removed for disconnected network devices.
network.errorA network-related or connection-related error.
presenceA generic presence change notification that doesn't fit into any -- other category,
-- such as going away or idle.
presence.offlineAn offline presence change notification.
presence.onlineAn online presence change notification.
transferA generic file transfer or download notification that doesn't -- fit into any other category.
transfer.completeA file transfer or download complete notification.
transfer.errorA file transfer or download error.
-- -- @property category -- @tparam[opt=nil] string|nil category -- @propemits true false --- True if the notification should be kept when an action is pressed. -- -- By default, invoking an action will destroy the notification. Some actions, -- like the "Snooze" action of alarm clock, will cause the notification to -- be updated with a date further in the future. -- -- @property resident -- @tparam[opt=false] boolean resident -- @propemits true false --- Delay in seconds after which hovered popup disappears. -- @property hover_timeout -- @tparam[opt=nil] number|nil hover_timeout -- @negativeallowed false -- @propertyunit second -- @propemits true false --- Target screen for the notification. -- @property screen -- @tparam[opt=awful.screen.preferred()] screen screen -- @propemits true false --- Corner of the workarea displaying the popups. -- --@DOC_awful_notification_box_corner_EXAMPLE@ -- -- @property position -- @tparam[opt=beautiful.notification_position] string|nil position -- @propertyvalue "top_right" -- @propertyvalue "top_left" -- @propertyvalue "bottom_left" -- @propertyvalue "bottom_right" -- @propertyvalue "top_middle" -- @propertyvalue "bottom_middle" -- @propertyvalue "middle" -- @propemits true false -- @see awful.placement.next_to --- Boolean forcing popups to display on top. -- @property ontop -- @tparam[opt=false] boolean ontop --- Popup height. -- --@DOC_awful_notification_geometry_EXAMPLE@ -- -- @property height -- @tparam number|nil height -- @propemits true false -- @propertyunit pixel -- @negativeallowed false -- @propbeautiful -- @see width --- Popup width. -- @property width -- @tparam number|nil width -- @propemits true false -- @propertyunit pixel -- @negativeallowed false -- @propbeautiful -- @see height --- Notification font. --@DOC_naughty_colors_EXAMPLE@ -- @property font -- @tparam[opt=beautiful.notification_font] font|nil font -- @propemits true false -- @see wibox.widget.textbox.font --- "All in one" way to access the default image or icon. -- -- A notification can provide a combination of an icon, a static image, or if -- enabled, a looping animation. Add to that the ability to import the icon -- information from the client or from a `.desktop` file, there is multiple -- conflicting sources of "icons". -- -- On the other hand, the vast majority of notifications don't multiple or -- ambiguous sources of icons. This property will pick the first of the -- following. -- -- * The `image`. -- * The `app_icon`. -- * The `icon` from a client with `normal` type. -- * The `icon` of a client with `dialog` type. -- -- @property icon -- @tparam[opt=nil] image|nil icon -- @propemits true false -- @see app_icon -- @see image --- Desired icon size in px. -- @property icon_size -- @tparam[opt=beautiful.notification_icon_size] integer|nil icon_size -- @negativeallowed false -- @propertyunit pixel -- @propemits true false --- The icon provided in the `app_icon` field of the DBus notification. -- -- This should always be either the URI (path) to an icon or a valid XDG -- icon name to be fetched from the theme. -- -- @property app_icon -- @tparam[opt=nil] string|nil app_icon -- @propemits true false --- The notification image. -- -- This is usually provided as a `gears.surface` object. The image is used -- instead of the `app_icon` by notification assets which are auto-generated -- or stored elsewhere than the filesystem (databases, web, Android phones, etc). -- -- @property image -- @tparam[opt=nil] image|nil image -- @propemits true false --- The notification (animated) images. -- -- Note that calling this without first setting -- `naughty.image_animations_enabled` to true will throw an exception. -- -- Also note that there is *zero* support for this anywhere else in `naughty` -- and very, very few applications support this. -- -- This exists purely to comply with the specification. -- -- @property images -- @tparam[opt=nil] nil|table images -- @tablerowtype A list of `image`s. -- @propemits true false --- Foreground color. -- --@DOC_awful_notification_fg_EXAMPLE@ -- -- @property fg -- @tparam[beautiful.notification_fg] color|nil fg -- @propbeautiful -- @propemits true false -- @see title -- @see gears.color --- Background color. -- --@DOC_awful_notification_bg_EXAMPLE@ -- -- @property bg -- @tparam[opt=beautiful.notification_bg] color|nil bg -- @propemits true false -- @see title -- @see gears.color --- Border width. -- @property border_width -- @tparam[opt=beautiful.notification_border_width or 0] number|nil border_width -- @negativeallowed false -- @propertyunit pixel -- @propbeautiful -- @propemits true false --- Border color. -- --@DOC_awful_notification_border_color_EXAMPLE@ -- -- Please note that only string (like `#ff0000`) are supported. -- -- @property border_color -- @tparam[opt=beautiful.notification_border_color] string|nil border_color -- @propemits true false -- @see gears.color --- Widget shape. -- -- Note that when using a custom `request::display` handler or `naughty.rules`, -- choosing between multiple shapes depending on the content can be done using -- expressions like: -- -- -- The notification object is called `n` -- shape = #n.actions > 0 and -- gears.shape.rounded_rect or gears.shape.rounded_bar, -- --@DOC_awful_notification_shape_EXAMPLE@ -- --@DOC_naughty_shape_EXAMPLE@ -- -- @property shape -- @tparam[opt=beautiful.notification_shape] shape|nil shape -- @propemits true false --- Widget opacity. -- @property opacity -- @tparam[opt=beautiful.notification_opacity] number|nil opacity -- @rangestart 0.0 -- @rangestop 1.0 -- @propertyunit Gradient beween 0.0 (transparent) and 1.0 (opaque). -- @propemits true false --- Widget margin. -- --@DOC_awful_notification_margin_EXAMPLE@ -- -- @property margin -- @tparam[opt=beautiful.notification_margin] number|table|nil margin -- @tparam[opt=0] number margin.top -- @tparam[opt=0] number margin.bottom -- @tparam[opt=0] number margin.left -- @tparam[opt=0] number margin.right -- @propertytype number A single value for all sides. -- @propertytype table A different value for all sides. -- @negativeallowed false -- @propertyunit pixel -- @propemits true false -- @see shape --- Function to run on left click. -- -- Use the signals rather than this. -- -- @deprecatedproperty run -- @param function -- @see destroyed --- Function to run when notification is destroyed. -- -- Use the signals rather than this. -- -- @deprecatedproperty destroy -- @param function -- @see destroyed --- Table with any of the above parameters. -- args will override ones defined -- in the preset. -- @property preset -- @tparam[opt=nil] table|nil preset -- @propemits true false --- Function that will be called with all arguments. -- The notification will only be displayed if the function returns true. -- Note: this function is only relevant to notifications sent via dbus. -- @property callback -- @tparam[opt=nil] function|nil callback -- @functionparam table legacy_data -- @functionparam string appname -- @functionparam number replaces_id -- @functionparam string app_icon -- @functionparam string title -- @functionparam string message -- @functionparam table actions -- @functionparam table hints -- @functionparam number expire -- @functionnoreturn -- @propemits true false --- A table containing strings that represents actions to buttons. -- -- The table key (a number) is used by DBus to set map the action. -- -- @property actions -- @tparam[opt={}] table actions -- @tablerowtype A list of `naughty.action` objects. -- @propemits true false --- Ignore this notification, do not display. -- -- Note that this property has to be set in a `preset` or in a `request::preset` -- handler. -- -- @property ignore -- @tparam[opt=false] boolean ignore -- @propemits true false --- Tell if the notification is currently suspended (read only). -- -- This is always equal to `naughty.suspended` -- @property suspended -- @tparam[opt=false] boolean suspended -- @propemits true false -- @see naughty.suspended --- If the notification is expired. -- @property is_expired -- @tparam boolean is_expired -- @propemits true false -- @propertydefault This becomes `true` either then `:destroy()` is called or -- if the `timeout` expires. -- @see naughty.expiration_paused --- If the timeout needs to be reset when a property changes. -- -- By default it fallsback to `naughty.auto_reset_timeout`, which itself is -- true by default. -- -- @property auto_reset_timeout -- @tparam[opt=true] boolean auto_reset_timeout -- @propemits true false --- Emitted when the notification is destroyed. -- @signal destroyed -- @tparam number reason Why it was destroyed -- @tparam boolean keep_visible If it was kept visible. -- @see naughty.notification_closed_reason -- . --FIXME needs a description -- @property ignore_suspend If set to true this notification -- will be shown even if notifications are suspended via `naughty.suspend`. --- A list of clients associated with this notification. -- -- When used with DBus notifications, this returns all clients sharing the PID -- of the notification sender. Note that this is highly unreliable. -- Applications that use a different process to send the notification or -- applications (and scripts) calling the `notify-send` command wont have any -- client. -- -- @property clients -- @tparam[opt={}] table clients -- @tablerowtype A list of `client` objects. --- The maximum popup width. -- -- Some notifications have overlong message, cap them to this width. Note that -- this is ignored by `naughty.list.notifications` because it delegate this -- decision to the layout. -- -- @property max_width -- @tparam[opt=500] number|nil max_width -- @propertyunit pixel -- @negativeallowed false -- @propemits true false -- @propbeautiful --- The application name specified by the notification. -- -- This can be anything. It is usually less relevant than the `clients` -- property, but can sometime be specified for remote or headless notifications. -- In these case, it helps to triage and detect the notification from the rules. -- @property app_name -- @tparam[opt=nil] string|nil app_name -- @propemits true false --- The widget template used to represent the notification. -- -- Some notifications, such as chat messages or music applications are better -- off with a specialized notification widget. -- -- @property widget_template -- @tparam[opt=nil] template|nil widget_template -- @propertydefault The default template as the icon, title, message and actions. -- @propemits true false --- Destroy notification by notification object. -- -- @method destroy -- @tparam string reason One of the reasons from `notification_closed_reason` -- @tparam[opt=false] boolean keep_visible If true, keep the notification visible -- @treturn boolean True if the popup was successfully destroyed, false otherwise. -- @emits destroyed -- @emitstparam destroyed integer reason The reason. -- @emitstparam destroyed boolean keep_visible If the notification should be kept. -- @see naughty.notification_closed_reason function notification:destroy(reason, keep_visible) if self._private.is_destroyed then gdebug.print_warning("Trying to destroy the same notification twice. It".. " was destroyed because: "..self._private.destroy_reason) return false end reason = reason or cst.notification_closed_reason.dismissed_by_user self:emit_signal("destroyed", reason, keep_visible) self._private.is_destroyed = true self._private.destroy_reason = reason return true end --- Set new notification timeout. -- @method reset_timeout -- @tparam number new_timeout Time in seconds after which notification disappears. -- @noreturn function notification:reset_timeout(new_timeout) if self.timer then self.timer:stop() end -- Do not set `self.timeout` to `self.timeout` since that would create the -- timer before the constructor ends. if new_timeout and self.timer then self.timeout = new_timeout elseif new_timeout and new_timeout ~= self._private.timeout then local previous_timer = self._private.timeout timer.delayed_call(function() if self._private.timeout == previous_timer then self.timeout = new_timeout end end) end if self.timer and not self.timer.started then self.timer:start() end end function notification:set_id(new_id) assert(self._private.id == nil, "Notification identifier can only be set once") self._private.id = new_id self:emit_signal("property::id", new_id) end -- Return true if `self` is suspended. local function get_suspended(self) return naughty.suspended and (not self._private.ignore_suspend) end function notification:set_timeout(timeout) timeout = timeout or 0 local die = function (reason) if reason == cst.notification_closed_reason.expired then self.is_expired = true if naughty.expiration_paused then table.insert(naughty.notifications._expired[1], self) return end end self:destroy(reason) end if self.timer and self._private.timeout == timeout then return end -- 0 == never if timeout > 0 then local timer_die = timer { timeout = timeout } timer_die:connect_signal("timeout", function() pcall(die, cst.notification_closed_reason.expired) -- Prevent infinite timers events on errors. if timer_die.started then timer_die:stop() end end) --FIXME there's still a dependency loop to fix before it works if not get_suspended(self) then timer_die:start() end -- Prevent a memory leak and the accumulation of active timers if self.timer and self.timer.started then self.timer:stop() end self.timer = timer_die end self.die = die self._private.timeout = timeout self:emit_signal("property::timeout", timeout) end function notification:get_message() -- This property was called "text" in older versions. -- Some modules like `lain` abused of the presets (they -- had little choice at the time) to set the message on -- an existing popup. local p = rawget(self, "preset") or {} local message = self._private.message or p.message or "" if message == "" and p.text and p.text ~= "" then gdebug.deprecate( "Using the preset configuration to set the notification ".. "message is not supported. Please use `n.message = 'text'`.", {deprecated_in=5} ) return p.text end return message end function notification:set_text(txt) gdebug.deprecate( "The `text` attribute is deprecated, use `message`", {deprecated_in=5} ) self:set_message(txt) end function notification:get_text() gdebug.deprecate( "The `text` attribute is deprecated, use `message`", {deprecated_in=5} ) return self:get_message() end local properties = { "message" , "title" , "timeout" , "hover_timeout" , "app_name" , "position", "ontop" , "border_width" , "width" , "font" , "icon" , "icon_size" , "fg" , "bg" , "height" , "border_color" , "shape" , "opacity" , "margin" , "ignore_suspend" , "destroy" , "preset" , "callback", "actions" , "run" , "id" , "ignore" , "auto_reset_timeout", "urgency" , "image" , "images" , "widget_template" , "max_width", "app_icon", } for _, prop in ipairs(properties) do notification["get_"..prop] = notification["get_"..prop] or function(self) -- It's possible this could be called from the `request::preset` handler. -- `rawget()` is necessary to avoid a stack overflow. local preset = rawget(self, "preset") return self._private[prop] or (preset and preset[prop]) or cst.config.defaults[prop] end notification["set_"..prop] = notification["set_"..prop] or function(self, value) self._private[prop] = value self:emit_signal("property::"..prop, value) -- When a notification is updated over dbus or by setting a property, -- it is usually convenient to reset the timeout. local reset = ((not self.suspended) or self._private.ignore_suspend) and self.auto_reset_timeout ~= false and naughty.auto_reset_timeout if reset then self:reset_timeout() end end end -- Changing the image will change the icon, make sure property::icon is emitted. for _, prop in ipairs {"image", "images" } do local cur = notification["set_"..prop] notification["set_"..prop] = function(self, value) cur(self, value) self._private.icon = nil self:emit_signal("property::icon") end end local hints_default = { urgency = "normal", resident = false, } for _, prop in ipairs { "category", "resident" } do notification["get_"..prop] = notification["get_"..prop] or function(self) return self._private[prop] or ( self._private.freedesktop_hints and self._private.freedesktop_hints[prop] ) or hints_default[prop] end notification["set_"..prop] = notification["set_"..prop] or function(self, value) self._private[prop] = value self:emit_signal("property::"..prop, value) end end -- Stop the request::icon when one is found. local function request_filter(self, context, _) if not pcommon.check(self, "notification", "icon", context) then return true end if self._private.icon then return true end end -- Convert encoded local URI to Unix paths. local function check_path(input) if type(input) ~= "string" then return nil end if input:sub(1,7) == "file://" then input = input:sub(8) end -- urldecode input = input:gsub("%%(%x%x)", function(x) return string.char(tonumber(x, 16)) end ) return gfs.file_readable(input) and input or nil end function notification.get_icon(self) -- Honor all overrides. if self._private.icon then return self._private.icon == "" and nil or self._private.icon end -- First, check if the image is passed as a surface or a path. if self.image and self.image ~= "" then naughty._emit_signal_if("request::icon", request_filter, self, "image", { image = self.image }) elseif self.images then naughty._emit_signal_if("request::icon", request_filter, self, "images", { images = self.images }) end if self._private.icon then return self._private.icon == "" and nil or self._private.icon end -- Second level of fallback, icon paths. local path = check_path(self._private.app_icon) if path then naughty._emit_signal_if("request::icon", request_filter, self, "path", { path = path }) end if self._private.icon then return self._private.icon == "" and nil or self._private.icon end -- Third level fallback is `app_icon`. if self._private.app_icon then naughty._emit_signal_if("request::icon", request_filter, self, "app_icon", { app_icon = self._private.app_icon }) end -- Finally, the clients. naughty._emit_signal_if("request::icon", request_filter, self, "clients", {}) return self._private.icon == "" and nil or self._private.icon end function notification.get_clients(self) -- Clients from the future don't send notification, it's useless to reload -- the list over and over. if self._private.clients then return self._private.clients end if not self._private._unique_sender then return {} end self._private.clients = require("naughty.dbus").get_clients(self) return self._private.clients end function notification.set_actions(self, new_actions) for _, a in ipairs(self._private.actions or {}) do a:disconnect_signal("_changed", self._private.action_cb ) a:disconnect_signal("invoked" , self._private.invoked_cb) end -- Clone so `append_actions` doesn't add unwanted actions to other -- notifications. self._private.actions = gtable.clone(new_actions, false) for _, a in ipairs(self._private.actions or {}) do a:connect_signal("_changed", self._private.action_cb ) a:connect_signal("invoked" , self._private.invoked_cb) end self:emit_signal("property::actions", new_actions) -- When a notification is updated over dbus or by setting a property, -- it is usually convenient to reset the timeout. local reset = (not get_suspended(self)) and self.auto_reset_timeout ~= false and naughty.auto_reset_timeout if reset then self:reset_timeout() end end --- Add more actions to the notification. -- @method append_actions -- @tparam table new_actions -- @noreturn function notification:append_actions(new_actions) self._private.actions = self._private.actions or {} for _, a in ipairs(new_actions or {}) do a:connect_signal("_changed", self._private.action_cb ) a:connect_signal("invoked" , self._private.invoked_cb) table.insert(self._private.actions, a) end end function notification:set_screen(s) assert(not self._private.screen) s = s and capi.screen[s] or nil -- Avoid an infinite loop in the management code. if s == self._private.weak_screen[1] then return end self._private.weak_screen = setmetatable({s}, {__mode="v"}) self:emit_signal("property::screen", s) end function notification:get_screen() return self._private.weak_screen[1] end --TODO v6: remove this local function convert_actions(actions) gdebug.deprecate( "The notification actions should now be of type `naughty.action`, ".. "not strings or callback functions", {deprecated_in=5} ) local naction = require("naughty.action") local new_actions = {} -- Does not attempt to handle when there is a mix of strings and objects for idx, name in pairs(actions) do local cb, old_idx = nil, idx if type(name) == "function" then cb = name end if type(idx) == "string" then name, idx = idx, #actions+1 end local a = naction { position = idx, name = name, } if cb then a:connect_signal("invoked", cb) end new_actions[old_idx] = a end -- Yes, it modifies `args`, this is legacy code, cloning the args -- just for this isn't worth it. for old_idx, a in pairs(new_actions) do actions[a.position] = a actions[ old_idx ] = nil end end -- The old API used monkey-patched variable presets. -- -- Monkey-patched anything is always an issue and prevent module from safely -- doing anything without stepping on each other foot. In the background, -- presets were selected with a rule-like API anyway. local function select_legacy_preset(n, args) for _, obj in pairs(cst.config.mapping) do local filter, preset = obj[1], obj[2] if (not filter.urgency or filter.urgency == args.urgency) and (not filter.category or filter.category == args.category) and (not filter.appname or filter.appname == args.appname) then args.preset = gtable.join(args.preset or {}, preset) end end -- gather variables together rawset(n, "preset", gtable.join( cst.config.defaults or {}, args.preset or cst.config.presets.normal or {}, rawget(n, "preset") or {} )) for k, v in pairs(n.preset) do -- Don't keep a strong reference to the screen, Lua 5.1 GC wont be -- smart enough to unwind the mess of circular weak references. if k ~= "screen" then n._private[k] = v end end if n.preset.screen then n._private.weak_screen[1] = capi.screen[n.preset.screen] end end --- Create a notification. -- -- @tparam[opt={}] table args The argument table containing any of the arguments below. -- @tparam[opt=""] string args.text Text of the notification. -- @tparam[opt] string args.title Title of the notification. -- @tparam[opt=5] integer args.timeout Time in seconds after which popup expires. -- Set 0 for no timeout. -- @tparam[opt] number args.hover_timeout Delay in seconds after which hovered popup disappears. -- @tparam[opt=focused] integer|screen args.screen Target screen for the notification. -- @tparam[opt="top_right"] string args.position Corner of the workarea displaying the popups. -- Values: `"top_right"`, `"top_left"`, `"bottom_left"`, -- `"bottom_right"`, `"top_middle"`, `"bottom_middle"`, `"middle"`. -- @tparam[opt=true] boolean args.ontop Boolean forcing popups to display on top. -- @tparam[opt=`beautiful.notification_height` or auto] integer args.height Popup height. -- @tparam[opt=`beautiful.notification_width` or auto] integer args.width Popup width. -- @tparam[opt=`beautiful.notification_font` or `beautiful.font` or `awesome.font`] string|lgi.Pango.FontDescription args.font Notification font. -- @tparam[opt] image args.icon Path to icon. -- @tparam[opt] integer args.icon_size Desired icon size in px. -- @tparam[opt=`beautiful.notification_fg` or `beautiful.fg_focus` or `'#ffffff'`] string args.fg Foreground color. -- @tparam[opt=`beautiful.notification_fg` or `beautiful.bg_focus` or `'#535d6c'`] string args.bg Background color. -- @tparam[opt=`beautiful.notification_border_width` or 1] integer args.border_width Border width. -- @tparam[opt=`beautiful.notification_border_color` or `beautiful.border_color_active` or `'#535d6c'`] gears.color args.border_color Border color. -- @tparam[opt=beautiful.notification_shape] gears.shape args.shape Widget shape. -- @tparam[opt=beautiful.notification_opacity] gears.opacity args.opacity Widget opacity. -- @tparam[opt=beautiful.notification_margin] gears.margin args.margin Widget margin. -- @tparam[opt] function args.run Function to run on left click. The notification -- object will be passed to it as an argument. -- You need to call e.g. -- `notification.die(naughty.notification_closed_reason.dismissedByUser)` from -- there to dismiss the notification yourself. -- @tparam[opt] function args.destroy Function to run when notification is destroyed. -- @tparam[opt] table args.preset Table with any of the above parameters. -- Note: Any parameters specified directly in args will override ones defined -- in the preset. -- @tparam[opt] function args.callback Function that will be called with all arguments. -- The notification will only be displayed if the function returns true. -- Note: this function is only relevant to notifications sent via dbus. -- @tparam[opt] table args.actions A list of `naughty.action`s. -- @tparam[opt=false] boolean args.ignore_suspend If set to true this notification -- will be shown even if notifications are suspended via `naughty.suspend`. -- @treturn naughty.notification A new notification object. -- @constructorfct naughty.notification -- @usage naughty.notification { -- title = "Achtung!", -- message = "You're idling", timeout = 0 -- } local function create(args) if cst.config.notify_callback then args = cst.config.notify_callback(args) if not args then return end end assert(not args.id, "Identifiers cannot be specified externally") args = args or {} -- Old actions usually have callbacks and names. But this isn't non -- compliant with the spec. The spec has explicit ordering and optional -- icons. The old format doesn't allow these metadata to be stored. local is_old_action = args.actions and ( (args.actions[1] and type(args.actions[1]) == "string") or (type(next(args.actions)) == "string") ) local n = gobject { enable_properties = true, } if args.text then gdebug.deprecate( "The `text` attribute is deprecated, use `message`", {deprecated_in=5} ) args.message = args.text end assert(naughty.emit_signal) -- Make sure all signals bubble up n:_connect_everything(naughty.emit_signal) -- Avoid modifying the original table local private = {weak_screen = setmetatable({}, {__mode="v"})} rawset(n, "_private", private) -- Allow extensions to create override the preset with custom data if not naughty._has_preset_handler then select_legacy_preset(n, args) end if is_old_action then convert_actions(args.actions) end for k, v in pairs(args) do -- Don't keep a strong reference to the screen, Lua 5.1 GC wont be -- smart enough to unwind the mess of circular weak references. if k ~= "screen" then private[k] = v end end -- It's an automatic property n.is_expired = false gtable.crush(n, notification, true) -- Always emit property::actions when any of the action change to allow -- some widgets to be updated without over complicated built-in tracking -- of all options. function n._private.action_cb() n:emit_signal("property::actions") end -- Listen to action press and destroy non-resident notifications. function n._private.invoked_cb(a, notif) if (not notif) or notif == n then n:emit_signal("invoked", a) if not n.resident then n:destroy(cst.notification_closed_reason.dismissed_by_user) end end end -- notif.actions should not be nil to allow checking if there is actions -- using the shorthand `if #notif.actions > 0 then` private.actions = {} if args.actions then notification.set_actions(n, args.actions) end n.id = n.id or notification._gen_next_id() -- The rules are attached to this. if naughty._has_preset_handler then naughty.emit_signal("request::preset", n, "new", args) end -- Register the notification before requesting a widget n:emit_signal("new", args) -- Let all listeners handle the actual visual aspects if (not n.ignore) and ((not n.preset) or n.preset.ignore ~= true) and (not get_suspended(n)) then naughty.emit_signal("request::display" , n, "new", args) naughty.emit_signal("request::fallback", n, "new", args) end -- Because otherwise the setter logic would not be executed if n._private.timeout then n:set_timeout(n._private.timeout or (n.preset and n.preset.timeout) or cst.config.timeout ) end return n end --- Grant a permission for a notification. -- -- @method grant -- @tparam string permission The permission name (just the name, no `request::`). -- @tparam string context The reason why this permission is requested. -- @noreturn -- @see awful.permissions --- Deny a permission for a notification -- -- @method deny -- @tparam string permission The permission name (just the name, no `request::`). -- @tparam string context The reason why this permission is requested. -- @noreturn -- @see awful.permissions pcommon.setup_grant(notification, "notification") -- This allows notification to be updated later. local counter = 1 -- Identifier support. function notification._gen_next_id() counter = counter+1 return counter end --@DOC_object_COMMON@ return setmetatable(notification, {__call = function(_, ...) return create(...) end})