2015-09-21 10:17:20 +02:00
|
|
|
---------------------------------------------------------------------------
|
|
|
|
--- Class module for icon lookup for menubar
|
|
|
|
--
|
|
|
|
-- @author Kazunobu Kuriyama
|
|
|
|
-- @copyright 2015 Kazunobu Kuriyama
|
|
|
|
-- @release @AWESOME_VERSION@
|
|
|
|
-- @classmod menubar.icon_theme
|
|
|
|
---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
-- This implementation is based on the specifications:
|
|
|
|
-- Icon Theme Specification 0.12
|
|
|
|
-- http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-0.12.html
|
|
|
|
|
|
|
|
local beautiful = require("beautiful")
|
|
|
|
local awful = require("awful")
|
|
|
|
local GLib = require("lgi").GLib
|
|
|
|
local Gio = require("lgi").Gio
|
|
|
|
local index_theme = require("menubar.index_theme")
|
|
|
|
|
|
|
|
local ipairs = ipairs
|
|
|
|
local setmetatable = setmetatable
|
|
|
|
local string = string
|
|
|
|
local table = table
|
|
|
|
local math = math
|
|
|
|
local io = io
|
|
|
|
|
|
|
|
-- Returns a table whose element is a path used for icon lookup.
|
|
|
|
-- The names of the directories and the order of them are based on the spec.
|
|
|
|
local get_default_base_directories = function()
|
|
|
|
local dirs = {}
|
|
|
|
|
|
|
|
table.insert(dirs, GLib.get_home_dir() .. "/.icons")
|
|
|
|
for _, dir in ipairs(GLib.get_system_data_dirs()) do
|
|
|
|
table.insert(dirs, dir .. "/icons")
|
|
|
|
end
|
|
|
|
table.insert(dirs, "/usr/share/pixmaps")
|
|
|
|
|
|
|
|
return dirs
|
|
|
|
end
|
|
|
|
|
|
|
|
local is_readable_directory = function(path)
|
|
|
|
local gfile = Gio.File.new_for_path(path)
|
|
|
|
local gfileinfo = gfile:query_info("standard::type,access::can-read",
|
|
|
|
Gio.FileQueryInfoFlags.NONE)
|
|
|
|
if not gfileinfo then return false end -- practically ENOENT
|
|
|
|
local is_dir = (gfileinfo:get_file_type() == "DIRECTORY")
|
|
|
|
local is_readable = gfileinfo:get_attribute_boolean("access::can-read")
|
|
|
|
return is_dir and is_readable
|
|
|
|
end
|
|
|
|
|
|
|
|
local get_pragmatic_base_directories = function()
|
|
|
|
local dirs = {}
|
|
|
|
|
2015-10-11 12:35:14 +02:00
|
|
|
local dir = GLib.build_filenamev({GLib.get_home_dir(), ".icons"})
|
2015-09-21 10:17:20 +02:00
|
|
|
if is_readable_directory(dir) then
|
|
|
|
table.insert(dirs, dir)
|
|
|
|
end
|
|
|
|
|
2015-10-11 12:35:14 +02:00
|
|
|
dir = GLib.build_filenamev({GLib.get_user_data_dir(), "icons"})
|
2015-09-21 10:17:20 +02:00
|
|
|
if is_readable_directory(dir) then
|
|
|
|
table.insert(dirs, dir)
|
|
|
|
end
|
|
|
|
|
|
|
|
for _, v in ipairs(GLib.get_system_data_dirs()) do
|
2015-10-11 12:35:14 +02:00
|
|
|
dir = GLib.build_filenamev({v, "icons"})
|
2015-09-21 10:17:20 +02:00
|
|
|
if is_readable_directory(dir) then
|
|
|
|
table.insert(dirs, dir)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local need_usr_share_pixmaps = true
|
|
|
|
for _, v in ipairs(GLib.get_system_data_dirs()) do
|
2015-10-11 12:35:14 +02:00
|
|
|
dir = GLib.build_filenamev({v, "pixmaps"})
|
2015-09-21 10:17:20 +02:00
|
|
|
if is_readable_directory(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 need_usr_share_pixmaps and is_readable_directory(dir) then
|
|
|
|
table.insert(dirs, dir)
|
|
|
|
end
|
|
|
|
|
|
|
|
return dirs
|
|
|
|
end
|
|
|
|
|
|
|
|
local get_default_icon_theme_name = function()
|
|
|
|
local icon_theme_names = { "Adwaita", "gnome", "hicolor" }
|
|
|
|
for _, dir in ipairs(get_pragmatic_base_directories()) do
|
|
|
|
for _, icon_theme_name in ipairs(icon_theme_names) do
|
|
|
|
local filename = string.format("%s/%s/index.theme", dir, icon_theme_name)
|
|
|
|
if awful.util.file_readable(filename) then
|
|
|
|
return icon_theme_name
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
local icon_theme = { mt = {} }
|
|
|
|
|
|
|
|
--- Class constructor of `icon_theme`
|
|
|
|
-- @tparam string icon_theme_name Internal name of icon theme
|
|
|
|
-- @tparam table base_directories Paths used for lookup
|
|
|
|
-- @treturn table An instance of the class `icon_theme`
|
2015-10-11 11:54:15 +02:00
|
|
|
icon_theme.new = function(icon_theme_name, base_directories)
|
2015-09-21 10:17:20 +02:00
|
|
|
local icon_theme_name = icon_theme_name or beautiful.icon_theme or get_default_icon_theme_name()
|
|
|
|
local base_directories = base_directories or get_pragmatic_base_directories()
|
|
|
|
|
|
|
|
local self = {}
|
|
|
|
self.icon_theme_name = icon_theme_name
|
|
|
|
self.base_directories = base_directories
|
|
|
|
self.extensions = { "png", "svg", "xpm" }
|
|
|
|
self.index_theme = index_theme(self.icon_theme_name, self.base_directories)
|
|
|
|
|
2015-10-11 11:54:15 +02:00
|
|
|
return setmetatable(self, { __index = icon_theme })
|
2015-09-21 10:17:20 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
local directory_matches_size = function(self, subdirectory, icon_size)
|
|
|
|
local kind, size, min_size, max_size, threshold = self.index_theme:get_per_directory_keys(subdirectory)
|
|
|
|
|
|
|
|
if kind == "Fixed" then
|
|
|
|
return icon_size == size
|
|
|
|
elseif kind == "Scaled" then
|
|
|
|
return icon_size >= min_size and icon_size <= max_size
|
|
|
|
elseif kind == "Threshold" then
|
|
|
|
return icon_size >= size - threshold and icon_size <= size + threshold
|
|
|
|
end
|
|
|
|
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
local directory_size_distance = function(self, subdirectory, icon_size)
|
|
|
|
local kind, size, min_size, max_size, threshold = self.index_theme:get_per_directory_keys(subdirectory)
|
|
|
|
|
|
|
|
if kind == "Fixed" then
|
|
|
|
return math.abs(icon_size - size)
|
|
|
|
elseif kind == "Scaled" then
|
|
|
|
if icon_size < min_size then
|
|
|
|
return min_size - icon_size
|
|
|
|
elseif icon_size > max_size then
|
|
|
|
return icon_size - max_size
|
|
|
|
end
|
|
|
|
return 0
|
|
|
|
elseif kind == "Threshold" then
|
|
|
|
if icon_size < size - threshold then
|
|
|
|
return min_size - icon_size
|
|
|
|
elseif icon_size > size + threshold then
|
|
|
|
return icon_size - max_size
|
|
|
|
end
|
|
|
|
return 0
|
|
|
|
end
|
|
|
|
|
|
|
|
return 0xffffffff -- Any large number will do.
|
|
|
|
end
|
|
|
|
|
|
|
|
local lookup_icon = function(self, icon_name, icon_size)
|
|
|
|
for _, subdir in ipairs(self.index_theme:get_subdirectories()) do
|
|
|
|
for _, basedir in ipairs(self.base_directories) do
|
|
|
|
for _, ext in ipairs(self.extensions) do
|
|
|
|
if directory_matches_size(self, subdir, icon_size) then
|
|
|
|
local filename = string.format("%s/%s/%s/%s.%s",
|
|
|
|
basedir, self.icon_theme_name, subdir,
|
|
|
|
icon_name, ext)
|
|
|
|
if awful.util.file_readable(filename) then
|
|
|
|
return filename
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local minimal_size = 0xffffffff -- Any large number will do.
|
|
|
|
local closest_filename = nil
|
|
|
|
for _, subdir in ipairs(self.index_theme:get_subdirectories()) do
|
|
|
|
for _, basedir in ipairs(self.base_directories) do
|
|
|
|
for _, ext in ipairs(self.extensions) do
|
|
|
|
local filename = string.format("%s/%s/%s/%s.%s",
|
|
|
|
basedir, self.icon_theme_name, subdir,
|
|
|
|
icon_name, ext)
|
|
|
|
local dist = directory_size_distance(self, subdir, icon_size)
|
|
|
|
if awful.util.file_readable(filename) and dist < minimal_size then
|
|
|
|
closest_filename = filename
|
|
|
|
minimal_size = dist
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if closest_filename then
|
|
|
|
return closest_filename
|
|
|
|
end
|
|
|
|
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
2015-10-11 11:54:15 +02:00
|
|
|
local find_icon_path_helper = function(self, icon_name, icon_size)
|
2015-09-21 10:17:20 +02:00
|
|
|
local filename = lookup_icon(self, icon_name, icon_size)
|
|
|
|
if filename then
|
|
|
|
return filename
|
|
|
|
end
|
|
|
|
|
|
|
|
for _, parent in ipairs(self.index_theme:get_inherits()) do
|
|
|
|
local parent_icon_theme = icon_theme(parent, self.base_directories)
|
|
|
|
filename = find_icon_path_helper(parent_icon_theme, icon_name, icon_size)
|
|
|
|
if filename then
|
|
|
|
return filename
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
local lookup_fallback_icon = function(self, icon_name)
|
|
|
|
for _, dir in ipairs(self.base_directories) do
|
|
|
|
for _, ext in ipairs(self.extensions) do
|
|
|
|
local filename = string.format("%s/%s.%s",
|
|
|
|
dir,
|
|
|
|
icon_name, ext)
|
|
|
|
if awful.util.file_readable(filename) then
|
|
|
|
return filename
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
--- Look up an image file based on a given icon name and/or a preferable size.
|
|
|
|
-- @tparam string icon_name Icon name to be looked up
|
|
|
|
-- @tparam number icon_size Prefereable icon size
|
|
|
|
-- @treturn string Absolute path to the icon file, or nil if not found
|
|
|
|
icon_theme.find_icon_path = function(self, icon_name, icon_size)
|
|
|
|
if not icon_name or icon_name == "" then
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
local icon_size = icon_size or 16
|
|
|
|
|
|
|
|
local filename = find_icon_path_helper(self, icon_name, icon_size)
|
|
|
|
if filename then
|
|
|
|
return filename
|
|
|
|
end
|
|
|
|
|
|
|
|
if self.icon_theme_name ~= "hicolor" then
|
2015-10-11 11:53:31 +02:00
|
|
|
filename = find_icon_path_helper(icon_theme("hicolor", self.base_directories), icon_name, icon_size)
|
2015-09-21 10:17:20 +02:00
|
|
|
if filename then
|
|
|
|
return filename
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return lookup_fallback_icon(self, icon_name)
|
|
|
|
end
|
|
|
|
|
2015-10-11 11:54:15 +02:00
|
|
|
icon_theme.mt.__call = function(_, ...)
|
|
|
|
return icon_theme.new(...)
|
2015-09-21 10:17:20 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
return setmetatable(icon_theme, icon_theme.mt)
|
|
|
|
|
|
|
|
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
|