diff --git a/lib/menubar/init.lua b/lib/menubar/init.lua index cd1199a1b..1596cb346 100644 --- a/lib/menubar/init.lua +++ b/lib/menubar/init.lua @@ -232,9 +232,10 @@ local function menulist_update(query, scr) end --- Create the menubar wibox and widgets. -local function initialize() +-- @tparam[opt] screen scr Screen. +local function initialize(scr) instance.wibox = wibox({}) - instance.widget = menubar.get() + instance.widget = menubar.get(scr) instance.wibox.ontop = true instance.prompt = awful.widget.prompt() local layout = wibox.layout.fixed.horizontal() @@ -244,8 +245,12 @@ local function initialize() end --- Refresh menubar's cache by reloading .desktop files. -function menubar.refresh() - menubar.menu_entries = menubar.menu_gen.generate() +-- @tparam[opt] screen scr Screen. +function menubar.refresh(scr) + menubar.menu_gen.generate(function(entries) + menubar.menu_entries = entries + menulist_update(nil, scr) + end) end --- Awful.prompt keypressed callback to be used when the user presses a key. @@ -296,11 +301,11 @@ end -- @param scr Screen. function menubar.show(scr) if not instance.wibox then - initialize() + initialize(scr) elseif instance.wibox.visible then -- Menu already shown, exit return elseif not menubar.cache_entries then - menubar.refresh() + menubar.refresh(scr) end -- Set position and size @@ -337,9 +342,10 @@ function menubar.hide() end --- Get a menubar wibox. +-- @tparam[opt] screen scr Screen. -- @return menubar wibox. -function menubar.get() - menubar.refresh() +function menubar.get(scr) + menubar.refresh(scr) -- Add to each category the name of its key in all_categories for k, v in pairs(menubar.menu_gen.all_categories) do v.key = k @@ -347,7 +353,7 @@ function menubar.get() return common_args.w end -function menubar.mt:__call(...) +function menubar.mt.__call(_, ...) return menubar.get(...) end diff --git a/lib/menubar/menu_gen.lua b/lib/menubar/menu_gen.lua index 9b4203b88..3cb5bfc4a 100644 --- a/lib/menubar/menu_gen.lua +++ b/lib/menubar/menu_gen.lua @@ -83,48 +83,58 @@ local function trim(s) end --- Generate an array of all visible menu entries. --- @treturn table All menu entries. -function menu_gen.generate() +-- @tparam function callback Will be fired when all menu entries were parsed +-- with the resulting list of menu entries as argument. +-- @tparam table callback.entries All menu entries. +function menu_gen.generate(callback) -- Update icons for category entries menu_gen.lookup_category_icons() local result = {} local unique_entries = {} + local dirs_parsed = 0 + for _, dir in ipairs(menu_gen.all_menu_dirs) do - for _, entry in ipairs(utils.parse_dir(dir)) do - -- Check whether to include program in the menu - if entry.show and entry.Name and entry.cmdline then - local unique_key = entry.Name .. '\0' .. entry.cmdline - if not unique_entries[unique_key] then - local target_category = nil - -- Check if the program falls into at least one of the - -- usable categories. Set target_category to be the id - -- of the first category it finds. - if entry.categories then - for _, category in pairs(entry.categories) do - local cat_key, cat_use = - get_category_name_and_usage_by_type(category) - if cat_key and cat_use then - target_category = cat_key - break + utils.parse_dir(dir, function(entries) + entries = entries or {} + for _, entry in ipairs(entries) do + -- Check whether to include program in the menu + if entry.show and entry.Name and entry.cmdline then + local unique_key = entry.Name .. '\0' .. entry.cmdline + if not unique_entries[unique_key] then + local target_category = nil + -- Check if the program falls into at least one of the + -- usable categories. Set target_category to be the id + -- of the first category it finds. + if entry.categories then + for _, category in pairs(entry.categories) do + local cat_key, cat_use = + get_category_name_and_usage_by_type(category) + if cat_key and cat_use then + target_category = cat_key + break + end end end - end - if target_category then - local name = trim(entry.Name) or "" - local cmdline = trim(entry.cmdline) or "" - local icon = entry.icon_path or nil - table.insert(result, { name = name, - cmdline = cmdline, - icon = icon, - category = target_category }) - unique_entries[unique_key] = true + if target_category then + local name = trim(entry.Name) or "" + local cmdline = trim(entry.cmdline) or "" + local icon = entry.icon_path or nil + table.insert(result, { name = name, + cmdline = cmdline, + icon = icon, + category = target_category }) + unique_entries[unique_key] = true + end end end end - end + dirs_parsed = dirs_parsed + 1 + if dirs_parsed == #menu_gen.all_menu_dirs then + callback(result) + end + end) end - return result end return menu_gen diff --git a/lib/menubar/utils.lua b/lib/menubar/utils.lua index c61c5ff16..20d61803b 100644 --- a/lib/menubar/utils.lua +++ b/lib/menubar/utils.lua @@ -15,8 +15,11 @@ local string = string local screen = screen local awful_util = require("awful.util") local theme = require("beautiful") -local glib = require("lgi").GLib +local lgi = require("lgi") +local gio = lgi.Gio +local glib = lgi.GLib local wibox = require("wibox") +local debug = require("gears.debug") local utils = {} @@ -242,18 +245,51 @@ function utils.parse_desktop_file(file) end --- Parse a directory with .desktop files recursively. --- @tparam string dir The directory. --- @treturn table Paths of found .desktop files. -function utils.parse_dir(dir) - local programs = {} - local files = io.popen('find '.. dir .." -name '*.desktop' 2>/dev/null") - for file in files:lines() do - local program = utils.parse_desktop_file(file) - if program then - table.insert(programs, program) +-- @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 enum, err = f:async_enumerate_children("standard::name", 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 - return programs + + gio.Async.start(function() + local result = {} + parser(dir_path, result) + callback(result) + end)() end --- Compute textbox width.