diff --git a/awesomerc.lua b/awesomerc.lua
index eed3dcbf1..f5528a694 100755
--- a/awesomerc.lua
+++ b/awesomerc.lua
@@ -10,6 +10,7 @@ local beautiful = require("beautiful")
-- Notification library
local naughty = require("naughty")
local menubar = require("menubar")
+local hotkeys_popup = require("awful.hotkeys_popup.widget")
-- {{{ Error handling
-- Check if awesome encountered an error during startup and fell back to
@@ -108,6 +109,7 @@ end
-- {{{ Menu
-- Create a laucher widget and a main menu
myawesomemenu = {
+ { "hotkeys", function() return false, hotkeys_popup.show_help end},
{ "manual", terminal .. " -e man awesome" },
{ "edit config", editor_cmd .. " " .. awesome.conffile },
{ "restart", awesome.restart },
@@ -227,6 +229,8 @@ root.buttons(awful.util.table.join(
-- {{{ Key bindings
globalkeys = awful.util.table.join(
+ awful.key({ modkey, }, "s", hotkeys_popup.show_help,
+ {description="show help", group="awesome"}),
awful.key({ modkey, }, "Left", awful.tag.viewprev,
{description = "view previous", group = "tag"}),
awful.key({ modkey, }, "Right", awful.tag.viewnext,
diff --git a/lib/awful/hotkeys_popup/init.lua b/lib/awful/hotkeys_popup/init.lua
new file mode 100644
index 000000000..513dd76ca
--- /dev/null
+++ b/lib/awful/hotkeys_popup/init.lua
@@ -0,0 +1,16 @@
+---------------------------------------------------------------------------
+--- Popup widget which shows current hotkeys and their descriptions.
+--
+-- @author Yauheni Kirylau <yawghen@gmail.com>
+-- @copyright 2014-2015 Yauheni Kirylau
+-- @release @AWESOME_VERSION@
+-- @module awful.hotkeys_popup
+---------------------------------------------------------------------------
+
+
+local hotkeys_popup = {
+ widget = require("awful.hotkeys_popup.widget"),
+ keys = require("awful.hotkeys_popup.keys")
+}
+hotkeys_popup.show_help = hotkeys_popup.widget.show_help
+return hotkeys_popup
diff --git a/lib/awful/hotkeys_popup/keys/init.lua b/lib/awful/hotkeys_popup/keys/init.lua
new file mode 100644
index 000000000..ce3411f35
--- /dev/null
+++ b/lib/awful/hotkeys_popup/keys/init.lua
@@ -0,0 +1,14 @@
+---------------------------------------------------------------------------
+--- Additional hotkeys for awful.hotkeys_widget
+--
+-- @author Yauheni Kirylau <yawghen@gmail.com>
+-- @copyright 2014-2015 Yauheni Kirylau
+-- @release @AWESOME_VERSION@
+-- @module awful.hotkeys_popup.keys
+---------------------------------------------------------------------------
+
+
+local keys = {
+ vim = require("awful.hotkeys_popup.keys.vim")
+}
+return keys
diff --git a/lib/awful/hotkeys_popup/keys/vim.lua b/lib/awful/hotkeys_popup/keys/vim.lua
new file mode 100644
index 000000000..43860a35d
--- /dev/null
+++ b/lib/awful/hotkeys_popup/keys/vim.lua
@@ -0,0 +1,132 @@
+---------------------------------------------------------------------------
+--- VIM hotkeys for awful.hotkeys_widget
+--
+-- @author Yauheni Kirylau <yawghen@gmail.com>
+-- @copyright 2014-2015 Yauheni Kirylau
+-- @release @AWESOME_VERSION@
+-- @module awful.hotkeys_popup.keys.vim
+---------------------------------------------------------------------------
+
+local hotkeys_popup = require("awful.hotkeys_popup.widget")
+
+local vim_rule = {name="vim"}
+for group_name, group_data in pairs({
+ vim_motion= { color="#009F00", rule=vim_rule },
+ vim_command= { color="#aFaF00", rule=vim_rule },
+ vim_command_insert= { color="#cF4F40", rule=vim_rule },
+ vim_operator= { color="#aF6F00", rule=vim_rule },
+}) do
+ hotkeys_popup.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",
+ t=". 'till",
+ ['[']=". misc",
+ [']']=". misc",
+ f=". find char",
+ [';']="repeat t/T/f/F",
+ ["'"]=". goto mk. BOL",
+ b="prev word",
+ n="next word",
+ [',']="reverse t/T/f/F",
+ ['/']=". find",
+ ['~']="toggle case",
+ ["#"]='prev indent',
+ ["$"]='EOL',
+ ["%"]='goto match bracket',
+ ["^"]='"soft" BOL',
+ ["*"]='next indent',
+ ["("]='begin sentence',
+ [")"]='end sentence',
+ ["_"]='"soft" BOL down',
+ ["+"]='next line',
+ W='next WORD',
+ E='end WORD',
+ T=". back 'till",
+ ['{']="begin parag.",
+ ['}']="end parag.",
+ F='. "back" find char',
+ G='EOF/goto line',
+ H='screen top',
+ L='screen bottom',
+ B='prev WORD',
+ N='prev (find)',
+ M='screen middle',
+ ['?']='. find(rev.)',
+ }
+ }},
+
+ vim_operator={{
+ modifiers = {},
+ keys = {
+ ['=']="auto format",
+ y="yank",
+ d="delete",
+ c="change",
+ ["!"]='external filter',
+ ['<']='unindent',
+ ['>']='indent',
+ }
+ }},
+
+ vim_command={{
+ modifiers = {},
+ keys = {
+ q=". record macro",
+ r=". replace char",
+ u="undo",
+ p="paste after",
+ g="gg: top of file, gf: open file here",
+ z="zt: cursor to top, zb: bottom, zz: center",
+ 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',
+ D='delete to EOL',
+ J='join lines',
+ K='help',
+ [':']='ex cmd line',
+ ['"']='. register spec',
+ ["|"]='BOL/goto col',
+ Z='quit and ZZ:save or ZQ:not',
+ X='back-delete',
+ V='visual lines',
+ }
+ }},
+
+ 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',
+ }
+ }},
+}
+
+hotkeys_popup.add_hotkeys(vim_keys)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/hotkeys_popup/widget.lua b/lib/awful/hotkeys_popup/widget.lua
new file mode 100644
index 000000000..3785faaa8
--- /dev/null
+++ b/lib/awful/hotkeys_popup/widget.lua
@@ -0,0 +1,409 @@
+---------------------------------------------------------------------------
+--- Popup widget which shows current hotkeys and their descriptions.
+--
+-- @author Yauheni Kirylau <yawghen@gmail.com>
+-- @copyright 2014-2015 Yauheni Kirylau
+-- @release @AWESOME_VERSION@
+-- @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 '' .. tostring(text) ..''
+end
+-- Set the foreground.
+function markup.fg(color, text)
+ return '' .. tostring(text) .. ''
+end
+-- Set the background.
+function markup.bg(color, text)
+ return '' .. tostring(text) .. ''
+end
+
+
+local widget = {
+ hide_without_description = true,
+ 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),
+ group_rules = {},
+ 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)
+ if joined_mods == "none" then
+ joined_mods = ""
+ else
+ joined_mods = markup.fg(widget.modifiers_color, joined_mods.."+")
+ end
+
+ local group = data.group or "none"
+ group_list[group] = true
+ if not target[group] then target[group] = {} end
+ table.insert(
+ target[group],
+ {hotkey= joined_mods .. (widget.labels[key] or key),
+ description=data.description}
+ )
+end
+
+
+local function sort_hotkeys(target)
+ -- @TODO: add sort by 12345qwertyasdf etc
+ for group, _ in pairs(group_list) do
+ if target[group] then
+ table.sort(
+ target[group],
+ function(a,b) return a.hotkey 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 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.layout.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 then
+ if not c or not awful.rules.match(c, data.rule) 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][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.
+-- @tparam[opt] bool nosort Do not sort hotkeys alphabetically.
+function widget.add_hotkeys(hotkeys, nosort)
+ 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
+ if not nosort then
+ sort_hotkeys(widget.additional_hotkeys)
+ end
+end
+
+return widget
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/menu.lua b/lib/awful/menu.lua
index d0efbe34d..ea515e6cd 100644
--- a/lib/awful/menu.lua
+++ b/lib/awful/menu.lua
@@ -47,12 +47,6 @@ local table_update = function (t, set)
return t
end
-local table_merge = function (t, set)
- for _, v in ipairs(set) do
- table.insert(t, v)
- end
-end
-
--- Key bindings for menu navigation.
-- Keys are: up, down, exec, enter, back, close. Value are table with a list of valid
@@ -484,15 +478,15 @@ function menu.clients(args, item_args)
c.icon }
if item_args then
if type(item_args) == "function" then
- table_merge(cls_t[#cls_t], item_args(c))
+ util.table.merge(cls_t[#cls_t], item_args(c))
else
- table_merge(cls_t[#cls_t], item_args)
+ util.table.merge(cls_t[#cls_t], item_args)
end
end
end
args = args or {}
args.items = args.items or {}
- table_merge(args.items, cls_t)
+ util.table.merge(args.items, cls_t)
local m = menu.new(args)
m:show(args)
diff --git a/lib/awful/util.lua b/lib/awful/util.lua
index ca54037d4..665953f15 100644
--- a/lib/awful/util.lua
+++ b/lib/awful/util.lua
@@ -385,9 +385,9 @@ end
-- @param indent Number of spaces added before each wrapped line. Default: 0.
-- @return The string with lines wrapped to width.
function util.linewrap(text, width, indent)
- local text = text or ""
- local width = width or 72
- local indent = indent or 0
+ text = text or ""
+ width = width or 72
+ indent = indent or 0
local pos = 1
return text:gsub("(%s+)()(%S+)()",
@@ -399,6 +399,13 @@ function util.linewrap(text, width, indent)
end)
end
+--- Count number of lines in a string
+-- @tparam string text Input string.
+-- @treturn int Number of lines.
+function util.linecount(text)
+ return select(2, text:gsub('\n', '\n')) + 1
+end
+
--- Get a sorted table with all integer keys from a table
-- @param t the table for which the keys to get
-- @return A table with keys
@@ -489,6 +496,17 @@ function util.table.iterate(t, filter, start)
end
end
+
+--- Merge items from the one table to another one
+-- @tparam table t the container table
+-- @tparam table set the mixin table
+function util.table.merge(t, set)
+ for _, v in ipairs(set) do
+ table.insert(t, v)
+ end
+end
+
+
-- Escape all special pattern-matching characters so that lua interprets them
-- literally instead of as a character class.
-- Source: http://stackoverflow.com/a/20778724/15690
diff --git a/lib/menubar/utils.lua b/lib/menubar/utils.lua
index 5c9e1e08b..8025fdb86 100644
--- a/lib/menubar/utils.lua
+++ b/lib/menubar/utils.lua
@@ -254,12 +254,20 @@ function utils.parse_dir(dir)
return programs
end
+--- Compute textbox width.
+-- @tparam wibox.widget.textbox textbox Textbox instance.
+-- @treturn int Text width.
+function utils.compute_textbox_width(textbox, s)
+ s = s or mouse.screen
+ local w, h = textbox:get_preferred_size(s)
+ return w
+end
+
--- Compute text width.
-- @tparam str text Text.
-- @treturn int Text width.
-function utils.compute_text_width(text)
- local _, logical = wibox.widget.textbox(awful_util.escape(text))._layout:get_pixel_extents()
- return logical.width
+function utils.compute_text_width(text, s)
+ return utils.compute_textbox_width(wibox.widget.textbox(awful_util.escape(text)), s)
end
return utils