only operate on surface copies

Working with icons is tricky because their surfaces do not use reference counting correctly. If gears.surface(c.icon) is called multiple time on the same icon, it will cause a double-free error and Awesome will crash.
This commit is contained in:
BZ 2021-02-02 17:26:36 +01:00
parent 7b37f7eece
commit 0afab7248e
1 changed files with 98 additions and 72 deletions

170
init.lua
View File

@ -1,104 +1,130 @@
local gears = require("gears")
local awful = require("awful")
local theme = require("beautiful")
local cairo = require("lgi").cairo
local module = {}
-- luacheck: globals client
local client = client
local icons, dynamic_icons, dynamic_classes, delay
local function len(T)
local count = 0
for _ in pairs(T) do count = count + 1 end
return count
local count = 0
for _ in pairs(T) do
count = count + 1
end
return count
end
local function contains(T, V)
for _, v in ipairs(T) do
if v == V then
return true
end
end
return false
for _, v in ipairs(T) do
if v == V then
return true
end
end
return false
end
local function icon_copy(icon)
if not icon then
return nil
end
local s = gears.surface(icon)
local img = cairo.ImageSurface.create(cairo.Format.ARGB32, s:get_width(), s:get_height())
local cr = cairo.Context(img)
cr:set_source_surface(s, 0, 0)
cr:paint()
return img
end
local function set_icon(c, icon)
if c and c.valid then
if not c.icon_backup then
c.icon_backup = icons[c.class] and gears.surface(icons[c.class]) or gears.surface(c.icon)
end
if icon then
icon = gears.surface(icon)
c.icon = icon and icon._native or nil
end
end
if c and c.valid then
if not c.icon_backup then
c.icon_backup = icon_copy(icons[c.class]) or icon_copy(c.icon)
end
icon = icon_copy(icon)
if icon then
c.icon = icon._native
end
end
end
local function set_dynamic_icon(c)
if c.name then
for regex, icon in pairs(dynamic_icons) do
if string.find(c.name, regex) then
set_icon(c, icon)
return
end
end
end
if c.name then
for regex, icon in pairs(dynamic_icons) do
if string.find(c.name, regex) then
set_icon(c, icon)
return
end
end
end
if c.icon_backup then
c.icon = c.icon_backup and c.icon_backup._native or nil
end
if c.icon_backup then
c.icon = c.icon_backup._native or nil
end
end
local function setup(config)
local cfg = config or {}
local cfg = config or {}
icons = cfg.icons or theme.ic_icons or {}
dynamic_icons = cfg.dynamic_icons or theme.ic_dynamic_icons or {}
dynamic_classes = cfg.dynamic_classes or theme.ic_dynamic_classes or {}
delay = cfg.delay or 0.5
icons = cfg.icons or theme.ic_icons or {}
dynamic_icons = cfg.dynamic_icons or theme.ic_dynamic_icons or {}
dynamic_classes = cfg.dynamic_classes or theme.ic_dynamic_classes or {}
delay = cfg.delay or 0.5
if type(icons) ~= 'table' then icons = {} end
if type(dynamic_icons) ~= 'table' then dynamic_icons = {} end
if type(dynamic_classes) ~= 'table' then dynamic_classes = {} end
if type(delay) ~= 'number' then delay = 0.5 end
if type(icons) ~= 'table' then
icons = {}
end
if type(dynamic_icons) ~= 'table' then
dynamic_icons = {}
end
if type(dynamic_classes) ~= 'table' then
dynamic_classes = {}
end
if type(delay) ~= 'number' then
delay = 0.5
end
client.connect_signal("manage", function(c)
-- set icon based on c.class
awful.spawn.easy_async_with_shell("sleep " .. delay, function()
if c and c.valid and icons[c.class] then
set_icon(c, icons[c.class])
end
end)
client.connect_signal("manage", function(c)
-- set icon based on c.class
awful.spawn.easy_async_with_shell("sleep " .. delay, function()
if c and c.valid and icons[c.class] then
set_icon(c, icons[c.class])
end
end)
if len(dynamic_icons) == 0 then
-- user has not defined any dynamic_icons; exit
return
end
if len(dynamic_icons) == 0 then
-- user has not defined any dynamic_icons; exit
return
end
-- dynamic classes are only being checked if defined explicitly by the user
if len(dynamic_classes) > 0 then
if not contains(dynamic_classes, c.class) then
-- client is not in dynamic_classes; exit
return
end
end
-- dynamic classes are only being checked if defined explicitly by the user
if len(dynamic_classes) > 0 then
if not contains(dynamic_classes, c.class) then
-- client is not in dynamic_classes; exit
return
end
end
-- the client name is now being monitored
c:connect_signal("property::name", set_dynamic_icon)
end)
-- the client name is now being monitored
c:connect_signal("property::name", set_dynamic_icon)
end)
end
return setmetatable(module, { __call = function(_, ...)
setup(...)
-- we have to update all clients icons manually when the user restarts awesomewm
-- since there is no "property::name" signal emitted by already running clients.
-- the set delay has to be higher than the regular delay, otherwise a race condition between both signals exists.
awful.spawn.easy_async_with_shell("sleep " .. (delay + 0.5), function()
for _, c in ipairs(client.get()) do
if c and c.valid then
c:emit_signal("property::name")
end
end
end)
end })
return setmetatable(module, {
__call = function(_, ...)
setup(...)
-- we have to update all clients icons manually when the user restarts awesomewm
-- since there is no "property::name" signal emitted by already running clients.
-- the set delay has to be higher than the regular delay, otherwise a race condition between both signals exists.
awful.spawn.easy_async_with_shell("sleep " .. (delay + 0.5), function()
for _, c in ipairs(client.get()) do
if c and c.valid then
c:emit_signal("property::name")
end
end
end)
end
})