----------------------------------------------------- -- Helper to get icons from a program/program name -- ----------------------------------------------------- local gears = require("gears") local GLib = require("lgi").GLib local theme_index = require("src.tools.theme_index") local function get_basedir() local dirs = {} local dir = GLib.build_filenamev({ GLib.get_home_dir(), ".icons" }) if gears.filesystem.dir_readable(dir) then table.insert(dirs, dir) end dir = GLib.build_filenamev({ GLib.get_user_data_dir(), "icons" }) if gears.filesystem.dir_readable(dir) then table.insert(dirs, dir) end for _, value in ipairs(GLib.get_system_data_dirs()) do dir = GLib.build_filenamev({ value, "icons" }) if gears.filesystem.dir_readable(dir) then table.insert(dirs, dir) end end local need_usr_share_pixmaps = true for _, value in ipairs(GLib.get_system_data_dirs()) do dir = GLib.build_filenamev({ value, "icons" }) if gears.filesystem.dir_readable(dir) then table.insert(dirs, dir) end if dir == "/usr/share/pixmaps" then need_usr_share_pixmaps = false end end dir = "/usr/share/pixmaps" if gears.filesystem.dir_readable(dir) then table.insert(dirs, dir) end if need_usr_share_pixmaps and gears.filesystem.dir_readable(dir) then table.insert(dirs, dir) end return dirs end local xdg_icon_lookup = { mt = {} } local icon_cache = {} xdg_icon_lookup.new = function(theme, base_dirs) local self = {} self.icon_theme = theme or User_config.icon_theme self.base_directories = base_dirs or get_basedir() self.file_extension = { "svg", "png", "xpm" } if not icon_cache[self.icon_theme] then icon_cache[self.icon_theme] = {} end local cache_key = table.concat(self.base_directories, ":") if not icon_cache[self.icon_theme][cache_key] then icon_cache[self.icon_theme][cache_key] = theme_index(self.icon_theme, self.base_directories) end self.theme_index = icon_cache[self.icon_theme][cache_key] return setmetatable(self, { __index = xdg_icon_lookup }) end ---Look for an fallback icon ---@param iconname any ---@return string|nil nil local function lookup_fallback_icon(self, iconname) for _, dir in ipairs(self.base_directories) do for _, ext in ipairs(self.file_extension) do local filename = string.format("%s/%s.%s", dir, iconname, ext) if gears.filesystem.file_readable(filename) then return filename end end end return nil end ---Checkes if the size equals the actual icon size ---@param subdir any ---@param iconsize any ---@return boolean local function directory_matches_size(self, subdir, iconsize) local type, size, min_size, max_size, threshold = self.theme_index.per_directory_keys[subdir]["Type"], self.theme_index.per_directory_keys[subdir]["Size"], self.theme_index.per_directory_keys[subdir]["MinSize"], self.theme_index.per_directory_keys[subdir]["MaxSize"], self.theme_index.per_directory_keys[subdir]["Threshold"] if type == "Fixed" then return iconsize == size elseif type == "Scalable" then return iconsize >= min_size and iconsize <= max_size elseif type == "Threshold" then return iconsize >= size - threshold and iconsize <= size + threshold end return false end ---Returns how far off the size is from the actual icon size ---@param subdir table ---@param iconsize number ---@return number local function directory_size_distance(self, subdir, iconsize) local type, size, min_size, max_size, threshold = self.theme_index.per_directory_keys[subdir]["Type"], self.theme_index.per_directory_keys[subdir]["Size"], self.theme_index.per_directory_keys[subdir]["MinSize"], self.theme_index.per_directory_keys[subdir]["MaxSize"], self.theme_index.per_directory_keys[subdir]["Threshold"] if type and min_size and max_size and threshold then if type == "Fixed" then return math.abs(size - iconsize) elseif type == "Scalable" then if iconsize < min_size then return min_size - iconsize elseif iconsize > max_size then return iconsize - max_size end return 0 elseif type == "Threshold" then if iconsize < size - threshold then return min_size - iconsize elseif iconsize > size + threshold then return iconsize - max_size end return 0 end end return 0xffffffff end ---Checks each and every sub directory for an icon ---@param iconname any ---@param size any ---@return string|unknown|nil path_to_icon local function lookup_icon(self, iconname, size) local already_checked = {} for _, subdir in ipairs(self.theme_index:get_subdirectories()) do for _, dir in ipairs(self.base_directories) do for _, ext in ipairs(self.file_extension) do if directory_matches_size(self, subdir, size) then local filename = string.format("%s/%s/%s/%s.%s", dir, self.icon_theme, subdir, iconname, ext) if gears.filesystem.file_readable(filename) then return filename else already_checked[filename] = true end end end end end local min_size = 0xffffffff local closest_filename = nil for _, subdir in ipairs(self.theme_index:get_subdirectories()) do local dist = directory_size_distance(self, subdir, size) if dist < min_size then for _, dir in ipairs(self.base_directories) do for _, ext in ipairs(self.file_extension) do local filename = string.format("%s/%s/%s/%s.%s", dir, self.icon_theme, subdir, iconname, ext) if not already_checked[filename] then if gears.filesystem.file_readable(filename) then closest_filename = filename min_size = dist end end end end end end return closest_filename or nil end ---Check if the icon inherits from another icon theme and search that for an icon ---@param icon any ---@param size any ---@param self any ---@return string|unknown|nil path_to_icon local function find_icon_helper(self, icon, size) local filename = lookup_icon(self, icon, size) if filename then return filename end -- Exists purely for clients in hope to find a matching icon. filename = lookup_icon(self, icon:lower(), size) if filename then return filename end -- !Disabled for now until memory leak can be fixed. --[[ for _, parent in ipairs(self.theme_index:get_inherits()) do if parent == "hicolor" then return end filename = find_icon_helper(xdg_icon_lookup(parent, self.base_directories), icon, size) if filename then return filename end end ]] return nil end local iconcache = {} ---Takes an icon and its props and theme to search for it inside the theme ---@param icon any ---@param size any ---@return string|nil path_to_icon function xdg_icon_lookup:find_icon(icon, size) size = size or 64 if icon_cache[icon] == "" then return nil end if iconcache[icon] then return iconcache[icon] end if not icon or icon == "" then return nil end if gears.filesystem.file_readable(icon) then iconcache[icon] = icon return icon end local filename = find_icon_helper(self, icon, size) if filename then iconcache[icon] = filename return filename end filename = find_icon_helper(xdg_icon_lookup("hicolor", self.base_directories), icon, size) if filename then iconcache[icon] = filename return filename end filename = lookup_fallback_icon(self, icon) if filename then iconcache[icon] = filename return filename end iconcache[icon] = "" return nil end xdg_icon_lookup.mt.__call = function(_, ...) return xdg_icon_lookup.new(...) end return setmetatable(xdg_icon_lookup, xdg_icon_lookup.mt)