summaryrefslogtreecommitdiff
path: root/lib/menubar/utils.lua
diff options
context:
space:
mode:
Diffstat (limited to 'lib/menubar/utils.lua')
-rw-r--r--lib/menubar/utils.lua316
1 files changed, 316 insertions, 0 deletions
diff --git a/lib/menubar/utils.lua b/lib/menubar/utils.lua
new file mode 100644
index 0000000..6f80e86
--- /dev/null
+++ b/lib/menubar/utils.lua
@@ -0,0 +1,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