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
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
|
---------------------------------------------------------------------------
--- Utility module for menubar
--
-- @author Antonio Terceiro
-- @copyright 2009, 2011-2012 Antonio Terceiro, Alexander Yakushev
-- @module menubar.utils
---------------------------------------------------------------------------
-- Grab environment
local io = io
local table = table
local ipairs = ipairs
local string = string
local screen = screen
local awful_util = require("awful.util")
local theme = require("beautiful")
local lgi = require("lgi")
local gio = lgi.Gio
local glib = lgi.GLib
local wibox = require("wibox")
local debug = require("gears.debug")
local protected_call = require("gears.protected_call")
local utils = {}
-- NOTE: This icons/desktop files module was written according to the
-- following freedesktop.org specifications:
-- Icons: http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-0.11.html
-- Desktop files: http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.0.html
-- Options section
--- Terminal which applications that need terminal would open in.
utils.terminal = 'xterm'
--- The default icon for applications that don't provide any icon in
-- their .desktop files.
local default_icon = nil
--- Name of the WM for the OnlyShownIn entry in the .desktop file.
utils.wm_name = "awesome"
-- Private section
local all_icon_sizes = {
'128x128' ,
'96x96',
'72x72',
'64x64',
'48x48',
'36x36',
'32x32',
'24x24',
'22x22',
'16x16'
}
--- List of supported icon formats.
local icon_formats = { "png", "xpm", "svg" }
--- Check whether the icon format is supported.
-- @param icon_file Filename of the icon.
-- @return true if format is supported, false otherwise.
local function is_format_supported(icon_file)
for _, f in ipairs(icon_formats) do
if icon_file:match('%.' .. f) then
return true
end
end
return false
end
local icon_lookup_path = nil
--- Get a list of icon lookup paths.
-- @treturn table A list of directories, without trailing slash.
local function get_icon_lookup_path()
if not icon_lookup_path then
local add_if_readable = function(t, path)
if awful_util.dir_readable(path) then
table.insert(t, path)
end
end
icon_lookup_path = {}
local icon_theme_paths = {}
local icon_theme = theme.icon_theme
local paths = glib.get_system_data_dirs()
table.insert(paths, 1, glib.get_user_data_dir())
table.insert(paths, 1, glib.build_filenamev({glib.get_home_dir(),
'.icons'}))
for _,dir in ipairs(paths) do
local icons_dir = glib.build_filenamev({dir, 'icons'})
if awful_util.dir_readable(icons_dir) then
if icon_theme then
add_if_readable(icon_theme_paths,
glib.build_filenamev({icons_dir,
icon_theme}))
end
-- Fallback theme.
add_if_readable(icon_theme_paths,
glib.build_filenamev({icons_dir, 'hicolor'}))
end
end
for _, icon_theme_directory in ipairs(icon_theme_paths) do
for _, size in ipairs(all_icon_sizes) do
add_if_readable(icon_lookup_path,
glib.build_filenamev({icon_theme_directory,
size, 'apps'}))
end
end
for _,dir in ipairs(paths)do
-- lowest priority fallbacks
add_if_readable(icon_lookup_path,
glib.build_filenamev({dir, 'pixmaps'}))
add_if_readable(icon_lookup_path,
glib.build_filenamev({dir, 'icons'}))
end
end
return icon_lookup_path
end
--- Lookup an icon in different folders of the filesystem.
-- @tparam string icon_file Short or full name of the icon.
-- @treturn string|boolean Full name of the icon, or false on failure.
function utils.lookup_icon_uncached(icon_file)
if not icon_file or icon_file == "" then
return false
end
if icon_file:sub(1, 1) == '/' and is_format_supported(icon_file) then
-- If the path to the icon is absolute and its format is
-- supported, do not perform a lookup.
return awful_util.file_readable(icon_file) and icon_file or nil
else
for _, directory in ipairs(get_icon_lookup_path()) do
if is_format_supported(icon_file) and
awful_util.file_readable(directory .. "/" .. icon_file) then
return directory .. "/" .. icon_file
else
-- Icon is probably specified without path and format,
-- like 'firefox'. Try to add supported extensions to
-- it and see if such file exists.
for _, format in ipairs(icon_formats) do
local possible_file = directory .. "/" .. icon_file .. "." .. format
if awful_util.file_readable(possible_file) then
return possible_file
end
end
end
end
return false
end
end
local lookup_icon_cache = {}
--- Lookup an icon in different folders of the filesystem (cached).
-- @param icon Short or full name of the icon.
-- @return full name of the icon.
function utils.lookup_icon(icon)
if not lookup_icon_cache[icon] and lookup_icon_cache[icon] ~= false then
lookup_icon_cache[icon] = utils.lookup_icon_uncached(icon)
end
return lookup_icon_cache[icon] or default_icon
end
--- Parse a .desktop file.
-- @param file The .desktop file.
-- @return A table with file entries.
function utils.parse_desktop_file(file)
local program = { show = true, file = file }
local desktop_entry = false
-- Parse the .desktop file.
-- We are interested in [Desktop Entry] group only.
for line in io.lines(file) do
if line:find("^%s*#") then
-- Skip comments.
(function() end)() -- I haven't found a nice way to silence luacheck here
elseif not desktop_entry and line == "[Desktop Entry]" then
desktop_entry = true
else
if line:sub(1, 1) == "[" and line:sub(-1) == "]" then
-- A declaration of new group - stop parsing
break
end
-- Grab the values
for key, value in line:gmatch("(%w+)%s*=%s*(.+)") do
program[key] = value
end
end
end
-- In case [Desktop Entry] was not found
if not desktop_entry then return nil end
-- In case the (required) 'Name' entry was not found
if not program.Name or program.Name == '' then return nil end
-- Don't show program if NoDisplay attribute is false
if program.NoDisplay and string.lower(program.NoDisplay) == "true" then
program.show = false
end
-- Only show the program if there is no OnlyShowIn attribute
-- or if it's equal to utils.wm_name
if program.OnlyShowIn ~= nil and not program.OnlyShowIn:match(utils.wm_name) then
program.show = false
end
-- Look up for a icon.
if program.Icon then
program.icon_path = utils.lookup_icon(program.Icon)
end
-- Split categories into a table. Categories are written in one
-- line separated by semicolon.
if program.Categories then
program.categories = {}
for category in program.Categories:gmatch('[^;]+') do
table.insert(program.categories, category)
end
end
if program.Exec then
-- Substitute Exec special codes as specified in
-- http://standards.freedesktop.org/desktop-entry-spec/1.1/ar01s06.html
if program.Name == nil then
program.Name = '['.. file:match("([^/]+)%.desktop$") ..']'
end
local cmdline = program.Exec:gsub('%%c', program.Name)
cmdline = cmdline:gsub('%%[fuFU]', '')
cmdline = cmdline:gsub('%%k', program.file)
if program.icon_path then
cmdline = cmdline:gsub('%%i', '--icon ' .. program.icon_path)
else
cmdline = cmdline:gsub('%%i', '')
end
if program.Terminal == "true" then
cmdline = utils.terminal .. ' -e ' .. cmdline
end
program.cmdline = cmdline
end
return program
end
--- Parse a directory with .desktop files recursively.
-- @tparam string dir_path The directory path.
-- @tparam function callback Will be fired when all the files were parsed
-- with the resulting list of menu entries as argument.
-- @tparam table callback.programs Paths of found .desktop files.
function utils.parse_dir(dir_path, callback)
local function parser(dir, programs)
local f = gio.File.new_for_path(dir)
-- Except for "NONE" there is also NOFOLLOW_SYMLINKS
local query = gio.FILE_ATTRIBUTE_STANDARD_NAME .. "," .. gio.FILE_ATTRIBUTE_STANDARD_TYPE
local enum, err = f:async_enumerate_children(query, gio.FileQueryInfoFlags.NONE)
if not enum then
debug.print_error(err)
return
end
local files_per_call = 100 -- Actual value is not that important
while true do
local list, enum_err = enum:async_next_files(files_per_call)
if enum_err then
debug.print_error(enum_err)
return
end
for _, info in ipairs(list) do
local file_type = info:get_file_type()
local file_path = enum:get_child(info):get_path()
if file_type == 'REGULAR' then
local program = utils.parse_desktop_file(file_path)
if program then
table.insert(programs, program)
end
elseif file_type == 'DIRECTORY' then
parser(file_path, programs)
end
end
if #list == 0 then
break
end
end
enum:async_close()
end
gio.Async.start(function()
local result = {}
parser(dir_path, result)
protected_call.call(callback, result)
end)()
end
--- Compute textbox width.
-- @tparam wibox.widget.textbox textbox Textbox instance.
-- @tparam number|screen s Screen
-- @treturn int Text width.
function utils.compute_textbox_width(textbox, s)
s = screen[s or mouse.screen]
local w, _ = textbox:get_preferred_size(s)
return w
end
--- Compute text width.
-- @tparam str text Text.
-- @tparam number|screen s Screen
-- @treturn int Text width.
function utils.compute_text_width(text, s)
return utils.compute_textbox_width(wibox.widget.textbox(awful_util.escape(text)), s)
end
return utils
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
|