1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
|
---------------------------------------------------------------------------
--- Class module for icon lookup for menubar
--
-- @author Kazunobu Kuriyama
-- @copyright 2015 Kazunobu Kuriyama
-- @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_util = require("awful.util")
local GLib = require("lgi").GLib
local index_theme = require("menubar.index_theme")
local ipairs = ipairs
local setmetatable = setmetatable
local string = string
local table = table
local math = math
local get_pragmatic_base_directories = function()
local dirs = {}
local dir = GLib.build_filenamev({GLib.get_home_dir(), ".icons"})
if awful_util.dir_readable(dir) then
table.insert(dirs, dir)
end
dir = GLib.build_filenamev({GLib.get_user_data_dir(), "icons"})
if awful_util.dir_readable(dir) then
table.insert(dirs, dir)
end
for _, v in ipairs(GLib.get_system_data_dirs()) do
dir = GLib.build_filenamev({v, "icons"})
if awful_util.dir_readable(dir) then
table.insert(dirs, dir)
end
end
local need_usr_share_pixmaps = true
for _, v in ipairs(GLib.get_system_data_dirs()) do
dir = GLib.build_filenamev({v, "pixmaps"})
if awful_util.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 need_usr_share_pixmaps and awful_util.dir_readable(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 = {} }
local index_theme_cache = {}
--- 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`
icon_theme.new = function(icon_theme_name, base_directories)
icon_theme_name = icon_theme_name or beautiful.icon_theme or get_default_icon_theme_name()
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" }
-- Instantiate index_theme (cached).
if not index_theme_cache[self.icon_theme_name] then
index_theme_cache[self.icon_theme_name] = {}
end
local cache_key = table.concat(self.base_directories, ':')
if not index_theme_cache[self.icon_theme_name][cache_key] then
index_theme_cache[self.icon_theme_name][cache_key] = index_theme(
self.icon_theme_name,
self.base_directories)
end
self.index_theme = index_theme_cache[self.icon_theme_name][cache_key]
return setmetatable(self, { __index = icon_theme })
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 == "Scalable" 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 == "Scalable" 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)
local checked_already = {}
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
else
checked_already[filename] = true
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
local dist = directory_size_distance(self, subdir, icon_size)
if dist < minimal_size then
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)
if not checked_already[filename] then
if awful_util.file_readable(filename) then
closest_filename = filename
minimal_size = dist
end
end
end
end
end
end
return closest_filename
end
local find_icon_path_helper -- Gets called recursively.
find_icon_path_helper = function(self, icon_name, icon_size)
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
function icon_theme:find_icon_path(icon_name, icon_size)
icon_size = icon_size or 16
if not icon_name or icon_name == "" then
return nil
end
local filename = find_icon_path_helper(self, icon_name, icon_size)
if filename then
return filename
end
if self.icon_theme_name ~= "hicolor" then
filename = find_icon_path_helper(icon_theme("hicolor", self.base_directories), icon_name, icon_size)
if filename then
return filename
end
end
return lookup_fallback_icon(self, icon_name)
end
icon_theme.mt.__call = function(_, ...)
return icon_theme.new(...)
end
return setmetatable(icon_theme, icon_theme.mt)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
|