diff options
Diffstat (limited to 'awesome/lib/menubar/init.lua')
-rw-r--r-- | awesome/lib/menubar/init.lua | 480 |
1 files changed, 480 insertions, 0 deletions
diff --git a/awesome/lib/menubar/init.lua b/awesome/lib/menubar/init.lua new file mode 100644 index 0000000..10ad65c --- /dev/null +++ b/awesome/lib/menubar/init.lua @@ -0,0 +1,480 @@ +--------------------------------------------------------------------------- +--- Menubar module, which aims to provide a freedesktop menu alternative +-- +-- List of menubar keybindings: +-- --- +-- +-- * "Left" | "C-j" select an item on the left +-- * "Right" | "C-k" select an item on the right +-- * "Backspace" exit the current category if we are in any +-- * "Escape" exit the current directory or exit menubar +-- * "Home" select the first item +-- * "End" select the last +-- * "Return" execute the entry +-- * "C-Return" execute the command with awful.spawn +-- * "C-M-Return" execute the command in a terminal +-- +-- @author Alexander Yakushev <yakushev.alex@gmail.com> +-- @copyright 2011-2012 Alexander Yakushev +-- @module menubar +--------------------------------------------------------------------------- + +-- Grab environment we need +local capi = { + client = client, + mouse = mouse, + screen = screen +} +local awful = require("awful") +local common = require("awful.widget.common") +local theme = require("beautiful") +local wibox = require("wibox") + +local function get_screen(s) + return s and capi.screen[s] +end + +-- menubar +local menubar = { mt = {}, menu_entries = {} } +menubar.menu_gen = require("menubar.menu_gen") +menubar.utils = require("menubar.utils") +local compute_text_width = menubar.utils.compute_text_width + +-- Options section + +--- When true the .desktop files will be reparsed only when the +-- extension is initialized. Use this if menubar takes much time to +-- open. +-- @tfield[opt=true] boolean cache_entries +menubar.cache_entries = true + +--- When true the categories will be shown alongside application +-- entries. +-- @tfield[opt=true] boolean show_categories +menubar.show_categories = true + +--- Specifies the geometry of the menubar. This is a table with the keys +-- x, y, width and height. Missing values are replaced via the screen's +-- geometry. However, missing height is replaced by the font size. +-- @table geometry +-- @tfield number geometry.x A forced horizontal position +-- @tfield number geometry.y A forced vertical position +-- @tfield number geometry.width A forced width +-- @tfield number geometry.height A forced height +menubar.geometry = { width = nil, + height = nil, + x = nil, + y = nil } + +--- Width of blank space left in the right side. +-- @tfield number right_margin +menubar.right_margin = theme.xresources.apply_dpi(8) + +--- Label used for "Next page", default "▶▶". +-- @tfield[opt="▶▶"] string right_label +menubar.right_label = "▶▶" + +--- Label used for "Previous page", default "◀◀". +-- @tfield[opt="◀◀"] string left_label +menubar.left_label = "◀◀" + +-- awful.widget.common.list_update adds three times a margin of dpi(4) +-- for each item: +-- @tfield number list_interspace +local list_interspace = theme.xresources.apply_dpi(4) * 3 + +--- Allows user to specify custom parameters for prompt.run function +-- (like colors). +-- @see awful.prompt +menubar.prompt_args = {} + +-- Private section +local current_item = 1 +local previous_item = nil +local current_category = nil +local shownitems = nil +local instance = { prompt = nil, + widget = nil, + wibox = nil } + +local common_args = { w = wibox.layout.fixed.horizontal(), + data = setmetatable({}, { __mode = 'kv' }) } + +--- Wrap the text with the color span tag. +-- @param s The text. +-- @param c The desired text color. +-- @return the text wrapped in a span tag. +local function colortext(s, c) + return "<span color='" .. awful.util.ensure_pango_color(c) .. "'>" .. s .. "</span>" +end + +--- Get how the menu item should be displayed. +-- @param o The menu item. +-- @return item name, item background color, background image, item icon. +local function label(o) + if o.focused then + return colortext(o.name, (theme.menu_fg_focus or theme.fg_focus)), (theme.menu_bg_focus or theme.bg_focus), nil, o.icon + else + return o.name, (theme.menu_bg_normal or theme.bg_normal), nil, o.icon + end +end + +local function load_count_table() + local count_file_name = awful.util.getdir("cache") .. "/menu_count_file" + + local count_file = io.open (count_file_name, "r") + local count_table = {} + + -- read weight file + if count_file then + io.input (count_file) + for line in io.lines() do + local name, count = string.match(line, "([^;]+);([^;]+)") + if name ~= nil and count ~= nil then + count_table[name] = count + end + end + end + + return count_table +end + +local function write_count_table(count_table) + local count_file_name = awful.util.getdir("cache") .. "/menu_count_file" + + local count_file = io.open (count_file_name, "w") + + if count_file then + io.output (count_file) + + for name, count in pairs(count_table) do + local str = string.format("%s;%d\n", name, count) + io.write(str) + end + io.flush() + end +end + +--- Perform an action for the given menu item. +-- @param o The menu item. +-- @return if the function processed the callback, new awful.prompt command, new awful.prompt prompt text. +local function perform_action(o) + if not o then return end + if o.key then + current_category = o.key + local new_prompt = shownitems[current_item].name .. ": " + previous_item = current_item + current_item = 1 + return true, "", new_prompt + elseif shownitems[current_item].cmdline then + awful.spawn(shownitems[current_item].cmdline) + + -- load count_table from cache file + local count_table = load_count_table() + + -- increase count + local curname = shownitems[current_item].name + if count_table[curname] ~= nil then + count_table[curname] = count_table[curname] + 1 + else + count_table[curname] = 1 + end + + -- write updated count table to cache file + write_count_table(count_table) + + -- Let awful.prompt execute dummy exec_callback and + -- done_callback to stop the keygrabber properly. + return false + end +end + +-- Cut item list to return only current page. +-- @tparam table all_items All items list. +-- @tparam str query Search query. +-- @tparam number|screen scr Screen +-- @return table List of items for current page. +local function get_current_page(all_items, query, scr) + scr = get_screen(scr) + if not instance.prompt.width then + instance.prompt.width = compute_text_width(instance.prompt.prompt, scr) + end + if not menubar.left_label_width then + menubar.left_label_width = compute_text_width(menubar.left_label, scr) + end + if not menubar.right_label_width then + menubar.right_label_width = compute_text_width(menubar.right_label, scr) + end + local available_space = instance.geometry.width - menubar.right_margin - + menubar.right_label_width - menubar.left_label_width - + compute_text_width(query, scr) - instance.prompt.width + + local width_sum = 0 + local current_page = {} + for i, item in ipairs(all_items) do + item.width = item.width or + compute_text_width(item.name, scr) + + (item.icon and instance.geometry.height or 0) + list_interspace + if width_sum + item.width > available_space then + if current_item < i then + table.insert(current_page, { name = menubar.right_label, icon = nil }) + break + end + current_page = { { name = menubar.left_label, icon = nil }, item, } + width_sum = item.width + else + table.insert(current_page, item) + width_sum = width_sum + item.width + end + end + return current_page +end + +--- Update the menubar according to the command entered by user. +-- @tparam str query Search query. +-- @tparam number|screen scr Screen +local function menulist_update(query, scr) + query = query or "" + shownitems = {} + local pattern = awful.util.query_to_pattern(query) + + -- All entries are added to a list that will be sorted + -- according to the priority (first) and weight (second) of its + -- entries. + -- If categories are used in the menu, we add the entries matching + -- the current query with high priority as to ensure they are + -- displayed first. Afterwards the non-category entries are added. + -- All entries are weighted according to the number of times they + -- have been executed previously (stored in count_table). + + local count_table = load_count_table() + local command_list = {} + + local PRIO_NONE = 0 + local PRIO_CATEGORY_MATCH = 2 + + -- Add the categories + if menubar.show_categories then + for _, v in pairs(menubar.menu_gen.all_categories) do + v.focused = false + if not current_category and v.use then + + -- check if current query matches a category + if string.match(v.name, pattern) then + + v.weight = 0 + v.prio = PRIO_CATEGORY_MATCH + + -- get use count from count_table if present + -- and use it as weight + if string.len(pattern) > 0 and count_table[v.name] ~= nil then + v.weight = tonumber(count_table[v.name]) + end + + -- check for prefix match + if string.match(v.name, "^" .. pattern) then + -- increase default priority + v.prio = PRIO_CATEGORY_MATCH + 1 + else + v.prio = PRIO_CATEGORY_MATCH + end + + table.insert (command_list, v) + end + end + end + end + + -- Add the applications according to their name and cmdline + for _, v in ipairs(menubar.menu_entries) do + v.focused = false + if not current_category or v.category == current_category then + + -- check if the query matches either the name or the commandline + -- of some entry + if string.match(v.name, pattern) + or string.match(v.cmdline, pattern) then + + v.weight = 0 + v.prio = PRIO_NONE + + -- get use count from count_table if present + -- and use it as weight + if string.len(pattern) > 0 and count_table[v.name] ~= nil then + v.weight = tonumber(count_table[v.name]) + end + + -- check for prefix match + if string.match(v.name, "^" .. pattern) + or string.match(v.cmdline, "^" .. pattern) then + -- increase default priority + v.prio = PRIO_NONE + 1 + else + v.prio = PRIO_NONE + end + + table.insert (command_list, v) + end + end + end + + local function compare_counts(a, b) + if a.prio == b.prio then + return a.weight > b.weight + end + return a.prio > b.prio + end + + -- sort command_list by weight (highest first) + table.sort(command_list, compare_counts) + -- copy into showitems + shownitems = command_list + + if #shownitems > 0 then + -- Insert a run item value as the last choice + table.insert(shownitems, { name = "Exec: " .. query, cmdline = query, icon = nil }) + + if current_item > #shownitems then + current_item = #shownitems + end + shownitems[current_item].focused = true + else + table.insert(shownitems, { name = "", cmdline = query, icon = nil }) + end + + common.list_update(common_args.w, nil, label, + common_args.data, + get_current_page(shownitems, query, scr)) +end + +--- Create the menubar wibox and widgets. +-- @tparam[opt] screen scr Screen. +local function initialize(scr) + instance.wibox = wibox({}) + instance.widget = menubar.get(scr) + instance.wibox.ontop = true + instance.prompt = awful.widget.prompt() + local layout = wibox.layout.fixed.horizontal() + layout:add(instance.prompt) + layout:add(instance.widget) + instance.wibox:set_widget(layout) +end + +--- Refresh menubar's cache by reloading .desktop files. +-- @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. +-- @param mod Table of key combination modifiers (Control, Shift). +-- @param key The key that was pressed. +-- @param comm The current command in the prompt. +-- @return if the function processed the callback, new awful.prompt command, new awful.prompt prompt text. +local function prompt_keypressed_callback(mod, key, comm) + if key == "Left" or (mod.Control and key == "j") then + current_item = math.max(current_item - 1, 1) + return true + elseif key == "Right" or (mod.Control and key == "k") then + current_item = current_item + 1 + return true + elseif key == "BackSpace" then + if comm == "" and current_category then + current_category = nil + current_item = previous_item + return true, nil, "Run: " + end + elseif key == "Escape" then + if current_category then + current_category = nil + current_item = previous_item + return true, nil, "Run: " + end + elseif key == "Home" then + current_item = 1 + return true + elseif key == "End" then + current_item = #shownitems + return true + elseif key == "Return" or key == "KP_Enter" then + if mod.Control then + current_item = #shownitems + if mod.Mod1 then + -- add a terminal to the cmdline + shownitems[current_item].cmdline = menubar.utils.terminal + .. " -e " .. shownitems[current_item].cmdline + end + end + return perform_action(shownitems[current_item]) + end + return false +end + +--- Show the menubar on the given screen. +-- @param scr Screen. +function menubar.show(scr) + if not instance.wibox then + initialize(scr) + elseif instance.wibox.visible then -- Menu already shown, exit + return + elseif not menubar.cache_entries then + menubar.refresh(scr) + end + + -- Set position and size + scr = scr or awful.screen.focused() or 1 + scr = get_screen(scr) + local scrgeom = scr.workarea + local geometry = menubar.geometry + instance.geometry = {x = geometry.x or scrgeom.x, + y = geometry.y or scrgeom.y, + height = geometry.height or awful.util.round(theme.get_font_height() * 1.5), + width = geometry.width or scrgeom.width} + instance.wibox:geometry(instance.geometry) + + current_item = 1 + current_category = nil + menulist_update(nil, scr) + + local prompt_args = menubar.prompt_args or {} + + awful.prompt.run(setmetatable({ + prompt = "Run: ", + textbox = instance.prompt.widget, + completion_callback = awful.completion.shell, + history_path = awful.util.get_cache_dir() .. "/history_menu", + done_callback = menubar.hide, + changed_callback = function(query) menulist_update(query, scr) end, + keypressed_callback = prompt_keypressed_callback + }, {__index=prompt_args})) + + instance.wibox.visible = true +end + +--- Hide the menubar. +function menubar.hide() + instance.wibox.visible = false +end + +--- Get a menubar wibox. +-- @tparam[opt] screen scr Screen. +-- @return menubar wibox. +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 + end + return common_args.w +end + +function menubar.mt.__call(_, ...) + return menubar.get(...) +end + +return setmetatable(menubar, menubar.mt) + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 |