--------------------------------------------------------------------------- --- 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