diff --git a/menubar/icon_theme.lua b/menubar/icon_theme.lua
new file mode 100644
index 0000000..358f007
--- /dev/null
+++ b/menubar/icon_theme.lua
@@ -0,0 +1,253 @@
+---------------------------------------------------------------------------
+--- (Deprecated) class module for icon lookup for menubar
+--
+-- @author Kazunobu Kuriyama
+-- @copyright 2015 Kazunobu Kuriyama
+-- @classmod menubar.icon_theme
+---------------------------------------------------------------------------
+
+-- This implementation is based on the specifications:
+-- Icon Theme Specification 0.12
+-- http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-0.12.html
+
+local beautiful = require("beautiful")
+local gfs = require("gears.filesystem")
+local GLib = require("lgi").GLib
+local index_theme = require("menubar.index_theme")
+
+local ipairs = ipairs
+local setmetatable = setmetatable
+local string = string
+local table = table
+local math = math
+
+local get_pragmatic_base_directories = function()
+ local dirs = {}
+
+ local dir = GLib.build_filenamev({GLib.get_home_dir(), ".icons"})
+ if gfs.dir_readable(dir) then
+ table.insert(dirs, dir)
+ end
+
+ dir = GLib.build_filenamev({GLib.get_user_data_dir(), "icons"})
+ if gfs.dir_readable(dir) then
+ table.insert(dirs, dir)
+ end
+
+ for _, v in ipairs(GLib.get_system_data_dirs()) do
+ dir = GLib.build_filenamev({v, "icons"})
+ if gfs.dir_readable(dir) then
+ table.insert(dirs, dir)
+ end
+ end
+
+ local need_usr_share_pixmaps = true
+ for _, v in ipairs(GLib.get_system_data_dirs()) do
+ dir = GLib.build_filenamev({v, "pixmaps"})
+ if gfs.dir_readable(dir) then
+ table.insert(dirs, dir)
+ end
+ if dir == "/usr/share/pixmaps" then
+ need_usr_share_pixmaps = false
+ end
+ end
+
+ dir = "/usr/share/pixmaps"
+ if need_usr_share_pixmaps and gfs.dir_readable(dir) then
+ table.insert(dirs, dir)
+ end
+
+ return dirs
+end
+
+local get_default_icon_theme_name = function()
+ local icon_theme_names = { "Adwaita", "gnome", "hicolor" }
+ for _, dir in ipairs(get_pragmatic_base_directories()) do
+ for _, icon_theme_name in ipairs(icon_theme_names) do
+ local filename = string.format("%s/%s/index.theme", dir, icon_theme_name)
+ if gfs.file_readable(filename) then
+ return icon_theme_name
+ end
+ end
+ end
+ return "hicolor"
+end
+
+local icon_theme = { mt = {} }
+
+local index_theme_cache = {}
+
+--- Class constructor of `icon_theme`
+-- @deprecated menubar.icon_theme.new
+-- @tparam string icon_theme_name Internal name of icon theme
+-- @tparam table base_directories Paths used for lookup
+-- @treturn table An instance of the class `icon_theme`
+icon_theme.new = function(icon_theme_name, base_directories)
+ icon_theme_name = icon_theme_name or beautiful.icon_theme or get_default_icon_theme_name()
+ base_directories = base_directories or get_pragmatic_base_directories()
+
+ local self = {}
+ self.icon_theme_name = icon_theme_name
+ self.base_directories = base_directories
+ self.extensions = { "png", "svg", "xpm" }
+
+ -- Instantiate index_theme (cached).
+ if not index_theme_cache[self.icon_theme_name] then
+ index_theme_cache[self.icon_theme_name] = {}
+ end
+ local cache_key = table.concat(self.base_directories, ':')
+ if not index_theme_cache[self.icon_theme_name][cache_key] then
+ index_theme_cache[self.icon_theme_name][cache_key] = index_theme(
+ self.icon_theme_name,
+ self.base_directories)
+ end
+ self.index_theme = index_theme_cache[self.icon_theme_name][cache_key]
+
+ return setmetatable(self, { __index = icon_theme })
+end
+
+local directory_matches_size = function(self, subdirectory, icon_size)
+ local kind, size, min_size, max_size, threshold = self.index_theme:get_per_directory_keys(subdirectory)
+
+ if kind == "Fixed" then
+ return icon_size == size
+ elseif kind == "Scalable" then
+ return icon_size >= min_size and icon_size <= max_size
+ elseif kind == "Threshold" then
+ return icon_size >= size - threshold and icon_size <= size + threshold
+ end
+
+ return false
+end
+
+local directory_size_distance = function(self, subdirectory, icon_size)
+ local kind, size, min_size, max_size, threshold = self.index_theme:get_per_directory_keys(subdirectory)
+
+ if kind == "Fixed" then
+ return math.abs(icon_size - size)
+ elseif kind == "Scalable" then
+ if icon_size < min_size then
+ return min_size - icon_size
+ elseif icon_size > max_size then
+ return icon_size - max_size
+ end
+ return 0
+ elseif kind == "Threshold" then
+ if icon_size < size - threshold then
+ return min_size - icon_size
+ elseif icon_size > size + threshold then
+ return icon_size - max_size
+ end
+ return 0
+ end
+
+ return 0xffffffff -- Any large number will do.
+end
+
+local lookup_icon = function(self, icon_name, icon_size)
+ local checked_already = {}
+ for _, subdir in ipairs(self.index_theme:get_subdirectories()) do
+ for _, basedir in ipairs(self.base_directories) do
+ for _, ext in ipairs(self.extensions) do
+ if directory_matches_size(self, subdir, icon_size) then
+ local filename = string.format("%s/%s/%s/%s.%s",
+ basedir, self.icon_theme_name, subdir,
+ icon_name, ext)
+ if gfs.file_readable(filename) then
+ return filename
+ else
+ checked_already[filename] = true
+ end
+ end
+ end
+ end
+ end
+
+ local minimal_size = 0xffffffff -- Any large number will do.
+ local closest_filename = nil
+ for _, subdir in ipairs(self.index_theme:get_subdirectories()) do
+ local dist = directory_size_distance(self, subdir, icon_size)
+ if dist < minimal_size then
+ for _, basedir in ipairs(self.base_directories) do
+ for _, ext in ipairs(self.extensions) do
+ local filename = string.format("%s/%s/%s/%s.%s",
+ basedir, self.icon_theme_name, subdir,
+ icon_name, ext)
+ if not checked_already[filename] then
+ if gfs.file_readable(filename) then
+ closest_filename = filename
+ minimal_size = dist
+ end
+ end
+ end
+ end
+ end
+ end
+ return closest_filename
+end
+
+local find_icon_path_helper -- Gets called recursively.
+find_icon_path_helper = function(self, icon_name, icon_size)
+ local filename = lookup_icon(self, icon_name, icon_size)
+ if filename then
+ return filename
+ end
+
+ for _, parent in ipairs(self.index_theme:get_inherits()) do
+ local parent_icon_theme = icon_theme(parent, self.base_directories)
+ filename = find_icon_path_helper(parent_icon_theme, icon_name, icon_size)
+ if filename then
+ return filename
+ end
+ end
+
+ return nil
+end
+
+local lookup_fallback_icon = function(self, icon_name)
+ for _, dir in ipairs(self.base_directories) do
+ for _, ext in ipairs(self.extensions) do
+ local filename = string.format("%s/%s.%s",
+ dir,
+ icon_name, ext)
+ if gfs.file_readable(filename) then
+ return filename
+ end
+ end
+ end
+ return nil
+end
+
+--- Look up an image file based on a given icon name and/or a preferable size.
+-- @deprecated menubar.icon_theme:find_icon_path
+-- @tparam string icon_name Icon name to be looked up
+-- @tparam number icon_size Prefereable icon size
+-- @treturn string Absolute path to the icon file, or nil if not found
+function icon_theme:find_icon_path(icon_name, icon_size)
+ icon_size = icon_size or 16
+ if not icon_name or icon_name == "" then
+ return nil
+ end
+
+ local filename = find_icon_path_helper(self, icon_name, icon_size)
+ if filename then
+ return filename
+ end
+
+ if self.icon_theme_name ~= "hicolor" then
+ filename = find_icon_path_helper(icon_theme("hicolor", self.base_directories), icon_name, icon_size)
+ if filename then
+ return filename
+ end
+ end
+
+ return lookup_fallback_icon(self, icon_name)
+end
+
+icon_theme.mt.__call = function(_, ...)
+ return icon_theme.new(...)
+end
+
+return setmetatable(icon_theme, icon_theme.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/menubar/index_theme.lua b/menubar/index_theme.lua
new file mode 100644
index 0000000..3ede62a
--- /dev/null
+++ b/menubar/index_theme.lua
@@ -0,0 +1,168 @@
+---------------------------------------------------------------------------
+--- (Deprecated) class module for parsing an index.theme file
+--
+-- @author Kazunobu Kuriyama
+-- @copyright 2015 Kazunobu Kuriyama
+-- @classmod menubar.index_theme
+---------------------------------------------------------------------------
+
+-- This implementation is based on the specifications:
+-- Icon Theme Specification 0.12
+-- http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-0.12.html
+
+local ipairs = ipairs
+local setmetatable = setmetatable
+local string = string
+local table = table
+local io = io
+
+-- index.theme groups
+local ICON_THEME = "Icon Theme"
+-- index.theme keys
+local DIRECTORIES = "Directories"
+local INHERITS = "Inherits"
+-- per-directory subkeys
+local TYPE = "Type"
+local SIZE = "Size"
+local MINSIZE = "MinSize"
+local MAXSIZE = "MaxSize"
+local THRESHOLD = "Threshold"
+
+local index_theme = { mt = {} }
+
+--- Class constructor of `index_theme`
+-- @deprecated menubar.index_theme.new
+-- @tparam table cls Metatable that will be used. Should always be `index_theme.mt`.
+-- @tparam string icon_theme_name Internal name of icon theme
+-- @tparam table base_directories Paths used for lookup
+-- @treturn table An instance of the class `index_theme`
+index_theme.new = function(cls, icon_theme_name, base_directories)
+ local self = {}
+ setmetatable(self, { __index = cls })
+
+ -- Initialize the fields
+ self.icon_theme_name = icon_theme_name
+ self.base_directory = nil
+ self[DIRECTORIES] = {}
+ self[INHERITS] = {}
+ self.per_directory_keys = {}
+
+ -- base_directory
+ local basedir = nil
+ local handler = nil
+ for _, dir in ipairs(base_directories) do
+ basedir = dir .. "/" .. self.icon_theme_name
+ handler = io.open(basedir .. "/index.theme", "r")
+ if handler then
+ -- Use the index.theme which is found first.
+ break
+ end
+ end
+ if not handler then
+ return self
+ end
+ self.base_directory = basedir
+
+ -- Parse index.theme.
+ while true do
+ local line = handler:read()
+ if not line then
+ break
+ end
+
+ local group_header = "^%[(.+)%]$"
+ local group = line:match(group_header)
+ if group then
+ if group == ICON_THEME then
+ while true do
+ local item = handler:read()
+ if not item then
+ break
+ end
+ if item:match(group_header) then
+ handler:seek("cur", -string.len(item) - 1)
+ break
+ end
+
+ local k, v = item:match("^(%w+)=(.*)$")
+ if k == DIRECTORIES or k == INHERITS then
+ string.gsub(v, "([^,]+),?", function(match)
+ table.insert(self[k], match)
+ end)
+ end
+ end
+ else
+ -- This must be a 'per-directory keys' group
+ local keys = {}
+
+ while true do
+ local item = handler:read()
+ if not item then
+ break
+ end
+ if item:match(group_header) then
+ handler:seek("cur", -string.len(item) - 1)
+ break
+ end
+
+ local k, v = item:match("^(%w+)=(%w+)$")
+ if k == SIZE or k == MINSIZE or k == MAXSIZE or k == THRESHOLD then
+ keys[k] = tonumber(v)
+ elseif k == TYPE then
+ keys[k] = v
+ end
+ end
+
+ -- Size is a must. Other keys are optional.
+ if keys[SIZE] then
+ -- Set unset keys to the default values.
+ if not keys[TYPE] then keys[TYPE] = THRESHOLD end
+ if not keys[MINSIZE] then keys[MINSIZE] = keys[SIZE] end
+ if not keys[MAXSIZE] then keys[MAXSIZE] = keys[SIZE] end
+ if not keys[THRESHOLD] then keys[THRESHOLD] = 2 end
+
+ self.per_directory_keys[group] = keys
+ end
+ end
+ end
+ end
+
+ handler:close()
+
+ return self
+end
+
+--- Table of the values of the `Directories` key
+-- @deprecated menubar.index_theme:get_subdirectories
+-- @treturn table Values of the `Directories` key
+index_theme.get_subdirectories = function(self)
+ return self[DIRECTORIES]
+end
+
+--- Table of the values of the `Inherits` key
+-- @deprecated menubar.index_theme:get_inherits
+-- @treturn table Values of the `Inherits` key
+index_theme.get_inherits = function(self)
+ return self[INHERITS]
+end
+
+--- Query (part of) per-directory keys of a given subdirectory name.
+-- @deprecated menubar.index_theme:get_per_directory_keys
+-- @tparam table subdirectory Icon theme's subdirectory
+-- @treturn string Value of the `Type` key
+-- @treturn number Value of the `Size` key
+-- @treturn number VAlue of the `MinSize` key
+-- @treturn number Value of the `MaxSize` key
+-- @treturn number Value of the `Threshold` key
+function index_theme:get_per_directory_keys(subdirectory)
+ local keys = self.per_directory_keys[subdirectory]
+ return keys[TYPE], keys[SIZE], keys[MINSIZE], keys[MAXSIZE], keys[THRESHOLD]
+end
+
+index_theme.mt.__call = function(cls, icon_theme_name, base_directories)
+ return index_theme.new(cls, icon_theme_name, base_directories)
+end
+
+return setmetatable(index_theme, index_theme.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/menubar/init.lua b/menubar/init.lua
new file mode 100644
index 0000000..96445e8
--- /dev/null
+++ b/menubar/init.lua
@@ -0,0 +1,601 @@
+---------------------------------------------------------------------------
+--- Menubar module, which aims to provide a freedesktop menu alternative.
+--
+-- List of menubar keybindings:
+-- ---
+--
+--
+--
+-- @author Alexander Yakushev <yakushev.alex@gmail.com>
+-- @copyright 2011-2012 Alexander Yakushev
+-- @popupmod menubar
+---------------------------------------------------------------------------
+
+-- Grab environment we need
+local capi = {
+ client = client,
+ mouse = mouse,
+ screen = screen
+}
+local gmath = require("gears.math")
+local awful = require("awful")
+local gfs = require("gears.filesystem")
+local common = require("awful.widget.common")
+local theme = require("beautiful")
+local wibox = require("wibox")
+local gcolor = require("gears.color")
+local gstring = require("gears.string")
+local gdebug = require("gears.debug")
+
+local function get_screen(s)
+ return s and capi.screen[s]
+end
+
+
+--- Menubar normal text color.
+-- @beautiful beautiful.menubar_fg_normal
+-- @param color
+
+--- Menubar normal background color.
+-- @beautiful beautiful.menubar_bg_normal
+-- @param color
+
+--- Menubar border width.
+-- @beautiful beautiful.menubar_border_width
+-- @tparam[opt=0] number menubar_border_width
+
+--- Menubar border color.
+-- @beautiful beautiful.menubar_border_color
+-- @param color
+
+--- Menubar selected item text color.
+-- @beautiful beautiful.menubar_fg_focus
+-- @param color
+
+--- Menubar selected item background color.
+-- @beautiful beautiful.menubar_bg_focus
+-- @param color
+
+--- Menubar font.
+-- @beautiful beautiful.menubar_font
+-- @param[opt=beautiful.font] font
+
+
+-- menubar
+local menubar = { menu_entries = {} }
+menubar.menu_gen = require("menubar.menu_gen")
+menubar.utils = require("menubar.utils")
+
+
+
+local current_page = {}
+-- 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
+
+--- When false will hide results if the current query is empty
+-- @tfield[opt=true] boolean match_empty
+menubar.match_empty = 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 spacing of dpi(4) between items.
+-- @tfield number list_spacing
+local list_spacing = theme.xresources.apply_dpi(4)
+
+--- Allows user to specify custom parameters for prompt.run function
+-- (like colors). This will merge with the default parameters, overriding affected values.
+-- @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 = nil
+
+local common_args = { w = wibox.layout.fixed.vertical(),
+ 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 "" .. s .. ""
+end
+
+--- Get how the menu item should be displayed.
+-- @param o The menu item.
+-- @return item name, item background color, background image, item icon, item args.
+local function label(o)
+ local fg_color = theme.menubar_fg_normal or theme.menu_fg_normal or theme.fg_normal
+ local bg_color = theme.menubar_bg_normal or theme.menu_bg_normal or theme.bg_normal
+ if o.focused then
+ fg_color = theme.menubar_fg_focus or theme.menu_fg_focus or theme.fg_focus
+ bg_color = theme.menubar_bg_focus or theme.menu_bg_focus or theme.bg_focus
+ end
+ return colortext(gstring.xml_escape(o.name), fg_color),
+ bg_color,
+ nil,
+ o.icon,
+ o.icon and {icon_size=20} -- TODO: dirty fix
+end
+
+local function load_count_table()
+ if instance.count_table then
+ return instance.count_table
+ end
+ instance.count_table = {}
+ local count_file_name = gfs.get_cache_dir() .. "/menu_count_file"
+ local count_file = io.open (count_file_name, "r")
+ if count_file then
+ for line in count_file:lines() do
+ local name, count = string.match(line, "([^;]+);([^;]+)")
+ if name ~= nil and count ~= nil then
+ instance.count_table[name] = count
+ end
+ end
+ count_file:close()
+ end
+ return instance.count_table
+end
+
+local function write_count_table(count_table)
+ count_table = count_table or instance.count_table
+ local count_file_name = gfs.get_cache_dir() .. "/menu_count_file"
+ local count_file = assert(io.open(count_file_name, "w"))
+ for name, count in pairs(count_table) do
+ local str = string.format("%s;%d\n", name, count)
+ count_file:write(str)
+ end
+ count_file:close()
+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
+ count_table[curname] = (count_table[curname] or 0) + 1
+ -- 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)
+
+ local compute_text_width = function(text, s)
+ return wibox.widget.textbox.get_markup_geometry(text, s, instance.font)['width']
+ end
+
+ 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 border_width = theme.menubar_border_width or theme.menu_border_width or 0
+ --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 - border_width * 2
+ -- space character is added as input cursor placeholder
+ local extra_width = menubar.left_label_width
+ local subtracted = false
+ local item_height = 24
+ local available_space = menubar.geometry.height - item_height * 2 -- TODO: dirty fix
+ local width_sum = 0
+ current_page = {}
+ for i, item in ipairs(all_items) do
+ item.width = item.width or (
+ compute_text_width(label(item), scr) +
+ (item.icon and (item_height + list_spacing) or 0)
+ ) -- TODO: 20 = dirty fix
+ local total_height = item_height * math.floor(item.width / menubar.geometry.width + 1)
+ if width_sum + total_height > available_space then -- TODO: 20 = dirty fix
+ if current_item < i then
+ table.insert(current_page, { name = menubar.right_label, ncon = nil })
+ break
+ end
+ current_page = { { name = menubar.left_label, icon = nil }, item, }
+ width_sum = total_height * 2 -- TODO: 20 = dirty fix
+ else
+ table.insert(current_page, item)
+ width_sum = width_sum + total_height -- TODO: 20 = dirty fix
+ end
+ end
+ return current_page
+end
+
+--- Update the menubar according to the command entered by user.
+-- @tparam number|screen scr Screen
+local function menulist_update(scr)
+ local query = instance.query or ""
+ shownitems = {}
+ local pattern = gstring.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
+ local add_entry = function(entry)
+ entry.focused = false
+ if not current_category or entry.category == current_category then
+
+ -- check if the query matches either the name or the commandline
+ -- of some entry
+ if string.match(entry.name, pattern)
+ or string.match(entry.cmdline, pattern) then
+
+ entry.weight = 0
+ entry.prio = PRIO_NONE
+
+ -- get use count from count_table if present
+ -- and use it as weight
+ if string.len(pattern) > 0 and count_table[entry.name] ~= nil then
+ entry.weight = tonumber(count_table[entry.name])
+ end
+
+ -- check for prefix match
+ if string.match(entry.name, "^" .. pattern)
+ or string.match(entry.cmdline, "^" .. pattern) then
+ -- increase default priority
+ entry.prio = PRIO_NONE + 1
+ else
+ entry.prio = PRIO_NONE
+ end
+
+ table.insert (command_list, entry)
+ end
+ end
+ end
+
+ -- Add entries if required
+ if query ~= "" or menubar.match_empty then
+ for _, v in ipairs(menubar.menu_entries) do
+ add_entry(v)
+ 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
+
+--- Refresh menubar's cache by reloading .desktop files.
+-- @tparam[opt] screen scr Screen.
+-- @staticfct menubar.refresh
+function menubar.refresh(scr)
+ scr = get_screen(scr or awful.screen.focused() or 1)
+ menubar.menu_gen.generate(function(entries)
+ menubar.menu_entries = entries
+ if instance then
+ menulist_update(scr)
+ end
+ 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 == "Up" or (mod.Control and key == "j") then
+ current_item = math.max(current_item - 1, 1)
+ return true
+ elseif key == "Down" or (mod.Control and key == "k") then
+ current_item = current_item + 1
+ return true
+ elseif key == "Left" or key == "Right" then
+ local tmp_sum = 0
+ local index = nil
+ local index_gen = function(tbl)
+ local idx = {}
+ for k,v in pairs(tbl) do
+ idx[v] = k
+ end
+ return idx
+ end
+ if current_page[1]["name"] == menubar.left_label then
+ tmp_sum = 1
+ if key == "Left" then
+ if not index then
+ index = index_gen(current_page)
+ end
+ tmp_sum = #current_page - index[shownitems[current_item]]
+ end
+ elseif key == "Left" then
+ tmp_sum = #current_page - current_item
+ end
+ tmp_sum = tmp_sum + 1
+ if current_page[#current_page]["name"] == menubar.right_label and key == "Right" then
+ if not index then
+ index = index_gen(current_page)
+ end
+ tmp_sum = index[shownitems[current_item]]
+ end
+ current_item = current_item + (#current_page - tmp_sum) * (key == "Right" and 1 or -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[opt] scr Screen.
+-- @staticfct menubar.show
+function menubar.show(scr)
+ scr = get_screen(scr or awful.screen.focused() or 1)
+ local fg_color = theme.menubar_fg_normal or theme.menu_fg_normal or theme.fg_normal
+ local bg_color = theme.menubar_bg_normal or theme.menu_bg_normal or theme.bg_normal
+ local border_width = theme.menubar_border_width or theme.menu_border_width or 0
+ local border_color = theme.menubar_border_color or theme.menu_border_color
+ local font = theme.menubar_font or theme.font or "Monospace 10"
+
+ if not instance then
+ -- 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
+
+ if menubar.cache_entries then
+ menubar.refresh(scr)
+ end
+
+ instance = {
+ wibox = wibox{
+ ontop = true,
+ bg = bg_color,
+ fg = fg_color,
+ border_width = border_width,
+ border_color = border_color,
+ font = font,
+ },
+ widget = common_args.w,
+ prompt = awful.widget.prompt(),
+ query = nil,
+ count_table = nil,
+ font = font,
+ }
+ local layout = wibox.layout.fixed.vertical()
+ layout:add(instance.prompt)
+ layout:add(instance.widget)
+ instance.wibox:set_widget(layout)
+ end
+
+ if instance.wibox.visible then -- Menu already shown, exit
+ return
+ elseif not menubar.cache_entries then
+ menubar.refresh(scr)
+ end
+
+ -- Set position and size
+ 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 gmath.round(theme.get_font_height(font) * 1.5),
+ width = (geometry.width or scrgeom.width) - border_width * 2}
+ instance.wibox:geometry(instance.geometry)
+
+ current_item = 1
+ current_category = nil
+ menulist_update(scr)
+
+ local default_prompt_args = {
+ prompt = "Run: ",
+ textbox = instance.prompt.widget,
+ completion_callback = awful.completion.shell,
+ history_path = gfs.get_cache_dir() .. "/history_menu",
+ done_callback = menubar.hide,
+ changed_callback = function(query)
+ instance.query = query
+ menulist_update(scr)
+ end,
+ keypressed_callback = prompt_keypressed_callback
+ }
+
+ awful.prompt.run(setmetatable(menubar.prompt_args, {__index=default_prompt_args}))
+
+
+ instance.wibox.visible = true
+end
+
+--- Hide the menubar.
+-- @staticfct menubar.hide
+function menubar.hide()
+ if instance then
+ instance.wibox.visible = false
+ instance.query = nil
+ end
+end
+
+--- Get a menubar wibox.
+-- @tparam[opt] screen scr Screen.
+-- @return menubar wibox.
+-- @deprecated get
+function menubar.get(scr)
+ gdebug.deprecate("Use menubar.show() instead", { deprecated_in = 5 })
+ 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
+
+local mt = {}
+function mt.__call(_, ...)
+ return menubar.get(...)
+end
+
+return setmetatable(menubar, mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/menubar/menu_gen.lua b/menubar/menu_gen.lua
new file mode 100644
index 0000000..19fb6e7
--- /dev/null
+++ b/menubar/menu_gen.lua
@@ -0,0 +1,136 @@
+---------------------------------------------------------------------------
+--- Menu generation module for menubar
+--
+-- @author Antonio Terceiro
+-- @copyright 2009, 2011-2012 Antonio Terceiro, Alexander Yakushev
+-- @module menubar.menu_gen
+---------------------------------------------------------------------------
+
+-- Grab environment
+local gtable = require("gears.table")
+local gfilesystem = require("gears.filesystem")
+local utils = require("menubar.utils")
+local pairs = pairs
+local ipairs = ipairs
+local table = table
+
+local menu_gen = {}
+
+-- Options section
+
+--- Get the path to the directories where XDG menu applications are installed.
+local function get_xdg_menu_dirs()
+ local dirs = gfilesystem.get_xdg_data_dirs()
+ table.insert(dirs, 1, gfilesystem.get_xdg_data_home())
+ return gtable.map(function(dir) return dir .. 'applications/' end, dirs)
+end
+
+--- Specifies all directories where menubar should look for .desktop
+-- files. The search is recursive.
+menu_gen.all_menu_dirs = get_xdg_menu_dirs()
+
+--- Specify the mapping of .desktop Categories section to the
+-- categories in the menubar. If "use" flag is set to false then any of
+-- the applications that fall only to this category will not be shown.
+menu_gen.all_categories = {
+ multimedia = { app_type = "AudioVideo", name = "Multimedia",
+ icon_name = "applications-multimedia", use = true },
+ development = { app_type = "Development", name = "Development",
+ icon_name = "applications-development", use = true },
+ education = { app_type = "Education", name = "Education",
+ icon_name = "applications-science", use = true },
+ games = { app_type = "Game", name = "Games",
+ icon_name = "applications-games", use = true },
+ graphics = { app_type = "Graphics", name = "Graphics",
+ icon_name = "applications-graphics", use = true },
+ office = { app_type = "Office", name = "Office",
+ icon_name = "applications-office", use = true },
+ internet = { app_type = "Network", name = "Internet",
+ icon_name = "applications-internet", use = true },
+ science = { app_type = "Science", name="Science",
+ icon_name = "applications-science", use = true },
+ settings = { app_type = "Settings", name = "Settings",
+ icon_name = "applications-utilities", use = true },
+ tools = { app_type = "System", name = "System Tools",
+ icon_name = "applications-system", use = true },
+ utility = { app_type = "Utility", name = "Accessories",
+ icon_name = "applications-accessories", use = true }
+}
+
+--- Find icons for category entries.
+-- @staticfct menubar.menu_gen.lookup_category_icons
+function menu_gen.lookup_category_icons()
+ for _, v in pairs(menu_gen.all_categories) do
+ v.icon = utils.lookup_icon(v.icon_name)
+ end
+end
+
+--- Get category key name and whether it is used by its app_type.
+-- @param app_type Application category as written in .desktop file.
+-- @return category key name in all_categories, whether the category is used
+local function get_category_name_and_usage_by_type(app_type)
+ for k, v in pairs(menu_gen.all_categories) do
+ if app_type == v.app_type then
+ return k, v.use
+ end
+ end
+end
+
+--- Generate an array of all visible menu entries.
+-- @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.
+-- @staticfct menubar.menu_gen.generate
+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
+ 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
+
+ local name = utils.rtrim(entry.Name) or ""
+ local cmdline = utils.rtrim(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
+ dirs_parsed = dirs_parsed + 1
+ if dirs_parsed == #menu_gen.all_menu_dirs then
+ callback(result)
+ end
+ end)
+ end
+end
+
+return menu_gen
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/menubar/utils.lua b/menubar/utils.lua
new file mode 100644
index 0000000..9c8f283
--- /dev/null
+++ b/menubar/utils.lua
@@ -0,0 +1,430 @@
+---------------------------------------------------------------------------
+--- Utility module for menubar
+--
+-- @author Antonio Terceiro
+-- @copyright 2009, 2011-2012 Antonio Terceiro, Alexander Yakushev
+-- @module menubar.utils
+---------------------------------------------------------------------------
+
+-- Grab environment
+local table = table
+local ipairs = ipairs
+local string = string
+local screen = screen
+local gfs = require("gears.filesystem")
+local theme = require("beautiful")
+local lgi = require("lgi")
+local gio = lgi.Gio
+local glib = lgi.GLib
+local w_textbox = require("wibox.widget.textbox")
+local gdebug = require("gears.debug")
+local protected_call = require("gears.protected_call")
+local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
+
+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.
+-- @param[opt="xterm"] string
+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 OnlyShowIn entry in the .desktop file.
+-- @param[opt="awesome"] string
+utils.wm_name = "awesome"
+
+-- Maps keys in desktop entries to suitable getter function.
+-- The order of entries is as in the spec.
+-- https://standards.freedesktop.org/desktop-entry-spec/latest/ar01s05.html
+local keys_getters
+do
+ local function get_string(kf, key)
+ return kf:get_string("Desktop Entry", key)
+ end
+ local function get_strings(kf, key)
+ return kf:get_string_list("Desktop Entry", key, nil)
+ end
+ local function get_localestring(kf, key)
+ return kf:get_locale_string("Desktop Entry", key, nil)
+ end
+ local function get_localestrings(kf, key)
+ return kf:get_locale_string_list("Desktop Entry", key, nil, nil)
+ end
+ local function get_boolean(kf, key)
+ return kf:get_boolean("Desktop Entry", key)
+ end
+
+ keys_getters = {
+ Type = get_string,
+ Version = get_string,
+ Name = get_localestring,
+ GenericName = get_localestring,
+ NoDisplay = get_boolean,
+ Comment = get_localestring,
+ Icon = get_localestring,
+ Hidden = get_boolean,
+ OnlyShowIn = get_strings,
+ NotShowIn = get_strings,
+ DBusActivatable = get_boolean,
+ TryExec = get_string,
+ Exec = get_string,
+ Path = get_string,
+ Terminal = get_boolean,
+ Actions = get_strings,
+ MimeType = get_strings,
+ Categories = get_strings,
+ Implements = get_strings,
+ Keywords = get_localestrings,
+ StartupNotify = get_boolean,
+ StartupWMClass = get_string,
+ URL = get_string,
+ }
+end
+
+-- Private section
+
+local do_protected_call, call_callback
+do
+ -- Lua 5.1 cannot yield across a protected call. Instead of hardcoding a
+ -- check, we check for this problem: The following coroutine yields true on
+ -- success (so resume() returns true, true). On failure, pcall returns
+ -- false and a message, so resume() returns true, false, message.
+ local _, has_yieldable_pcall = coroutine.resume(coroutine.create(function()
+ return pcall(coroutine.yield, true)
+ end))
+ if has_yieldable_pcall then
+ do_protected_call = protected_call.call
+ call_callback = function(callback, ...)
+ return callback(...)
+ end
+ else
+ do_protected_call = function(f, ...)
+ return f(...)
+ end
+ call_callback = protected_call.call
+ end
+end
+
+local all_icon_sizes = {
+ 'scalable',
+ '128x128',
+ '96x96',
+ '72x72',
+ '64x64',
+ '48x48',
+ '36x36',
+ '32x32',
+ '24x24',
+ '22x22',
+ '16x16'
+}
+
+--- List of supported icon exts.
+local supported_icon_file_exts = { png = 1, xpm = 2, svg = 3 }
+
+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 icon_lookup_path then return icon_lookup_path end
+
+ local function ensure_args(t, paths)
+ if type(paths) == 'string' then paths = { paths } end
+ return t or {}, paths
+ end
+
+ local function add_if_readable(t, paths)
+ t, paths = ensure_args(t, paths)
+
+ for _, path in ipairs(paths) do
+ if gfs.dir_readable(path) then
+ table.insert(t, path)
+ end
+ end
+ return t
+ end
+
+ local function add_with_dir(t, paths, dir)
+ t, paths = ensure_args(t, paths)
+ dir = { nil, dir }
+
+ for _, path in ipairs(paths) do
+ dir[1] = path
+ table.insert(t, glib.build_filenamev(dir))
+ end
+ return t
+ end
+
+ icon_lookup_path = {}
+ local theme_priority = { 'hicolor' }
+ if theme.icon_theme then table.insert(theme_priority, 1, theme.icon_theme) end
+
+ local paths = add_with_dir({}, glib.get_home_dir(), '.icons')
+ add_with_dir(paths, {
+ glib.get_user_data_dir(), -- $XDG_DATA_HOME, typically $HOME/.local/share
+ unpack(glib.get_system_data_dirs()) -- $XDG_DATA_DIRS, typically /usr/{,local/}share
+ }, 'icons')
+ add_with_dir(paths, glib.get_system_data_dirs(), 'pixmaps')
+
+ local icon_theme_paths = {}
+ for _, theme_dir in ipairs(theme_priority) do
+ add_if_readable(icon_theme_paths,
+ add_with_dir({}, paths, theme_dir))
+ end
+
+ local app_in_theme_paths = {}
+ for _, icon_theme_directory in ipairs(icon_theme_paths) do
+ for _, size in ipairs(all_icon_sizes) do
+ table.insert(app_in_theme_paths,
+ glib.build_filenamev({ icon_theme_directory,
+ size, 'apps' }))
+ table.insert(app_in_theme_paths,
+ glib.build_filenamev({ icon_theme_directory,
+ size, 'categories' }))
+ end
+ end
+ add_if_readable(icon_lookup_path, app_in_theme_paths)
+
+ return add_if_readable(icon_lookup_path, paths)
+end
+
+--- Remove CR newline from the end of the string.
+-- @param s string to trim
+-- @staticfct menubar.utils.rtrim
+function utils.rtrim(s)
+ if not s then return end
+ if string.byte(s, #s) == 13 then
+ return string.sub(s, 1, #s - 1)
+ end
+ return s
+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.
+-- @staticfct menubar.utils.lookup_icon_uncached
+function utils.lookup_icon_uncached(icon_file)
+ if not icon_file or icon_file == "" then
+ return false
+ end
+
+ local icon_file_ext = icon_file:match(".+%.(.*)$")
+ if icon_file:sub(1, 1) == '/' and supported_icon_file_exts[icon_file_ext] then
+ -- If the path to the icon is absolute do not perform a lookup [nil if unsupported ext or missing]
+ return gfs.file_readable(icon_file) and icon_file or nil
+ else
+ -- Look for the requested file in the lookup path
+ for _, directory in ipairs(get_icon_lookup_path()) do
+ local possible_file = directory .. "/" .. icon_file
+ -- Check to see if file exists if requested with a valid extension
+ if supported_icon_file_exts[icon_file_ext] and gfs.file_readable(possible_file) then
+ return possible_file
+ else
+ -- Find files with any supported extension if icon specified without, eg: 'firefox'
+ for ext, _ in pairs(supported_icon_file_exts) do
+ local possible_file_new_ext = possible_file .. "." .. ext
+ if gfs.file_readable(possible_file_new_ext) then
+ return possible_file_new_ext
+ end
+ end
+ end
+ end
+ -- No icon found
+ 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.
+-- @staticfct menubar.utils.lookup_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.
+-- @staticfct menubar.utils.parse_desktop_file
+function utils.parse_desktop_file(file)
+ local program = { show = true, file = file }
+
+ -- Parse the .desktop file.
+ -- We are interested in [Desktop Entry] group only.
+ local keyfile = glib.KeyFile()
+ if not keyfile:load_from_file(file, glib.KeyFileFlags.NONE) then
+ return nil
+ end
+
+ -- In case [Desktop Entry] was not found
+ if not keyfile:has_group("Desktop Entry") then
+ return nil
+ end
+
+ for _, key in pairs(keyfile:get_keys("Desktop Entry")) do
+ local getter = keys_getters[key] or function(kf, k)
+ return kf:get_string("Desktop Entry", k)
+ end
+ program[key] = getter(keyfile, key)
+ 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 true
+ if program.NoDisplay then
+ program.show = false
+ else
+ -- Only check these values is NoDisplay is true (or non-existent)
+
+ -- Only show the program if there is no OnlyShowIn attribute
+ -- or if it contains wm_name or wm_name is empty
+ if utils.wm_name ~= "" then
+ if program.OnlyShowIn then
+ program.show = false -- Assume false until found
+ for _, wm in ipairs(program.OnlyShowIn) do
+ if wm == utils.wm_name then
+ program.show = true
+ break
+ end
+ end
+ else
+ program.show = true
+ end
+ end
+
+ -- Only need to check NotShowIn if the program is being shown
+ if program.show and program.NotShowIn then
+ for _, wm in ipairs(program.NotShowIn) do
+ if wm == utils.wm_name then
+ program.show = false
+ break
+ end
+ end
+ end
+ end
+
+ -- Look up for a icon.
+ if program.Icon then
+ program.icon_path = utils.lookup_icon(program.Icon)
+ end
+
+ -- Make the variable lower-case like the rest of them
+ if program.Categories then
+ program.categories = program.Categories
+ 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.
+-- @staticfct menubar.utils.parse_dir
+function utils.parse_dir(dir_path, callback)
+
+ local function get_readable_path(file)
+ return file:get_path() or file:get_uri()
+ end
+
+ local function parser(file, programs)
+ -- Except for "NONE" there is also NOFOLLOW_SYMLINKS
+ local query = gio.FILE_ATTRIBUTE_STANDARD_NAME .. "," .. gio.FILE_ATTRIBUTE_STANDARD_TYPE
+ local enum, err = file:async_enumerate_children(query, gio.FileQueryInfoFlags.NONE)
+ if not enum then
+ gdebug.print_warning(get_readable_path(file) .. ": " .. tostring(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
+ gdebug.print_error(get_readable_path(file) .. ": " .. tostring(enum_err))
+ return
+ end
+ for _, info in ipairs(list) do
+ local file_type = info:get_file_type()
+ local file_child = enum:get_child(info)
+ if file_type == 'REGULAR' then
+ local path = file_child:get_path()
+ if path then
+ local success, program = pcall(utils.parse_desktop_file, path)
+ if not success then
+ gdebug.print_error("Error while reading '" .. path .. "': " .. program)
+ elseif program then
+ table.insert(programs, program)
+ end
+ end
+ elseif file_type == 'DIRECTORY' then
+ parser(file_child, programs)
+ end
+ end
+ if #list == 0 then
+ break
+ end
+ end
+ enum:async_close()
+ end
+
+ gio.Async.start(do_protected_call)(function()
+ local result = {}
+ parser(gio.File.new_for_path(dir_path), result)
+ call_callback(callback, result)
+ end)
+end
+
+-- luacov: disable
+
+function utils.compute_textbox_width(textbox, s)
+ gdebug.deprecate("Use 'width, _ = textbox:get_preferred_size(s)' directly.", {deprecated_in=4})
+ s = screen[s or mouse.screen]
+ local w, _ = textbox:get_preferred_size(s)
+ return w
+end
+
+function utils.compute_text_width(text, s, font)
+ gdebug.deprecate("Use 'width = textbox.get_markup_geometry(text, s, font)['width']'.", {deprecated_in=4})
+ return w_textbox.get_markup_geometry(text, s, font)['width']
+end
+
+-- luacov: enable
+
+return utils
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/rc.lua b/rc.lua
index 8c468fa..e3ac851 100644
--- a/rc.lua
+++ b/rc.lua
@@ -3,6 +3,7 @@
pcall(require, "luarocks.loader")
-- Standard awesome library
+local capi = {awesome = awesome}
local gears = require("gears")
local awful = require("awful")
require("awful.autofocus")
@@ -13,15 +14,191 @@ local beautiful = require("beautiful")
-- Notification library
local naughty = require("naughty")
local menubar = require("menubar")
-local hotkeys_popup = require("awful.hotkeys_popup")
+local menu_width = 200
+local tmp_screen = { 1080, 1920 }
+menubar.geometry = { height = tmp_screen[1], width = menu_width, x = tmp_screen[2] - menu_width }
+
+
+--local hotkeys_popup = require("awful.hotkeys_popup")
+local hotkeys = require("awful.hotkeys_popup");
--local hotkeys = require("awful.hotkeys_popup")
--local hotkeys_popup = hotkeys_popup.widget.new({ width = 100, height = 100 })
--hotkeys_popup.show_help = hotkeys_popup.widget.show_help
-- Enable hotkeys help widget for VIM and other apps
-- when client with a matching name is opened:
-require("awful.hotkeys_popup.keys.vim")
---local cairo = require("lgi").cairo
+--vim = require("awful.hotkeys_popup.keys.vim")
+local my_hotkeys_popup = hotkeys.widget.new({ width = 1870, height = 1050});
+
+local vim_rule_any = {name={"vim", "VIM"}}
+for group_name, group_data in pairs({
+ ["VIM: motion"] = { color="#009F00", rule_any=vim_rule_any },
+ ["VIM: command"] = { color="#aFaF00", rule_any=vim_rule_any },
+ ["VIM: command (insert)"] = { color="#cF4F40", rule_any=vim_rule_any },
+ ["VIM: operator"] = { color="#aF6F00", rule_any=vim_rule_any },
+ ["VIM: find"] = { color="#65cF9F", rule_any=vim_rule_any },
+ ["VIM: scroll"] = { color="#659FdF", rule_any=vim_rule_any },
+}) do
+ my_hotkeys_popup:add_group_rules(group_name, group_data)
+end
+
+
+local vim_keys = {
+
+ ["VIM: motion"] = {{
+ modifiers = {},
+ keys = {
+ ['`']="goto mark",
+ ['0']='"hard" BOL',
+ ['-']="prev line",
+ w="next word",
+ e="end word",
+ ['[']=". misc",
+ [']']=". misc",
+ ["'"]=". goto mk. BOL",
+ b="prev word",
+ ["|"]='BOL/goto col',
+ ["$"]='EOL',
+ ["%"]='goto matching bracket',
+ ["^"]='"soft" BOL',
+ ["("]='sentence begin',
+ [")"]='sentence end',
+ ["_"]='"soft" BOL down',
+ ["+"]='next line',
+ W='next WORD',
+ E='end WORD',
+ ['{']="paragraph begin",
+ ['}']="paragraph end",
+ G='EOF/goto line',
+ H='move cursor to screen top',
+ M='move cursor to screen middle',
+ L='move cursor to screen bottom',
+ B='prev WORD',
+ }
+ }, {
+ modifiers = {"Ctrl"},
+ keys = {
+ u="half page up",
+ d="half page down",
+ b="page up",
+ f="page down",
+ o="prev mark",
+ }
+ }},
+
+ ["VIM: operator"] = {{
+ modifiers = {},
+ keys = {
+ ['=']="auto format",
+ y="yank",
+ d="delete",
+ c="change",
+ ["!"]='external filter',
+ ['<']='unindent',
+ ['>']='indent',
+ }
+ }},
+
+ ["VIM: command"] = {{
+ modifiers = {},
+ keys = {
+ ['~']="toggle case",
+ q=". record macro",
+ r=". replace char",
+ u="undo",
+ p="paste after",
+ gg="go to the top of file",
+ gf="open file under cursor",
+ x="delete char",
+ v="visual mode",
+ m=". set mark",
+ ['.']="repeat command",
+ ["@"]='. play macro',
+ ["&"]='repeat :s',
+ Q='ex mode',
+ Y='yank line',
+ U='undo line',
+ P='paste before cursor',
+ D='delete to EOL',
+ J='join lines',
+ K='help',
+ [':']='ex cmd line',
+ ['"']='. register spec',
+ ZZ='quit and save',
+ ZQ='quit discarding changes',
+ X='back-delete',
+ V='visual lines selection',
+ }
+ }, {
+ modifiers = {"Ctrl"},
+ keys = {
+ w=". window operations",
+ r="redo",
+ ["["]="normal mode",
+ a="increase number",
+ x="decrease number",
+ g="file/cursor info",
+ z="suspend",
+ c="cancel/normal mode",
+ v="visual block selection",
+ }
+ }},
+
+ ["VIM: command (insert)"] = {{
+ modifiers = {},
+ keys = {
+ i="insert mode",
+ o="open below",
+ a="append",
+ s="subst char",
+ R='replace mode',
+ I='insert at BOL',
+ O='open above',
+ A='append at EOL',
+ S='subst line',
+ C='change to EOL',
+ }
+ }},
+
+ ["VIM: find"] = {{
+ modifiers = {},
+ keys = {
+ [';']="repeat t/T/f/F",
+ [',']="reverse t/T/f/F",
+ ['/']=". find",
+ ['?']='. reverse find',
+ n="next search match",
+ N='prev search match',
+ f=". find char",
+ F='. reverse find char',
+ t=". 'till char",
+ T=". reverse 'till char",
+ ["*"]='find word under cursor',
+ ["#"]='reverse find under cursor',
+ }
+ }},
+
+ ["VIM: scroll"] = {{
+ modifiers = {},
+ keys = {
+ zt="scroll cursor to the top",
+ zz="scroll cursor to the center",
+ zb="scroll cursor to the bottom",
+ }
+ },{
+ modifiers = {"Ctrl"},
+ keys = {
+ e="scroll line up",
+ y="scroll line down",
+ }
+ }},
+}
+
+my_hotkeys_popup:add_hotkeys(vim_keys)
+
+--my_hotkeys_popup.keys.vim = vim
+--local cairo = require("lgi").cairo
+
-- My custom libs
local vicious = require("vicious")
@@ -75,6 +252,7 @@ modkey = "Mod4"
-- Table of layouts to cover with awful.layout.inc, order matters.
awful.layout.layouts = {
+ awful.layout.suit.spiral,
awful.layout.suit.floating,
awful.layout.suit.tile,
awful.layout.suit.tile.left,
@@ -82,7 +260,6 @@ awful.layout.layouts = {
awful.layout.suit.tile.top,
awful.layout.suit.fair,
awful.layout.suit.fair.horizontal,
- awful.layout.suit.spiral,
awful.layout.suit.spiral.dwindle,
awful.layout.suit.max,
awful.layout.suit.max.fullscreen,
@@ -97,7 +274,7 @@ awful.layout.layouts = {
-- {{{ Menu
-- Create a launcher widget and a main menu
myawesomemenu = {
- { "hotkeys", function() hotkeys_popup.show_help(nil, awful.screen.focused()) end },
+ { "hotkeys", function() my_hotkeys_popup:show_help(nil, awful.screen.focused()) end },
{ "manual", terminal .. " -e man awesome" },
{ "edit config", editor_cmd .. " " .. awesome.conffile },
{ "restart", awesome.restart },
@@ -119,7 +296,18 @@ menubar.utils.terminal = terminal -- Set the terminal for applications that requ
-- Keyboard map indicator and switcher
mykeyboardlayout = awful.widget.keyboardlayout()
-
+--mykeyboardlayout.widget.forced_height = 20
+--mykeyboardlayout.widget.text = mykeyboardlayout.widget.text:gsub("%s+", "")
+--mykeyboardlayout.widget.forced_width = 20
+local clean_textbox = function(w)
+ w.text = w.text:sub(2)
+ w.text = w.text:sub(1, -2)
+end
+capi.awesome.connect_signal("xkb::map_changed",
+ function () clean_textbox(mykeyboardlayout.widget) end)
+capi.awesome.connect_signal("xkb::group_changed",
+ function () clean_textbox(mykeyboardlayout.widget) end)
+clean_textbox(mykeyboardlayout.widget)
-- {{{ Wibar
-- Create a wibox for each screen and add it
@@ -194,7 +382,7 @@ end
----
datewidget = wibox.widget.textbox()
-vicious.register(datewidget, vicious.widgets.date, "%b %d, %R ")
+vicious.register(datewidget, vicious.widgets.date, "%m %d %H %M %S", 1)
--memwidget = wibox.widget.textbox()
--vicious.cache(vicious.widgets.mem)
@@ -204,16 +392,19 @@ vicious.register(datewidget, vicious.widgets.date, "%b %d, %R ")
batwidget = wibox.widget.progressbar()
batbox = wibox.layout.margin(
wibox.widget{ { max_value = 1, widget = batwidget,
- border_width = 0.5, border_color = "#000000",
- color = { type = "linear",
- from = { 0, 0 },
- to = { 0, 30 },
- stops = { { 0, "#AECF96" },
- { 1, "#FF5656" } } } },
- forced_height = 10, forced_width = 8,
- direction = 'east', color = beautiful.fg_widget,
+ border_width = 0, border_color = "#000000",
+ color = "#FFFFFF",
+ background_color = "#000000"
+ },
+ --color = { type = "linear",
+ -- from = { 0, 0 },
+ -- to = { 0, 30 },
+ -- stops = { { 0, "#FFFFFF" },
+ -- { 1, "#000000" } } } },
+ forced_height = 1, forced_width = 1,
+ direction = 'south', color = beautiful.fg_widget,
layout = wibox.container.rotate },
- 1, 4, 1, 1)
+ 0, 0, 0, 0)
-- Register battery widget
vicious.register(batwidget, vicious.widgets.bat, "$2", 1, "BAT1")
@@ -269,11 +460,87 @@ awful.screen.connect_for_each_screen(function(s)
awful.button({ }, 4, function () awful.layout.inc( 1) end),
awful.button({ }, 5, function () awful.layout.inc(-1) end)))
-- Create a taglist widget
- s.mytaglist = awful.widget.taglist {
- screen = s,
- filter = awful.widget.taglist.filter.all,
- buttons = taglist_buttons
- }
+ --s.mytaglist = awful.widget.taglist {
+ -- screen = s,
+ -- filter = awful.widget.taglist.filter.all,
+ -- buttons = taglist_buttons
+ --}
+s.mytaglist = awful.widget.taglist {
+ screen = s,
+ filter = awful.widget.taglist.filter.all,
+ style = {
+ shape = function (cr, width, height) gears.shape.powerline(cr, width*8/10, height, height/4) end
+ },
+ layout = {
+ spacing = -14,
+ spacing_widget = {
+ color = '#dddddd',
+ shape = function (cr, width, height) gears.shape.powerline(cr, width/2, height, height/4) end,
+ widget = wibox.widget.separator,
+ },
+ layout = wibox.layout.fixed.horizontal
+ },
+ widget_template = {
+ {
+ {
+ {
+ {
+ --{
+ {
+ id = 'index_role',
+ font = "Indie Flower Bold 11",
+ widget = wibox.widget.textbox,
+ },
+
+ widget = wibox.container.place,
+ --},
+ --bg = '#55000044',
+ --shape = gears.shape.circle,
+ --widget = wibox.container.background,
+ },
+ layout = wibox.container.rotate,
+ direction = "east",
+ },
+ --{
+ -- {
+ -- id = 'icon_role',
+ -- widget = wibox.widget.imagebox,
+ -- },
+ -- margins = 2,
+ -- widget = wibox.container.margin,
+ --},
+ --{
+ -- id = 'text_role',
+ -- widget = wibox.widget.textbox,
+ --},
+ layout = wibox.layout.fixed.horizontal,
+ },
+ left = 5,
+ right = 9,
+ widget = wibox.container.margin
+ },
+ id = 'background_role',
+ widget = wibox.container.background,
+ -- Add support for hover colors and an index label
+ create_callback = function(self, c3, index, objects) --luacheck: no unused args
+ self:get_children_by_id('index_role')[1].markup = ''..index..''
+ --self:connect_signal('mouse::enter', function()
+ -- if self.bg ~= '#ff000033' then
+ -- self.backup = self.bg
+ -- self.has_backup = true
+ -- end
+ -- self.bg = '#ff00ff55'
+ --end)
+ --self:connect_signal('mouse::leave', function()
+ -- if self.has_backup then self.bg = self.backup end
+ --end)
+ end,
+ --update_callback = function(self, c3, index, objects) --luacheck: no unused args
+ -- self:get_children_by_id('index_role')[1].markup = ' '..index..' '
+ --end,
+ },
+ buttons = taglist_buttons
+}
-- Create a tasklist widget
s.mytasklist = awful.widget.tasklist {
@@ -283,29 +550,79 @@ awful.screen.connect_for_each_screen(function(s)
}
-- Create the wibox
- s.mywibox = awful.wibar({ position = "bottom", screen = s })
+ s.mywibox = awful.wibar({ position = "left", screen = s })
-- Add widgets to the wibox
s.mywibox:setup {
- layout = wibox.layout.align.horizontal,
- { -- Left widgets
- layout = wibox.layout.fixed.horizontal,
- mylauncher,
- s.mytaglist,
- s.mypromptbox,
- },
- s.mytasklist, -- Middle widget
- { -- Right widgets
- layout = wibox.layout.fixed.horizontal,
- wibox.widget.systray(),
- memwidget,
- swapwidget,
- cpuwidget,
- mykeyboardlayout,
- datewidget,
- batbox,
- s.mylayoutbox,
- },
+ layout = wibox.container.rotate,
+ direction = "west",
+ {
+ layout = wibox.layout.align.vertical,
+ {
+ layout = wibox.layout.constraint,
+ strategy = "max",
+ height = 18,
+ {
+ layout = wibox.layout.align.horizontal,
+ { -- Left widgets
+ layout = wibox.layout.fixed.horizontal,
+ {
+ layout = wibox.container.rotate,
+ direction = "east",
+ mylauncher,
+ },
+ s.mytaglist,
+ s.mypromptbox,
+ },
+ s.mytasklist, -- Middle widget
+ { -- Right widgets
+ layout = wibox.layout.fixed.horizontal,
+ wibox.widget.systray(),
+ memwidget,
+ swapwidget,
+ cpuwidget,
+ {
+ layout = wibox.container.rotate,
+ direction = "east",
+ mykeyboardlayout.widget,
+ },
+ {
+ forced_width = 1,
+ orientation = "vertical",
+ widget = wibox.widget.separator
+ },
+ {
+ thickness = 0,
+ forced_width = 6,
+ orientation = "vertical",
+ widget = wibox.widget.separator
+ },
+ {
+ layout = wibox.container.rotate,
+ direction = "east",
+ datewidget,
+ },
+ s.mylayoutbox,
+ },
+ },
+ },
+ {
+ layout = wibox.layout.constraint,
+ {
+ layout = wibox.layout.flex.horizontal,
+ {
+ layout = wibox.container.rotate,
+ direction = "north",
+ batbox,
+ },
+ {
+ layout = wibox.container.rotate,
+ direction = "south",
+ batbox,
+ }
+ }
+ }
+ }
}
end)
-- }}}
@@ -320,7 +637,10 @@ root.buttons(gears.table.join(
-- {{{ Key bindings
globalkeys = gears.table.join(
- awful.key({ modkey, }, "s", hotkeys_popup.show_help,
+ awful.key({ modkey, }, "s",
+ function ()
+ my_hotkeys_popup:show_help(nil, awful.screen.focused())
+ end,
{description="show help", group="awesome"}),
awful.key({ modkey, }, "Left", awful.tag.viewprev,
{description = "view previous", group = "tag"}),
@@ -601,7 +921,7 @@ client.connect_signal("manage", function (c)
-- Set the windows at the slave,
-- i.e. put it at the end of others instead of setting it master.
-- if not awesome.startup then awful.client.setslave(c) end
-
+ c.shape = beautiful.shape
if awesome.startup
and not c.size_hints.user_position
and not c.size_hints.program_position then
@@ -661,7 +981,16 @@ end)
client.connect_signal("mouse::enter", function(c)
c:emit_signal("request::activate", "mouse_enter", {raise = false})
end)
-
-client.connect_signal("focus", function(c) c.border_color = beautiful.border_focus end)
-client.connect_signal("unfocus", function(c) c.border_color = beautiful.border_normal end)
+--beautiful.border_normal = "#00000055"
+--beautiful.border_focus = "#00000000"
+client.connect_signal("focus", function(c)
+ c.border_color = beautiful.border_focus
+ c.border_width = beautiful.border_width_focus
+end)
+client.connect_signal("unfocus", function(c)
+ c.border_color = beautiful.border_normal
+ c.border_width = beautiful.border_width
+ c.useless_gap = 0
+end)
+
-- }}}
diff --git a/themes/zenburn/awesome-icon.png b/themes/zenburn/awesome-icon.png
new file mode 100644
index 0000000..60be4f2
Binary files /dev/null and b/themes/zenburn/awesome-icon.png differ
diff --git a/themes/zenburn/submenu.png b/themes/zenburn/submenu.png
new file mode 100644
index 0000000..b2778e2
Binary files /dev/null and b/themes/zenburn/submenu.png differ
diff --git a/themes/zenburn/taglist/squarefz.png b/themes/zenburn/taglist/squarefz.png
index 0927720..a65608b 100644
Binary files a/themes/zenburn/taglist/squarefz.png and b/themes/zenburn/taglist/squarefz.png differ
diff --git a/themes/zenburn/taglist/squarefz_orig.png b/themes/zenburn/taglist/squarefz_orig.png
new file mode 100644
index 0000000..0927720
Binary files /dev/null and b/themes/zenburn/taglist/squarefz_orig.png differ
diff --git a/themes/zenburn/taglist/squarez.png b/themes/zenburn/taglist/squarez.png
index 9b41c26..0fd3c2a 100644
Binary files a/themes/zenburn/taglist/squarez.png and b/themes/zenburn/taglist/squarez.png differ
diff --git a/themes/zenburn/taglist/squarez_orig.png b/themes/zenburn/taglist/squarez_orig.png
new file mode 100644
index 0000000..9b41c26
Binary files /dev/null and b/themes/zenburn/taglist/squarez_orig.png differ
diff --git a/themes/zenburn/theme.lua b/themes/zenburn/theme.lua
index 75fdee7..0adfaca 100644
--- a/themes/zenburn/theme.lua
+++ b/themes/zenburn/theme.lua
@@ -5,9 +5,10 @@
-- By Adrian C. (anrxc) --
----------------------------------------
-local themes_path = require("gears.filesystem").get_themes_dir()
+local themes_path = "/home/surferlul/.config/awesome/themes/"
local dpi = require("beautiful.xresources").apply_dpi
local beautiful = require("beautiful")
+local gears = require("gears")
-- {{{ Main
local theme = {}
@@ -34,9 +35,9 @@ theme.hotkeys_fg = "#EEEEFF"
theme.hotkeys_label_fg = "#111122"
theme.hotkeys_modifiers_fg = "#DD7777"
theme.hotkeys_description_font = "Indie Flower Bold 10"
-theme.hotkeys_border_width = 1
+theme.hotkeys_border_width = 2
theme.hotkeys_border_color = "#6f6f6f55"
-
+theme.hotkeys_shape = function(cr, width, height) gears.shape.rounded_rect(cr, width, height, 15) end
theme.menubar_bg_normal = "#11112299"
theme.menubar_fg_normal = "#FFBBBB"
theme.menubar_bg_focus = "#333344FF"
@@ -48,10 +49,12 @@ theme.menu_bg_focus = "#333344FF"
theme.menu_fg_focus = "#BBBBFF"
-- {{{ Borders
-theme.useless_gap = dpi(2)
-theme.border_width = dpi(1)
-theme.border_normal = "#6F6F6F55"
-theme.border_focus = "#6F6F6F55"
+theme.shape = function(cr, width, height) gears.shape.rounded_rect(cr, width, height, 4) end
+theme.useless_gap = 2
+theme.border_width = 2
+theme.border_width_focus = 2
+theme.border_normal = "#00000077" -- basically no border
+theme.border_focus = "#00000000" -- slightly more lit up than the shadow
theme.border_marked = "#CC939355"
-- }}}
@@ -103,7 +106,7 @@ theme.taglist_squares_unsel = themes_path .. "zenburn/taglist/squarez.png"
-- {{{ Misc
theme.awesome_icon = themes_path .. "zenburn/awesome-icon.png"
-theme.menu_submenu_icon = themes_path .. "default/submenu.png"
+theme.menu_submenu_icon = themes_path .. "zenburn/submenu.png"
-- }}}
-- {{{ Layout