Merge pull request #2988 from Elv13/notif_rules_groundwork

Fix multiple notifications issue
This commit is contained in:
Emmanuel Lepage Vallée 2020-02-15 17:59:28 -05:00 committed by GitHub
commit 59b31e74b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 617 additions and 88 deletions

View File

@ -1533,7 +1533,7 @@ function placement.next_to(d, args)
geo.width, geo.height = dgeo.width, dgeo.height
fit = fit_in_bounding(pos.region.screen, geo, args)
fit = fit_in_bounding(pos.region, geo, args)
if fit then break end
end

View File

@ -21,7 +21,7 @@
-- @popupmod awful.popup
---------------------------------------------------------------------------
local wibox = require( "wibox" )
local util = require( "awful.util" )
local gtable = require( "gears.table" )
local placement = require( "awful.placement" )
local xresources= require("beautiful.xresources")
local timer = require( "gears.timer" )
@ -441,7 +441,7 @@ local function create_popup(_, args)
enable_properties = true
})
util.table.crush(ii, main_widget, true)
gtable.crush(ii, main_widget, true)
-- Create a wibox to host the widget
local w = wibox(args or {})
@ -453,7 +453,7 @@ local function create_popup(_, args)
widget = child_widget
})
util.table.crush(w, popup)
gtable.crush(w, popup)
ii:set_widget(child_widget)

View File

@ -80,9 +80,17 @@ end
--- Return true whether rectangle B is in the right direction
-- compared to rectangle A.
-- @param dir The direction.
-- @param gA The geometric specification for rectangle A.
-- @param gB The geometric specification for rectangle B.
--
-- The valid `dir` are:
--
-- * up
-- * down
-- * left
-- * right
--
-- @tparam string dir The direction.
-- @tparam table gA The geometric specification for rectangle A.
-- @tparam table gB The geometric specification for rectangle B.
-- @return True if B is in the direction of A.
local function is_in_direction(dir, gA, gB)
if dir == "up" then
@ -100,9 +108,17 @@ end
--- Calculate distance between two points.
-- i.e: if we want to move to the right, we will take the right border
-- of the currently focused screen and the left side of the checked screen.
-- @param dir The direction.
-- @param _gA The first rectangle.
-- @param _gB The second rectangle.
--
-- The valid `dir` are:
--
-- * up
-- * down
-- * left
-- * right
--
-- @tparam string dir The direction.
-- @tparam table _gA The first rectangle.
-- @tparam table _gB The second rectangle.
-- @return The distance between the screens.
local function calculate_distance(dir, _gA, _gB)
local gAx = _gA.x
@ -158,6 +174,7 @@ end
-- @tparam table a The area.
-- @tparam table b The other area.
-- @treturn boolean If the areas are identical.
-- @staticfct gears.geometry.rectangle.are_equal
function gears.geometry.rectangle.are_equal(a, b)
for _, v in ipairs {"x", "y", "width", "height"} do
if a[v] ~= b[v] then return false end
@ -165,9 +182,28 @@ function gears.geometry.rectangle.are_equal(a, b)
return true
end
--- Return if rectangle `a` is within rectangle `b`.
--
-- This includes the edges. 100% of `a` area has to be within `b` for this
-- function to return true. If you wish to know if any part of `a` intersect
-- with `b`, use `gears.geometry.rectangle.get_intersection`.
--
-- @tparam table a The smaller area.
-- @tparam table b The larger area.
-- @treturn boolean If the areas are identical.
-- @staticfct gears.geometry.rectangle.is_inside
-- @see gears.geometry.rectangle.get_intersection
function gears.geometry.rectangle.is_inside(a, b)
return (a.x >= b.x
and a.y >= b.y
and a.x+a.width <= b.x + b.width
and a.y+a.height <= b.y + b.height
)
end
--- Check if an area intersect another area.
-- @param a The area.
-- @param b The other area.
-- @tparam table a The area.
-- @tparam table b The other area.
-- @return True if they intersect, false otherwise.
-- @staticfct gears.geometry.rectangle.area_intersect_area
function gears.geometry.rectangle.area_intersect_area(a, b)
@ -190,6 +226,7 @@ end
-- @tparam number b.height The rectangle height
-- @treturn table The intersect area.
-- @staticfct gears.geometry.rectangle.get_intersection
-- @see gears.geometry.rectangle.is_inside
function gears.geometry.rectangle.get_intersection(a, b)
local g = {}
g.x = math.max(a.x, b.x)

View File

@ -27,6 +27,7 @@ local action = {}
-- The action name.
-- @property name
-- @tparam string name The name.
-- @propemits true false
-- If the action is selected.
--
@ -35,14 +36,17 @@ local action = {}
--
-- @property selected
-- @param boolean
-- @propemits true false
--- The action position (index).
-- @property position
-- @param number
-- @propemits true false
--- The action icon.
-- @property icon
-- @tparam gears.surface|string icon
-- @propemits true false
--- If the action should hide the label and only display the icon.
--
@ -50,6 +54,7 @@ local action = {}
--
-- @property icon_only
-- @param[opt=false] boolean
-- @propemits true false
--- When a notification is invoked.
-- @signal invoked

View File

@ -36,13 +36,14 @@ local function update_background(notif, wdg)
wdg:set_bg(bg)
wdg:set_shape(shape) -- otherwise there's no borders
wdg:set_shape_border_width(bw)
wdg:set_shape_border_color(bc)
wdg:set_border_width(bw)
wdg:set_border_color(bc)
end
--- The attached notification.
-- @property notification
-- @tparam naughty.notification notification
-- @propemits true false
function background:set_notification(notif)
if self._private.notification == notif then return end
@ -66,12 +67,16 @@ function background:set_notification(notif)
notif:connect_signal("property::border_width", self._private.background_changed_callback)
notif:connect_signal("property::border_color", self._private.background_changed_callback)
notif:connect_signal("property::shape" , self._private.background_changed_callback)
self:emit_signal("property::notification", notif)
end
--- Create a new naughty.container.background.
-- @tparam table args
-- @tparam naughty.notification args.notification The notification.
-- @constructorfct naughty.container.background
-- @usebeautiful beautiful.notification_border_width Fallback when the `border_width` property isn't set.
-- @usebeautiful beautiful.notification_border_color Fallback when the `border_color` property isn't set.
-- @usebeautiful beautiful.notification_shape Fallback when the `shape` property isn't set.
local function new(args)
args = args or {}

View File

@ -102,6 +102,8 @@ gtable.crush(naughty, require("naughty.constants"))
--
-- @property suspended
-- @param boolean
-- @emits added
-- @propemits true false
--- Do not allow notifications to auto-expire.
--
@ -111,6 +113,7 @@ gtable.crush(naughty, require("naughty.constants"))
--
-- @property expiration_paused
-- @param[opt=false] boolean
-- @propemits true false
--- A table with all active notifications.
--
@ -122,6 +125,7 @@ gtable.crush(naughty, require("naughty.constants"))
--
-- @property active
-- @param table
-- @propemits false false
--- True when there is a handler connected to `request::display`.
-- @property has_display_handler
@ -134,6 +138,7 @@ gtable.crush(naughty, require("naughty.constants"))
--
-- @property auto_reset_timeout
-- @tparam[opt=true] boolean auto_reset_timeout
-- @propemits true false
--- Enable or disable naughty ability to claim to support animations.
--
@ -143,6 +148,7 @@ gtable.crush(naughty, require("naughty.constants"))
--
-- @property image_animations_enabled
-- @param[opt=false] boolean
-- @propemits true false
--- Enable or disable the persistent notifications.
--
@ -158,6 +164,7 @@ gtable.crush(naughty, require("naughty.constants"))
--
-- @property persistence_enabled
-- @param[opt=false] boolean
-- @propemits true false
local properties = {
suspended = false,
@ -196,6 +203,16 @@ screen.connect_for_each_screen(function(s)
end)
capi.screen.connect_signal("removed", function(scr)
-- Allow the notifications to be moved to another screen.
for _, list in pairs(naughty.notifications[scr]) do
-- Clone the list to avoid having an iterator while mutating.
list = gtable.clone(list, false)
for _, n in ipairs(list) do
naughty.emit_signal("request::screen", n, "removed", {})
end
end
-- Destroy all notifications on this screen
naughty.destroy_all_notifications({scr})
naughty.notifications[scr] = nil
@ -210,6 +227,7 @@ local function remove_from_index(n)
for _, ns in pairs(positions) do
for k, n2 in ipairs(ns) do
if n2 == n then
assert(ns[k+1] ~= n, "The notification index is corrupted")
table.remove(ns, k)
return
end
@ -220,6 +238,11 @@ end
-- When id or screen are set after the object is created, update the indexing.
local function update_index(n)
-- Do things in the right order.
if not n._private.registered then return end
assert(not n._private.is_destroyed, "The notification index is corrupted")
-- Find the only index and remove it (there's an useless loop, but it's small).
remove_from_index(n)
@ -231,7 +254,6 @@ local function update_index(n)
table.insert(naughty.notifications[s][n.position], n)
end
--- Notification state.
--
-- This function is deprecated, use `naughty.suspended`.
@ -357,6 +379,9 @@ function naughty.destroy_all_notifications(screens, reason)
for _, scr in pairs(screens) do
for _, list in pairs(naughty.notifications[scr]) do
while #list > 0 do
-- Better cause an error than risk an infinite loop.
assert(not list[1]._private.is_destroyed)
ret = ret and list[1]:destroy(reason)
end
end
@ -406,6 +431,10 @@ function naughty.get__has_preset_handler()
return conns["request::preset"] and #conns["request::preset"] > 0 or false
end
function naughty._reset_display_handlers()
conns["request::display"] = nil
end
--- Set new notification timeout.
--
-- This function is deprecated, use `notification:reset_timeout(new_timeout)`.
@ -500,6 +529,20 @@ function naughty.set_expiration_paused(p)
end
end
--- The default handler for `request::screen`.
--
-- It selects `awful.screen.focused()`.
--
-- @signalhandler naughty.default_screen_handler
function naughty.default_screen_handler(n)
if n.screen and n.screen.valid then return end
n.screen = screen.focused()
end
naughty.connect_signal("request::screen", naughty.default_screen_handler)
--- Emitted when an error occurred and requires attention.
-- @signal request::display_error
-- @tparam string message The error message.
@ -546,15 +589,28 @@ end
-- @tparam naughty.action action The action.
-- @tparam string icon_name The icon name.
--- Emitted when the screen is not defined or being removed.
-- @signal request::screen
-- @tparam table notification The `naughty.notification` object. This is
-- currently either "new" or "removed".
-- @tparam string context Why is the signal sent.
-- Register a new notification object.
local function register(notification, args)
assert(not notification._private.registered)
-- Add the some more properties
rawset(notification, "get_suspended", get_suspended)
--TODO v5 uncouple the notifications and the screen
local s = get_screen(args.screen
or (notification.preset and notification.preset.screen)
or screen.focused())
local s = get_screen(notification.screen or args.screen
or (notification.preset and notification.preset.screen))
if not s then
naughty.emit_signal("request::screen", notification, "new", {})
s = notification.screen
end
assert(s)
-- insert the notification to the table
table.insert(naughty._active, notification)
@ -562,6 +618,8 @@ local function register(notification, args)
notification.idx = #naughty.notifications[s][notification.position]
notification.screen = s
notification._private.registered = true
if properties.suspended and not args.ignore_suspend then
notification._private.args = args
table.insert(naughty.notifications.suspended, notification)
@ -599,7 +657,7 @@ local function set_index_miss(_, key, value)
resume()
end
naughty.emit_signal("property::"..key)
naughty.emit_signal("property::"..key, value)
else
rawset(naughty, key, value)
end

View File

@ -10,13 +10,16 @@
-- @popupmod naughty.layout.box
----------------------------------------------------------------------------
local capi = {screen=screen}
local beautiful = require("beautiful")
local gtimer = require("gears.timer")
local gtable = require("gears.table")
local wibox = require("wibox")
local popup = require("awful.popup")
local awcommon = require("awful.widget.common")
local placement = require("awful.placement")
local abutton = require("awful.button")
local ascreen = require("awful.screen")
local gpcall = require("gears.protected_call")
local dpi = require("beautiful").xresources.apply_dpi
@ -26,11 +29,33 @@ local box, by_position = {}, {}
-- Init the weak tables for each positions. It is done ahead of time rather
-- than when notifications are added to simplify the code.
for _, pos in ipairs { "top_left" , "top_middle" , "top_right",
local function init_screen(s)
if not s.valid then return end
if by_position[s] then return by_position[s] end
by_position[s] = setmetatable({},{__mode = "k"})
for _, pos in ipairs { "top_left" , "top_middle" , "top_right",
"bottom_left", "bottom_middle", "bottom_right" } do
by_position[pos] = setmetatable({},{__mode = "v"})
by_position[s][pos] = setmetatable({},{__mode = "v"})
end
return by_position[s]
end
ascreen.connect_for_each_screen(init_screen)
-- Manually cleanup to help the GC.
capi.screen.connect_signal("removed", function(scr)
-- By that time, all direct events should have been handled. Cleanup the
-- leftover. Being a weak table doesn't help Lua 5.1.
gtimer.delayed_call(function()
by_position[scr] = nil
end)
end)
local function get_spacing()
local margin = beautiful.notification_spacing or 2
return {top = margin, bottom = margin}
@ -42,9 +67,10 @@ local function update_position(position)
local align = position:match("_(.*)")
:gsub("left", "front"):gsub("right", "back")
for k, wdg in ipairs(by_position[position]) do
for _, pos in pairs(by_position) do
for k, wdg in ipairs(pos[position]) do
local args = {
geometry = by_position[position][k-1],
geometry = pos[position][k-1],
preferred_positions = {pref },
preferred_anchors = {align},
margins = get_spacing(),
@ -57,15 +83,16 @@ local function update_position(position)
wdg.visible = true
end
end
end
local function finish(self)
self.visible = false
assert(by_position[self.position])
assert(init_screen(self.screen)[self.position])
for k, v in ipairs(by_position[self.position]) do
for k, v in ipairs(init_screen(self.screen)[self.position]) do
if v == self then
table.remove(by_position[self.position], k)
table.remove(init_screen(self.screen)[self.position], k)
break
end
end
@ -73,6 +100,17 @@ local function finish(self)
update_position(self.position)
end
-- It isn't a good idea to use the `attach` `awful.placement` property. If the
-- screen is resized or the notification is moved, it causes side effects.
-- Better listen to geometry changes and reflow.
capi.screen.connect_signal("property::geometry", function(s)
for pos, notifs in pairs(by_position[s]) do
if #notifs > 0 then
update_position(pos)
end
end
end)
--- The maximum notification width.
-- @beautiful beautiful.notification_max_width
-- @tparam[opt=500] number notification_max_width
@ -92,8 +130,10 @@ end
-- @tparam[opt="top_right"] string notification_position
--- The widget notification object.
--
-- @property notification
-- @param naughty.notification
-- @tparam naughty.notification notification
-- @propemits true false
--- The widget template to construct the box content.
--
@ -135,6 +175,8 @@ end
--
-- @property widget_template
-- @param widget
-- @usebeautiful beautiful.notification_max_width The maximum width for the
-- resulting widget.
local function generate_widget(args, n)
local w = gpcall(wibox.widget.base.make_widget_from_value,
@ -192,12 +234,15 @@ local function init(self, notification)
end
end
local s = notification.screen
assert(s)
-- Add the notification to the active list
assert(by_position[position])
assert(init_screen(s)[position], "Invalid position "..position)
self:_apply_size_now()
table.insert(by_position[position], self)
table.insert(init_screen(s)[position], self)
local function update() update_position(position) end
@ -220,6 +265,8 @@ function box:set_notification(notif)
init(self, notif)
self._private.notification = notif
self:emit_signal("property::notification", notif)
end
function box:get_position()
@ -230,19 +277,41 @@ function box:get_position()
return "top_right"
end
--- Create a notification popup box.
--
-- @constructorfct naughty.layout.box
-- @tparam[opt=nil] table args
-- @tparam table args.widget_template A widget definition template which will
-- be instantiated for each box.
-- @tparam naughty.notification args.notification The notification object.
-- @tparam string args.position The position. See `naughty.notification.position`.
--@DOC_wibox_constructor_COMMON@
-- @usebeautiful beautiful.notification_position If `position` is not defined
-- in the notification object (or in this constructor).
local function new(args)
args = args or {}
-- Set the default wibox values
local new_args = {
ontop = true,
visible = false,
bg = args and args.bg or beautiful.notification_bg,
fg = args and args.fg or beautiful.notification_fg,
shape = args and args.shape or beautiful.notification_shape,
border_width = args and args.border_width or beautiful.notification_border_width or 1,
border_color = args and args.border_color or beautiful.notification_border_color,
bg = args.bg or beautiful.notification_bg,
fg = args.fg or beautiful.notification_fg,
shape = args.shape or beautiful.notification_shape,
border_width = args.border_width or beautiful.notification_border_width or 1,
border_color = args.border_color or beautiful.notification_border_color,
}
new_args = args and setmetatable(new_args, {__index = args}) or new_args
-- The C code needs `pairs` to work, so a full copy is required.
gtable.crush(new_args, args, true)
-- Add a weak-table layer for the screen.
local weak_args = setmetatable({
screen = args.notification and args.notification.screen or nil
}, {__mode="v"})
setmetatable(new_args, {__index = weak_args})
-- Generate the box before the popup is created to avoid the size changing
new_args.widget = generate_widget(new_args, new_args.notification)
@ -276,6 +345,8 @@ local function new(args)
abutton({ }, 3, hide)
))
gtable.crush(ret, box, false)
return ret
end

View File

@ -186,23 +186,47 @@ local actionlist = {}
--- The actionlist parent notification.
-- @property notification
-- @param notification
-- @tparam naughty.notification notification
-- @propemits true false
-- @see naughty.notification
--- The actionlist layout.
-- If no layout is specified, a `wibox.layout.fixed.horizontal` will be created
-- automatically.
-- @property layout
-- @param widget
-- @property base_layout
-- @tparam widget base_layout
-- @propemits true false
-- @see wibox.layout.fixed.horizontal
--- The actionlist parent notification.
-- @property widget_template
-- @param table
-- @tparam table widget_template
-- @propemits true false
--- A table with values to override each `beautiful.notification_action` values.
-- @property style
-- @param table
-- @tparam table style
-- @propemits true false
-- @usebeautiful beautiful.font Fallback when the `font` property isn't set.
-- @usebeautiful beautiful.notification_action_underline_normal Fallback.
-- @usebeautiful beautiful.notification_action_underline_selected Fallback.
-- @usebeautiful beautiful.notification_action_icon_only Fallback.
-- @usebeautiful beautiful.notification_action_label_only Fallback.
-- @usebeautiful beautiful.notification_action_shape_normal Fallback.
-- @usebeautiful beautiful.notification_action_shape_selected Fallback.
-- @usebeautiful beautiful.notification_action_shape_border_color_normal Fallback.
-- @usebeautiful beautiful.notification_action_shape_border_color_selected Fallback.
-- @usebeautiful beautiful.notification_action_shape_border_width_normal Fallback.
-- @usebeautiful beautiful.notification_action_shape_border_width_selected Fallback.
-- @usebeautiful beautiful.notification_action_icon_size_normal Fallback.
-- @usebeautiful beautiful.notification_action_icon_size_selected Fallback.
-- @usebeautiful beautiful.notification_action_bg_normal Fallback.
-- @usebeautiful beautiful.notification_action_bg_selected Fallback.
-- @usebeautiful beautiful.notification_action_fg_normal Fallback.
-- @usebeautiful beautiful.notification_action_fg_selected Fallback.
-- @usebeautiful beautiful.notification_action_bgimage_normal Fallback.
-- @usebeautiful beautiful.notification_action_bgimage_selected Fallback.
function actionlist:set_notification(notif)
self._private.notification = notif
@ -215,6 +239,7 @@ function actionlist:set_notification(notif)
self:emit_signal("widget::layout_changed")
self:emit_signal("widget::redraw_needed")
self:emit_signal("property::notification", notif)
end
function actionlist:set_base_layout(layout)
@ -224,6 +249,7 @@ function actionlist:set_base_layout(layout)
self:emit_signal("widget::layout_changed")
self:emit_signal("widget::redraw_needed")
self:emit_signal("property::base_layout", layout)
end
function actionlist:set_widget_template(widget_template)
@ -236,6 +262,7 @@ function actionlist:set_widget_template(widget_template)
self:emit_signal("widget::layout_changed")
self:emit_signal("widget::redraw_needed")
self:emit_signal("property::widget_template", widget_template)
end
function actionlist:set_style(style)
@ -246,6 +273,7 @@ function actionlist:set_style(style)
self:emit_signal("widget::layout_changed")
self:emit_signal("widget::redraw_needed")
self:emit_signal("property::style", style)
end
function actionlist:get_notification()

View File

@ -159,23 +159,48 @@ local notificationlist = {}
--- The notificationlist parent notification.
-- @property notification
-- @param notification
-- @tparam naughty.notification notification
-- @propemits true false
-- @see naughty.notification
--- The notificationlist layout.
--- A `wibox.layout` to be used to place the entries.
--
-- If no layout is specified, a `wibox.layout.fixed.vertical` will be created
-- automatically.
-- @property layout
-- @param widget
--
-- @property base_layout
-- @tparam widget base_layout
-- @propemits true false
-- @usebeautiful beautiful.notification_spacing
-- @see wibox.layout.fixed.horizontal
-- @see wibox.layout.fixed.vertical
-- @see wibox.layout.flex.horizontal
-- @see wibox.layout.flex.vertical
-- @see wibox.layout.grid
--- The notificationlist parent notification.
-- @property widget_template
-- @param table
-- @tparam table widget_template
-- @propemits true false
--- A table with values to override each `beautiful.notification_action` values.
-- @property style
-- @param table
-- @tparam table style
-- @propemits true false
-- @usebeautiful beautiful.notification_shape_normal Fallback.
-- @usebeautiful beautiful.notification_shape_selected Fallback.
-- @usebeautiful beautiful.notification_shape_border_color_normal Fallback.
-- @usebeautiful beautiful.notification_shape_border_color_selected Fallback.
-- @usebeautiful beautiful.notification_shape_border_width_normal Fallback.
-- @usebeautiful beautiful.notification_shape_border_width_selected Fallback.
-- @usebeautiful beautiful.notification_icon_size_normal Fallback.
-- @usebeautiful beautiful.notification_icon_size_selected Fallback.
-- @usebeautiful beautiful.notification_bg_normal Fallback.
-- @usebeautiful beautiful.notification_bg_selected Fallback.
-- @usebeautiful beautiful.notification_fg_normal Fallback.
-- @usebeautiful beautiful.notification_fg_selected Fallback.
-- @usebeautiful beautiful.notification_bgimage_normal Fallback.
-- @usebeautiful beautiful.notification_bgimage_selected Fallback.
function notificationlist:set_widget_template(widget_template)
self._private.widget_template = widget_template
@ -187,6 +212,7 @@ function notificationlist:set_widget_template(widget_template)
self:emit_signal("widget::layout_changed")
self:emit_signal("widget::redraw_needed")
self:emit_signal("property::widget_template", widget_template)
end
function notificationlist:set_style(style)
@ -197,6 +223,7 @@ function notificationlist:set_style(style)
self:emit_signal("widget::layout_changed")
self:emit_signal("widget::redraw_needed")
self:emit_signal("property::style", style)
end
function notificationlist:layout(_, width, height)
@ -213,20 +240,12 @@ function notificationlist:fit(context, width, height)
return wibox.widget.base.fit_widget(self, context, self._private.base_layout, width, height)
end
--- A `wibox.layout` to be used to place the entries.
-- @property base_layout
-- @param widget
-- @see wibox.layout.fixed.horizontal
-- @see wibox.layout.fixed.vertical
-- @see wibox.layout.flex.horizontal
-- @see wibox.layout.flex.vertical
-- @see wibox.layout.grid
--- A function to prevent some notifications from being added to the list.
-- @property filter
-- @param function
-- @tparam function filter
-- @propemits true false
for _, prop in ipairs { "filter", "client", "clients", "tag", "tags", "screen", "base_layout" } do
for _, prop in ipairs { "filter", "base_layout" } do
notificationlist["set_"..prop] = function(self, value)
self._private[prop] = value
@ -234,6 +253,7 @@ for _, prop in ipairs { "filter", "client", "clients", "tag", "tags", "screen",
self:emit_signal("widget::layout_changed")
self:emit_signal("widget::redraw_needed")
self:emit_signal("property::"..prop, value)
end
notificationlist["get_"..prop] = function(self)
@ -319,9 +339,9 @@ end
module.filter = {}
---
-- @param n The notification.
-- @return Always returns true because it doesn't filter anything at all.
--- All notifications.
-- @tparam naughty.notification n The notification.
-- @treturn boolean Always returns true because it doesn't filter anything at all.
-- @filterfunction naughty.list.notifications.filter.all
function module.filter.all(n) -- luacheck: no unused args
return true
@ -333,9 +353,9 @@ end
--
-- filter = function(n) return naughty.list.notifications.filter.most_recent(n, 3) end
--
-- @param n The notification.
-- @tparam naughty.notification n The notification.
-- @tparam[opt=1] number count The number of recent notifications to allow.
-- @return Always returns true because it doesn't filter anything at all.
-- @treturn boolean Always returns true because it doesn't filter anything at all.
-- @filterfunction naughty.list.notifications.filter.most_recent
function module.filter.most_recent(n, count)
for i=1, count or 1 do

View File

@ -15,6 +15,7 @@
-- @copyright 2017 Emmanuel Lepage Vallee
-- @coreclassmod naughty.notification
---------------------------------------------------------------------------
local capi = { screen = screen }
local gobject = require("gears.object")
local gtable = require("gears.table")
local gsurface = require("gears.surface")
@ -75,7 +76,8 @@ local notification = {}
-- This is the equivalent to a PID as allows external applications to select
-- notifications.
-- @property id
-- @param number
-- @tparam number id
-- @propemits true false
--- Text of the notification.
--
@ -89,12 +91,14 @@ local notification = {}
--- Title of the notification.
--@DOC_naughty_helloworld_EXAMPLE@
-- @property title
-- @param string
-- @tparam string title
-- @propemits true false
--- Time in seconds after which popup expires.
-- Set 0 for no timeout.
-- @property timeout
-- @param number
-- @tparam number timeout
-- @propemits true false
--- The notification urgency level.
--
@ -106,6 +110,7 @@ local notification = {}
--
-- @property urgency
-- @param string
-- @propemits true false
--- The notification category.
--
@ -152,6 +157,7 @@ local notification = {}
--
-- @property category
-- @tparam string|nil category
-- @propemits true false
--- True if the notification should be kept when an action is pressed.
--
@ -161,14 +167,17 @@ local notification = {}
--
-- @property resident
-- @param[opt=false] boolean
-- @propemits true false
--- Delay in seconds after which hovered popup disappears.
-- @property hover_timeout
-- @param number
-- @propemits true false
--- Target screen for the notification.
-- @property screen
-- @param screen
-- @propemits true false
--- Corner of the workarea displaying the popups.
--
@ -186,6 +195,7 @@ local notification = {}
--
-- @property position
-- @param string
-- @propemits true false
-- @see awful.placement.next_to
--- Boolean forcing popups to display on top.
@ -198,17 +208,20 @@ local notification = {}
--
-- @property height
-- @param number
-- @propemits true false
-- @see width
--- Popup width.
-- @property width
-- @param number
-- @propemits true false
-- @see height
--- Notification font.
--@DOC_naughty_colors_EXAMPLE@
-- @property font
-- @param string
-- @propemits true false
--- "All in one" way to access the default image or icon.
--
@ -228,12 +241,14 @@ local notification = {}
--
-- @property icon
-- @tparam string|surface icon
-- @propemits true false
-- @see app_icon
-- @see image
--- Desired icon size in px.
-- @property icon_size
-- @param number
-- @propemits true false
--- The icon provided in the `app_icon` field of the DBus notification.
--
@ -242,6 +257,7 @@ local notification = {}
--
-- @property app_icon
-- @param string
-- @propemits true false
--- The notification image.
--
@ -251,6 +267,7 @@ local notification = {}
--
-- @property image
-- @tparam string|surface image
-- @propemits true false
--- The notification (animated) images.
--
@ -262,6 +279,7 @@ local notification = {}
--
-- @property images
-- @tparam nil|table images
-- @propemits true false
--- Foreground color.
--
@ -269,6 +287,7 @@ local notification = {}
--
-- @property fg
-- @tparam string|color|pattern fg
-- @propemits true false
-- @see title
-- @see gears.color
@ -278,13 +297,14 @@ local notification = {}
--
-- @property bg
-- @tparam string|color|pattern bg
-- @propemits true false
-- @see title
-- @see gears.color
--- Border width.
-- @property border_width
-- @param number
-- @see title
-- @propemits true false
--- Border color.
--
@ -292,7 +312,7 @@ local notification = {}
--
-- @property border_color
-- @param string
-- @see title
-- @propemits true false
-- @see gears.color
--- Widget shape.
@ -311,10 +331,12 @@ local notification = {}
--
-- @property shape
-- @tparam gears.shape shape
-- @propemits true false
--- Widget opacity.
-- @property opacity
-- @tparam number opacity Between 0 to 1.
-- @propemits true false
--- Widget margin.
--
@ -322,6 +344,7 @@ local notification = {}
--
-- @property margin
-- @tparam number|table margin
-- @propemits true false
-- @see shape
--- Function to run on left click.
@ -345,12 +368,14 @@ local notification = {}
-- in the preset.
-- @property preset
-- @param table
-- @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
-- @param function
-- @propemits true false
--- A table containing strings that represents actions to buttons.
--
@ -358,6 +383,7 @@ local notification = {}
--
-- @property actions
-- @param table
-- @propemits true false
--- Ignore this notification, do not display.
--
@ -366,16 +392,19 @@ local notification = {}
--
-- @property ignore
-- @param boolean
-- @propemits true false
--- Tell if the notification is currently suspended (read only).
--
-- This is always equal to `naughty.suspended`
--@property suspended
--@param boolean
-- @propemits true false
--- If the notification is expired.
-- @property is_expired
-- @param boolean
-- @propemits true false
-- @see naughty.expiration_paused
--- If the timeout needs to be reset when a property changes.
@ -385,6 +414,7 @@ local notification = {}
--
-- @property auto_reset_timeout
-- @tparam[opt=true] boolean auto_reset_timeout
-- @propemits true false
--- Emitted when the notification is destroyed.
-- @signal destroyed
@ -415,6 +445,7 @@ local notification = {}
--
-- @property[opt=500] max_width
-- @param number
-- @propemits true false
--- The application name specified by the notification.
--
@ -423,6 +454,7 @@ local notification = {}
-- In these case, it helps to triage and detect the notification from the rules.
-- @property app_name
-- @param string
-- @propemits true false
--- The widget template used to represent the notification.
--
@ -431,16 +463,18 @@ local notification = {}
--
-- @property widget_template
-- @param table
--FIXME remove the screen attribute, let the handlers decide
-- document all handler extra properties
-- @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
-- @return True if the popup was successfully destroyed, false otherwise
-- @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"..
@ -525,6 +559,7 @@ function notification:set_timeout(timeout)
end
self.die = die
self._private.timeout = timeout
self:emit_signal("property::timeout", timeout)
end
function notification:set_text(txt)
@ -545,14 +580,14 @@ end
local properties = {
"message" , "title" , "timeout" , "hover_timeout" ,
"screen" , "position", "ontop" , "border_width" ,
"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_name",
"max_width",
}
for _, prop in ipairs(properties) do
@ -684,6 +719,23 @@ function notification:append_actions(new_actions)
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(
@ -835,7 +887,7 @@ local function create(args)
n:_connect_everything(naughty.emit_signal)
-- Avoid modifying the original table
local private = {}
local private = {weak_screen = setmetatable({}, {__mode="v"})}
rawset(n, "_private", private)
-- Allow extensions to create override the preset with custom data
@ -848,8 +900,12 @@ local function create(args)
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

View File

@ -77,6 +77,7 @@ end
--- The attached notification.
-- @property notification
-- @tparam naughty.notification notification
-- @propemits true false
function icon:set_notification(notif)
if self._private.notification == notif then return end
@ -95,6 +96,7 @@ function icon:set_notification(notif)
self._private.notification = notif
notif:connect_signal("property::icon", self._private.icon_changed_callback)
self:emit_signal("property::notification", notif)
end
local valid_strategies = {
@ -117,7 +119,11 @@ local valid_strategies = {
--@DOC_wibox_nwidget_icon_strategy_EXAMPLE@
--
-- @property resize_strategy
-- @param string
-- @tparam string resize_strategy
-- @propemits true false
-- @usebeautiful beautiful.notification_icon_resize_strategy The fallback when
-- there is no specified strategy.
-- @usebeautiful beautiful.notification_icon_size The size upper bound.
function icon:set_resize_strategy(strategy)
assert(valid_strategies[strategy], "Invalid strategy")
@ -125,6 +131,7 @@ function icon:set_resize_strategy(strategy)
self._private.resize_strategy = strategy
self:emit_signal("widget::redraw_needed")
self:emit_signal("property::resize_strategy", strategy)
end

View File

@ -36,6 +36,7 @@ end
--- The attached notification.
-- @property notification
-- @tparam naughty.notification notification
-- @propemits true false
function message:set_notification(notif)
if self._private.notification == notif then return end
@ -53,12 +54,15 @@ function message:set_notification(notif)
notif:connect_signal("property::message", self._private.message_changed_callback)
notif:connect_signal("property::fg" , self._private.message_changed_callback)
self:emit_signal("property::notification", notif)
end
--- Create a new naughty.widget.message.
-- @tparam table args
-- @tparam naughty.notification args.notification The notification.
-- @constructorfct naughty.widget.message
-- @usebeautiful beautiful.notification_fg
-- @usebeautiful beautiful.notification_font
local function new(args)
args = args or {}

View File

@ -36,6 +36,7 @@ end
--- The attached notification.
-- @property notification
-- @tparam naughty.notification notification
-- @propemits true false
function title:set_notification(notif)
if self._private.notification == notif then return end
@ -54,12 +55,15 @@ function title:set_notification(notif)
notif:connect_signal("property::title", self._private.title_changed_callback)
notif:connect_signal("property::fg" , self._private.title_changed_callback)
self:emit_signal("property::notification", notif)
end
--- Create a new naughty.widget.title.
-- @tparam table args
-- @tparam naughty.notification args.notification The notification.
-- @constructorfct naughty.widget.title
-- @usebeautiful beautiful.notification_fg
-- @usebeautiful beautiful.notification_font
local function new(args)
args = args or {}

View File

@ -145,6 +145,79 @@ describe("gears.geometry", function()
end)
end)
describe("rectangle.is_inside", function()
it("equality1", function()
assert.is_true(geo.rectangle.is_inside(
{x=0, y=0, width=10, height=10},
{x=0, y=0, width=10, height=10}
))
end)
it("equality2", function()
assert.is_true(geo.rectangle.is_inside(
{x=10, y=10, width=10, height=10},
{x=10, y=10, width=10, height=10}
))
end)
it("top left edge", function()
assert.is_true(geo.rectangle.is_inside(
{x=0, y=0, width=5 , height=5 },
{x=0, y=0, width=10, height=10}
))
end)
it("bottom right edge", function()
assert.is_true(geo.rectangle.is_inside(
{x=5, y=5, width=5 , height=5 },
{x=0, y=0, width=10, height=10}
))
end)
it("middle", function()
assert.is_true(geo.rectangle.is_inside(
{x=2.5, y=2.5, width=5 , height=5 },
{x=0 , y=0 , width=10, height=10}
))
end)
it("edge overflow", function()
assert.is_false(geo.rectangle.is_inside(
{x=0, y=0, width=11, height=11},
{x=0, y=0, width=10, height=10}
))
end)
it("middle overflow", function()
assert.is_false(geo.rectangle.is_inside(
{x=2.5, y=2.5, width=11, height=11},
{x=0 , y=0 , width=10, height=10}
))
end)
it("top left outside", function()
assert.is_false(geo.rectangle.is_inside(
{x=-10, y=-10, width=11, height=11},
{x=0 , y=0 , width=10, height=10}
))
end)
it("no intersect top left", function()
assert.is_false(geo.rectangle.is_inside(
{x=-10, y=-10, width=5 , height=5 },
{x=0 , y=0 , width=10, height=10}
))
end)
it("no intersect bottom right", function()
assert.is_false(geo.rectangle.is_inside(
{x=11, y=11, width=5 , height=5 },
{x=0 , y=0 , width=10, height=10}
))
end)
end)
describe("rectangle.area_remove", function()
-- TODO perhaps it would be better to compare against a cairo.region
-- than to have this overly specific tests?

View File

@ -11,6 +11,9 @@ local GLib = require("lgi" ).GLib
local gpcall = require("gears.protected_call")
local dwidget = require("naughty.widget._default")
-- Bypass the new rc.lua and force the legacy mode again.
naughty._reset_display_handlers()
-- This module test deprecated APIs
require("gears.debug").deprecate = function() end

View File

@ -0,0 +1,158 @@
--- This test suite focuses on the AwesomeWM v4.4+ notification API and
-- specifically how the `naughty.layout.box` popup widgets handle multi-screen
-- scenario.
local steps = {}
local naughty = require("naughty")
local grect = require("gears.geometry").rectangle
-- Do not use whatever `rc.lua` has. This avoids having to update the test
-- every time.
naughty._reset_display_handlers()
local called = 0
naughty.connect_signal("request::display", function(n)
called = called + 1
n._private._box_wibox = naughty.layout.box { notification = n }
end)
local positions = {
"top_left" , "top_middle" , "top_right" ,
"bottom_left" , "bottom_middle" , "bottom_right" ,
}
local objs = {}
local s1, s2 = mouse.screen, nil
for _, p in ipairs(positions) do
objs[p] = setmetatable({},{
__index=function(t,k) t[k]={};return t[k] end,
__mode = "k"
})
end
local function add_many(s)
for _, pos in ipairs(positions) do
for i=1, 5 do
table.insert(objs[pos][s], naughty.notification {
message = pos..i,
position = pos,
screen = s,
})
end
end
end
local function remove_at(s, idx)
-- This will be validated with many asserts in the code.
for _, pos in ipairs(positions) do
local n = table.remove(objs[pos][s], idx)
assert(n)
n:destroy()
assert(n._private.is_destroyed)
end
end
local function check_screen(s)
for _, pos in ipairs(positions) do
for _, n in pairs(objs[pos][s]) do
assert(n.screen == s)
assert(n._private._box_wibox)
assert(grect.is_inside(
n._private._box_wibox:geometry(),
s.geometry
))
end
end
end
-- Create notifications in each position.
table.insert(steps, function()
add_many(s1)
return true
end)
-- Make sure removing notification works.
table.insert(steps, function()
remove_at(s1, 2)
-- Split the screen
s1:split()
s2 = screen[2]
assert(s1 ~= s2)
return true
end)
-- Make sure the notification moved as the screen shrunk.
table.insert(steps, function()
check_screen(s1)
-- Make sure we can still remove them without errors.
remove_at(s1, 2)
-- Add more!
add_many(s2)
-- Make sure none got moved to the wrong position due to a fallback code
-- path triggered by accident. The first few iteration were prone to this.
check_screen(s1)
check_screen(s2)
return true
end)
-- Remove everything and see what happens.
table.insert(steps, function()
for _=1, 3 do
for _, s in ipairs {s1,s2} do
remove_at(s, 1)
end
end
for _=1, 2 do
remove_at(s2, 1)
end
-- And add them again.
add_many(s1)
add_many(s2)
return true
end)
--local weak = nil --FIXME
-- Delete a screen and make sure it gets GCed.
table.insert(steps, function()
s2:fake_remove()
-- Help the weak tables a little.
for _, pos in ipairs(positions) do
objs[pos][s1] = nil
end
-- Drop our string reference to s2.
--weak, s2 = setmetatable({s2}, {__mode="v"}), nil --FIXME
return true
end)
--FIXME
--table.insert(steps, function()
-- if weak[1] == nil then return true end
--
-- for _=1, 10 do
-- collectgarbage("collect")
-- end
--end)
require("_runner").run_steps(steps)