summaryrefslogtreecommitdiff
path: root/awesome/lib/awful/hotkeys_popup/widget.lua
diff options
context:
space:
mode:
Diffstat (limited to 'awesome/lib/awful/hotkeys_popup/widget.lua')
-rw-r--r--awesome/lib/awful/hotkeys_popup/widget.lua482
1 files changed, 482 insertions, 0 deletions
diff --git a/awesome/lib/awful/hotkeys_popup/widget.lua b/awesome/lib/awful/hotkeys_popup/widget.lua
new file mode 100644
index 0000000..62a2231
--- /dev/null
+++ b/awesome/lib/awful/hotkeys_popup/widget.lua
@@ -0,0 +1,482 @@
+---------------------------------------------------------------------------
+--- Popup widget which shows current hotkeys and their descriptions.
+--
+-- @author Yauheni Kirylau <yawghen@gmail.com>
+-- @copyright 2014-2015 Yauheni Kirylau
+-- @module awful.hotkeys_popup.widget
+---------------------------------------------------------------------------
+
+local capi = {
+ screen = screen,
+ client = client,
+ keygrabber = keygrabber,
+}
+local awful = require("awful")
+local wibox = require("wibox")
+local beautiful = require("beautiful")
+local dpi = beautiful.xresources.apply_dpi
+local compute_textbox_width = require("menubar").utils.compute_textbox_width
+
+
+-- Stripped copy of this module https://github.com/copycat-killer/lain/blob/master/util/markup.lua:
+local markup = {}
+-- Set the font.
+function markup.font(font, text)
+ return '<span font="' .. tostring(font) .. '">' .. tostring(text) ..'</span>'
+end
+-- Set the foreground.
+function markup.fg(color, text)
+ return '<span foreground="' .. tostring(color) .. '">' .. tostring(text) .. '</span>'
+end
+-- Set the background.
+function markup.bg(color, text)
+ return '<span background="' .. tostring(color) .. '">' .. tostring(text) .. '</span>'
+end
+
+local widget_module = {
+ group_rules = {},
+}
+
+--- Don't show hotkeys without descriptions.
+widget_module.hide_without_description = true
+
+--- Merge hotkey records into one if they have the same modifiers and
+-- description.
+widget_module.merge_duplicates = true
+
+
+function widget_module.new()
+local widget = {
+ hide_without_description = widget_module.hide_without_description,
+ merge_duplicates = widget_module.merge_duplicates,
+ group_rules = awful.util.table.clone(widget_module.group_rules),
+ title_font = "Monospace Bold 9",
+ description_font = "Monospace 8",
+ width = dpi(1200),
+ height = dpi(800),
+ border_width = beautiful.border_width or dpi(2),
+ modifiers_color = beautiful.bg_minimize or "#555555",
+ group_margin = dpi(6),
+ additional_hotkeys = {},
+ labels = {
+ Mod4="Super",
+ Mod1="Alt",
+ Escape="Esc",
+ Insert="Ins",
+ Delete="Del",
+ Backspace="BackSpc",
+ Return="Enter",
+ Next="PgDn",
+ Prior="PgUp",
+ ['#108']="Alt Gr",
+ Left='←',
+ Up='↑',
+ Right='→',
+ Down='↓',
+ ['#67']="F1",
+ ['#68']="F2",
+ ['#69']="F3",
+ ['#70']="F4",
+ ['#71']="F5",
+ ['#72']="F6",
+ ['#73']="F7",
+ ['#74']="F8",
+ ['#75']="F9",
+ ['#76']="F10",
+ ['#95']="F11",
+ ['#96']="F12",
+ ['#10']="1",
+ ['#11']="2",
+ ['#12']="3",
+ ['#13']="4",
+ ['#14']="5",
+ ['#15']="6",
+ ['#16']="7",
+ ['#17']="8",
+ ['#18']="9",
+ ['#19']="0",
+ ['#20']="-",
+ ['#21']="=",
+ Control="Ctrl"
+ },
+}
+
+local cached_wiboxes = {}
+local cached_awful_keys = nil
+local colors_counter = {}
+local colors = beautiful.xresources.get_current_theme()
+local group_list = {}
+
+
+local function get_next_color(id)
+ id = id or "default"
+ if colors_counter[id] then
+ colors_counter[id] = math.fmod(colors_counter[id] + 1, 15) + 1
+ else
+ colors_counter[id] = 1
+ end
+ return colors["color"..tostring(colors_counter[id], 15)]
+end
+
+
+local function join_plus_sort(modifiers)
+ if #modifiers<1 then return "none" end
+ table.sort(modifiers)
+ return table.concat(modifiers, '+')
+end
+
+
+local function add_hotkey(key, data, target)
+ if widget.hide_without_description and not data.description then return end
+
+ local readable_mods = {}
+ for _, mod in ipairs(data.mod) do
+ table.insert(readable_mods, widget.labels[mod] or mod)
+ end
+ local joined_mods = join_plus_sort(readable_mods)
+
+ local group = data.group or "none"
+ group_list[group] = true
+ if not target[group] then target[group] = {} end
+ local new_key = {
+ key = (widget.labels[key] or key),
+ mod = joined_mods,
+ description = data.description
+ }
+ local index = data.description or "none" -- or use its hash?
+ if not target[group][index] then
+ target[group][index] = new_key
+ else
+ if widget.merge_duplicates and joined_mods == target[group][index].mod then
+ target[group][index].key = target[group][index].key .. "/" .. new_key.key
+ else
+ while target[group][index] do
+ index = index .. " "
+ end
+ target[group][index] = new_key
+ end
+ end
+end
+
+
+local function sort_hotkeys(target)
+ -- @TODO: add sort by 12345qwertyasdf etc
+ for group, _ in pairs(group_list) do
+ if target[group] then
+ local sorted_table = {}
+ for _, key in pairs(target[group]) do
+ table.insert(sorted_table, key)
+ end
+ table.sort(
+ sorted_table,
+ function(a,b) return (a.mod or '')..a.key<(b.mod or '')..b.key end
+ )
+ target[group] = sorted_table
+ end
+ end
+end
+
+
+local function import_awful_keys()
+ if cached_awful_keys then
+ return
+ end
+ cached_awful_keys = {}
+ for _, data in pairs(awful.key.hotkeys) do
+ add_hotkey(data.key, data, cached_awful_keys)
+ end
+ sort_hotkeys(cached_awful_keys)
+end
+
+
+local function group_label(group, color)
+ local textbox = wibox.widget.textbox(
+ markup.font(widget.title_font,
+ markup.bg(
+ color or (widget.group_rules[group] and
+ widget.group_rules[group].color or get_next_color("group_title")
+ ),
+ markup.fg(beautiful.bg_normal or "#000000", " "..group.." ")
+ )
+ )
+ )
+ local margin = wibox.container.margin()
+ margin:set_widget(textbox)
+ margin:set_top(widget.group_margin)
+ return margin
+end
+
+local function get_screen(s)
+ return s and capi.screen[s]
+end
+
+local function create_wibox(s, available_groups)
+ s = get_screen(s)
+
+ local wa = s.workarea
+ local height = (widget.height < wa.height) and widget.height or
+ (wa.height - widget.border_width * 2)
+ local width = (widget.width < wa.width) and widget.width or
+ (wa.width - widget.border_width * 2)
+
+ -- arrange hotkey groups into columns
+ local line_height = beautiful.get_font_height(widget.title_font)
+ local group_label_height = line_height + widget.group_margin
+ -- -1 for possible pagination:
+ local max_height_px = height - group_label_height
+ local column_layouts = {}
+ for _, group in ipairs(available_groups) do
+ local keys = cached_awful_keys[group] or widget.additional_hotkeys[group]
+ local joined_descriptions = ""
+ for i, key in ipairs(keys) do
+ joined_descriptions = joined_descriptions .. key.description .. (i~=#keys and "\n" or "")
+ end
+ -- +1 for group label:
+ local items_height = awful.util.linecount(joined_descriptions) * line_height + group_label_height
+ local current_column
+ local available_height_px = max_height_px
+ local add_new_column = true
+ for i, column in ipairs(column_layouts) do
+ if ((column.height_px + items_height) < max_height_px) or
+ (i == #column_layouts and column.height_px < max_height_px / 2)
+ then
+ current_column = column
+ add_new_column = false
+ available_height_px = max_height_px - current_column.height_px
+ break
+ end
+ end
+ local overlap_leftovers
+ if items_height > available_height_px then
+ local new_keys = {}
+ overlap_leftovers = {}
+ -- +1 for group title and +1 for possible hyphen (v):
+ local available_height_items = (available_height_px - group_label_height*2) / line_height
+ for i=1,#keys do
+ table.insert(((i<available_height_items) and new_keys or overlap_leftovers), keys[i])
+ end
+ keys = new_keys
+ table.insert(keys, {key=markup.fg(widget.modifiers_color, "▽"), description=""})
+ end
+ if not current_column then
+ current_column = {layout=wibox.layout.fixed.vertical()}
+ end
+ current_column.layout:add(group_label(group))
+
+ local function insert_keys(_keys, _add_new_column)
+ local max_label_width = 0
+ local max_label_content = ""
+ local joined_labels = ""
+ for i, key in ipairs(_keys) do
+ local length = string.len(key.key or '') + string.len(key.description or '')
+ local modifiers = key.mod
+ if not modifiers or modifiers == "none" then
+ modifiers = ""
+ else
+ length = length + string.len(modifiers) + 1 -- +1 for "+" character
+ modifiers = markup.fg(widget.modifiers_color, modifiers.."+")
+ end
+ local rendered_hotkey = markup.font(widget.title_font,
+ modifiers .. (key.key or "") .. " "
+ ) .. markup.font(widget.description_font,
+ key.description or ""
+ )
+ if length > max_label_width then
+ max_label_width = length
+ max_label_content = rendered_hotkey
+ end
+ joined_labels = joined_labels .. rendered_hotkey .. (i~=#_keys and "\n" or "")
+ end
+ current_column.layout:add(wibox.widget.textbox(joined_labels))
+ local max_width = compute_textbox_width(wibox.widget.textbox(max_label_content), s) +
+ widget.group_margin
+ if not current_column.max_width or max_width > current_column.max_width then
+ current_column.max_width = max_width
+ end
+ -- +1 for group label:
+ current_column.height_px = (current_column.height_px or 0) +
+ awful.util.linecount(joined_labels)*line_height + group_label_height
+ if _add_new_column then
+ table.insert(column_layouts, current_column)
+ end
+ end
+
+ insert_keys(keys, add_new_column)
+ if overlap_leftovers then
+ current_column = {layout=wibox.layout.fixed.vertical()}
+ insert_keys(overlap_leftovers, true)
+ end
+ end
+
+ -- arrange columns into pages
+ local available_width_px = width
+ local pages = {}
+ local columns = wibox.layout.fixed.horizontal()
+ for _, item in ipairs(column_layouts) do
+ if item.max_width > available_width_px then
+ columns.widgets[#columns.widgets]['widget']:add(
+ group_label("PgDn - Next Page", beautiful.fg_normal)
+ )
+ table.insert(pages, columns)
+ columns = wibox.layout.fixed.horizontal()
+ available_width_px = width - item.max_width
+ local old_widgets = item.layout.widgets
+ item.layout.widgets = {group_label("PgUp - Prev Page", beautiful.fg_normal)}
+ awful.util.table.merge(item.layout.widgets, old_widgets)
+ else
+ available_width_px = available_width_px - item.max_width
+ end
+ local column_margin = wibox.container.margin()
+ column_margin:set_widget(item.layout)
+ column_margin:set_left(widget.group_margin)
+ columns:add(column_margin)
+ end
+ table.insert(pages, columns)
+
+ local mywibox = wibox({
+ ontop = true,
+ opacity = beautiful.notification_opacity or 1,
+ border_width = widget.border_width,
+ border_color = beautiful.fg_normal,
+ })
+ mywibox:geometry({
+ x = wa.x + math.floor((wa.width - width - widget.border_width*2) / 2),
+ y = wa.y + math.floor((wa.height - height - widget.border_width*2) / 2),
+ width = width,
+ height = height,
+ })
+ mywibox:set_widget(pages[1])
+ mywibox:buttons(awful.util.table.join(
+ awful.button({ }, 1, function () mywibox.visible=false end),
+ awful.button({ }, 3, function () mywibox.visible=false end)
+ ))
+
+ local widget_obj = {}
+ widget_obj.current_page = 1
+ widget_obj.wibox = mywibox
+ function widget_obj:page_next()
+ if self.current_page == #pages then return end
+ self.current_page = self.current_page + 1
+ self.wibox:set_widget(pages[self.current_page])
+ end
+ function widget_obj:page_prev()
+ if self.current_page == 1 then return end
+ self.current_page = self.current_page - 1
+ self.wibox:set_widget(pages[self.current_page])
+ end
+ function widget_obj:show()
+ self.wibox.visible = true
+ end
+ function widget_obj:hide()
+ self.wibox.visible = false
+ end
+
+ return widget_obj
+end
+
+
+--- Show popup with hotkeys help.
+-- @tparam[opt] client c Client.
+-- @tparam[opt] screen s Screen.
+function widget.show_help(c, s)
+ import_awful_keys()
+ c = c or capi.client.focus
+ s = s or (c and c.screen or awful.screen.focused())
+
+ local available_groups = {}
+ for group, _ in pairs(group_list) do
+ local need_match
+ for group_name, data in pairs(widget.group_rules) do
+ if group_name==group and (
+ data.rule or data.rule_any or data.except or data.except_any
+ ) then
+ if not c or not awful.rules.matches(c, {
+ rule=data.rule,
+ rule_any=data.rule_any,
+ except=data.except,
+ except_any=data.except_any
+ }) then
+ need_match = true
+ break
+ end
+ end
+ end
+ if not need_match then table.insert(available_groups, group) end
+ end
+
+ local joined_groups = join_plus_sort(available_groups)
+ if not cached_wiboxes[s] then
+ cached_wiboxes[s] = {}
+ end
+ if not cached_wiboxes[s][joined_groups] then
+ cached_wiboxes[s][joined_groups] = create_wibox(s, available_groups)
+ end
+ local help_wibox = cached_wiboxes[s][joined_groups]
+ help_wibox:show()
+
+ return capi.keygrabber.run(function(_, key, event)
+ if event == "release" then return end
+ if key then
+ if key == "Next" then
+ help_wibox:page_next()
+ elseif key == "Prior" then
+ help_wibox:page_prev()
+ else
+ capi.keygrabber.stop()
+ help_wibox:hide()
+ end
+ end
+ end)
+end
+
+
+--- Add hotkey descriptions for third-party applications.
+-- @tparam table hotkeys Table with bindings,
+-- see `awful.hotkeys_popup.key.vim` as an example.
+function widget.add_hotkeys(hotkeys)
+ for group, bindings in pairs(hotkeys) do
+ for _, binding in ipairs(bindings) do
+ local modifiers = binding.modifiers
+ local keys = binding.keys
+ for key, description in pairs(keys) do
+ add_hotkey(key, {
+ mod=modifiers,
+ description=description,
+ group=group},
+ widget.additional_hotkeys
+ )
+ end
+ end
+ end
+ sort_hotkeys(widget.additional_hotkeys)
+end
+
+
+return widget
+end
+
+local function get_default_widget()
+ if not widget_module.default_widget then
+ widget_module.default_widget = widget_module.new()
+ end
+ return widget_module.default_widget
+end
+
+--- Show popup with hotkeys help (default widget instance will be used).
+-- @tparam[opt] client c Client.
+-- @tparam[opt] screen s Screen.
+function widget_module.show_help(...)
+ return get_default_widget().show_help(...)
+end
+
+--- Add hotkey descriptions for third-party applications
+-- (default widget instance will be used).
+-- @tparam table hotkeys Table with bindings,
+-- see `awful.hotkeys_popup.key.vim` as an example.
+function widget_module.add_hotkeys(...)
+ return get_default_widget().add_hotkeys(...)
+end
+
+return widget_module
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80