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