diff options
author | ache <ache@ache.one> | 2017-03-13 23:17:19 +0100 |
---|---|---|
committer | ache <ache@ache.one> | 2017-03-13 23:17:19 +0100 |
commit | 22d656903563f75678f3634964731ccf93355dfd (patch) | |
tree | e3cb6279d95c9764093072d5e946566ea6533799 /lib/shifty.lua |
Init commit
Diffstat (limited to 'lib/shifty.lua')
-rw-r--r-- | lib/shifty.lua | 1138 |
1 files changed, 1138 insertions, 0 deletions
diff --git a/lib/shifty.lua b/lib/shifty.lua new file mode 100644 index 0000000..9320295 --- /dev/null +++ b/lib/shifty.lua @@ -0,0 +1,1138 @@ +--- Shifty: Dynamic tagging library, version for awesome v3.5 +-- @author koniu <gkusnierz@gmail.com> +-- @author resixian (aka bioe007) <resixian@gmail.com> +-- @author cdump <andreevmaxim@gmail.com> +-- +-- https://github.com/cdump/awesome-shifty +-- http://awesome.naquadah.org/wiki/index.php?title=Shifty + +-- environment +local type = type +local ipairs = ipairs +local table = table +local string = string +local beautiful = require("beautiful") +local awful = require("awful") +local wibox = require("wibox") +local pairs = pairs +local io = io +local math = math +local tonumber = tonumber +local dbg= dbg +local capi = { + client = client, + tag = tag, + screen = screen, + button = button, + mouse = mouse, + root = root, + timer = timer +} + +local shifty = {} + +-- variables +shifty.config = {} +shifty.config.tags = {} +shifty.config.apps = {} +shifty.config.defaults = {} +shifty.config.float_bars = false +shifty.config.guess_name = true +shifty.config.guess_position = true +shifty.config.remember_index = true +shifty.config.sloppy = true +shifty.config.default_name = "new" +shifty.config.clientkeys = {} +shifty.config.globalkeys = nil +shifty.config.layouts = {} +shifty.config.prompt_sources = { + "config_tags", + "config_apps", + "existing", + "history" +} +shifty.config.prompt_matchers = { + "^", + ":", + "" +} +shifty.config.delete_deserted = true + +local matchp = "" +local index_cache = {} +for i = 1, capi.screen.count() do index_cache[i] = {} end + +--name2tags: matches string 'name' to tag objects +-- @param name : tag name to find +-- @param scr : screen to look for tags on +-- @return table of tag objects or nil +function name2tags(name, scr) + local ret = {} + local a, b = scr or 1, scr or capi.screen.count() + for s = a, b do + for i, t in ipairs(awful.tag.gettags(s)) do + if name == t.name then + table.insert(ret, t) + end + end + end + if #ret > 0 then return ret end +end + +function name2tag(name, scr, idx) + local ts = name2tags(name, scr) + if ts then return ts[idx or 1] end +end + +--tag2index: finds index of a tag object +-- @param scr : screen number to look for tag on +-- @param tag : the tag object to find +-- @return the index [or zero] or end of the list +function tag2index(scr, tag) + for i, t in ipairs(awful.tag.gettags(scr)) do + if t == tag then return i end + end +end + +--rename +--@param tag: tag object to be renamed +--@param prefix: if any prefix is to be added +--@param no_selectall: +function shifty.rename(tag, prefix, no_selectall) + local theme = beautiful.get() + local t = tag or awful.tag.selected(capi.mouse.screen) + + if t == nil then return end + + local scr = awful.tag.getscreen(t) + local bg = nil + local fg = nil + local text = prefix or t.name + local before = t.name + + if t == awful.tag.selected(scr) then + bg = theme.bg_focus or '#535d6c' + fg = theme.fg_urgent or '#ffffff' + else + bg = theme.bg_normal or '#222222' + fg = theme.fg_urgent or '#ffffff' + end + + local tag_index = tag2index(scr, t) + -- Access to textbox widget in taglist + local tb_widget = shifty.taglist[scr].widgets[tag_index].widget.widgets[2].widget + awful.prompt.run({ + fg_cursor = fg, bg_cursor = bg, ul_cursor = "single", + text = text, selectall = not no_selectall}, + tb_widget, + function (name) if name:len() > 0 then t.name = name; end end, + completion, + awful.util.getdir("cache") .. "/history_tags", + nil, + function () + if t.name == before then + if awful.tag.getproperty(t, "initial") then shifty.del(t) end + else + awful.tag.setproperty(t, "initial", true) + set(t) + end + tagkeys(capi.screen[scr]) + t:emit_signal("property::name") + end + ) +end + +--send: moves client to tag[idx] +-- maybe this isn't needed here in shifty? +-- @param idx the tag number to send a client to +function send(idx) + local scr = capi.client.focus.screen or capi.mouse.screen + local sel = awful.tag.selected(scr) + local sel_idx = tag2index(scr, sel) + local tags = awful.tag.gettags(scr) + local target = awful.util.cycle(#tags, sel_idx + idx) + awful.client.movetotag(tags[target], capi.client.focus) + awful.tag.viewonly(tags[target]) +end + +function shifty.send_next() send(1) end +function shifty.send_prev() send(-1) end + +--pos2idx: translate shifty position to tag index +--@param pos: position (an integer) +--@param scr: screen number +function pos2idx(pos, scr) + local v = 1 + if pos and scr then + local tags = awful.tag.gettags(scr) + for i = #tags , 1, -1 do + local t = tags[i] + if awful.tag.getproperty(t, "position") and + awful.tag.getproperty(t, "position") <= pos then + v = i + 1 + break + end + end + end + return v +end + +--select : helper function chooses the first non-nil argument +--@param args - table of arguments +local function select(args) + for i, a in pairs(args) do + if a ~= nil then + return a + end + end +end + +--tagtoscr : move an entire tag to another screen +-- +--@param scr : the screen to move tag to +--@param t : the tag to be moved [awful.tag.selected()] +--@return the tag +function shifty.tagtoscr(scr, t) + -- break if called with an invalid screen number + if not scr or scr < 1 or scr > capi.screen.count() then return end + -- tag to move + local otag = t or awful.tag.selected() + + awful.tag.setscreen(otag, scr) + -- set screen and then reset tag to order properly + if #otag:clients() > 0 then + for _ , c in ipairs(otag:clients()) do + if not c.sticky then + c.screen = scr + c:tags({otag}) + else + awful.client.toggletag(otag, c) + end + end + end + return otag +end + +--set : set a tags properties +--@param t: the tag +--@param args : a table of optional (?) tag properties +--@return t - the tag object +function set(t, args) + if not t then return end + if not args then args = {} end + + -- set the name + t.name = args.name or t.name + + -- attempt to load preset on initial run + local preset = (awful.tag.getproperty(t, "initial") and + shifty.config.tags[t.name]) or {} + + -- pick screen and get its tag table + local scr = args.screen or + (not awful.tag.getscreen(t) and awful.tag.getscreen(preset)) or + awful.tag.getscreen(t) or + capi.mouse.screen + + local clientstomove = nil + if scr > capi.screen.count() then scr = capi.screen.count() end + if awful.tag.getscreen(t) and scr ~= awful.tag.getscreen(t) then + shifty.tagtoscr(scr, t) + awful.tag.setscreen(t, nil) + end + local tags = awful.tag.gettags(scr) + + -- try to guess position from the name + local guessed_position = nil + if not (args.position or preset.position) and shifty.config.guess_position then + local num = t.name:find('^[1-9]') + if num then guessed_position = tonumber(t.name:sub(1, 1)) end + end + + -- allow preset.layout to be a table to provide a different layout per + -- screen for a given tag + local preset_layout = preset.layout + if preset_layout and preset_layout[scr] then + preset_layout = preset.layout[scr] + end + + -- select from args, preset, getproperty, + -- config.defaults.configs or defaults + local props = { + layout = select{args.layout, preset_layout, + awful.tag.getproperty(t, "layout"), + shifty.config.defaults.layout, awful.layout.suit.tile}, + mwfact = select{args.mwfact, preset.mwfact, + awful.tag.getproperty(t, "mwfact"), + shifty.config.defaults.mwfact, 0.55}, + nmaster = select{args.nmaster, preset.nmaster, + awful.tag.getproperty(t, "nmaster"), + shifty.config.defaults.nmaster, 1}, + ncol = select{args.ncol, preset.ncol, + awful.tag.getproperty(t, "ncol"), + shifty.config.defaults.ncol, 1}, + matched = select{args.matched, awful.tag.getproperty(t, "matched")}, + exclusive = select{args.exclusive, preset.exclusive, + awful.tag.getproperty(t, "exclusive"), + shifty.config.defaults.exclusive}, + persist = select{args.persist, preset.persist, + awful.tag.getproperty(t, "persist"), + shifty.config.defaults.persist}, + nopopup = select{args.nopopup, preset.nopopup, + awful.tag.getproperty(t, "nopopup"), + shifty.config.defaults.nopopup}, + leave_kills = select{args.leave_kills, preset.leave_kills, + awful.tag.getproperty(t, "leave_kills"), + shifty.config.defaults.leave_kills}, + max_clients = select{args.max_clients, preset.max_clients, + awful.tag.getproperty(t, "max_clients"), + shifty.config.defaults.max_clients}, + position = select{args.position, preset.position, guessed_position, + awful.tag.getproperty(t, "position")}, + icon = select{args.icon and args.icon, + preset.icon and preset.icon, + awful.tag.getproperty(t, "icon"), + shifty.config.defaults.icon and shifty.config.defaults.icon}, + icon_only = select{args.icon_only, preset.icon_only, + awful.tag.getproperty(t, "icon_only"), + shifty.config.defaults.icon_only}, + sweep_delay = select{args.sweep_delay, preset.sweep_delay, + awful.tag.getproperty(t, "sweep_delay"), + shifty.config.defaults.sweep_delay}, + overload_keys = select{args.overload_keys, preset.overload_keys, + awful.tag.getproperty(t, "overload_keys"), + shifty.config.defaults.overload_keys}, + } + + -- get layout by name if given as string + if type(props.layout) == "string" then + props.layout = getlayout(props.layout) + end + + -- set keys + if args.keys or preset.keys then + local keys = awful.util.table.join(shifty.config.globalkeys, + args.keys or preset.keys) + if props.overload_keys then + props.keys = keys + else + props.keys = squash_keys(keys) + end + end + + -- calculate desired taglist index + local index = args.index or preset.index or shifty.config.defaults.index + local rel_index = args.rel_index or + preset.rel_index or + shifty.config.defaults.rel_index + local sel = awful.tag.selected(scr) + --TODO: what happens with rel_idx if no tags selected + local sel_idx = (sel and tag2index(scr, sel)) or 0 + local t_idx = tag2index(scr, t) + local limit = (not t_idx and #tags + 1) or #tags + local idx = nil + + if rel_index then + idx = awful.util.cycle(limit, (t_idx or sel_idx) + rel_index) + elseif index then + idx = awful.util.cycle(limit, index) + elseif props.position then + idx = pos2idx(props.position, scr) + if t_idx and t_idx < idx then idx = idx - 1 end + elseif shifty.config.remember_index and index_cache[scr][t.name] then + idx = math.min(index_cache[scr][t.name], #tags+1) + elseif not t_idx then + idx = #tags + 1 + end + + -- if we have a new index, remove from old index and insert + if idx then + if t_idx then table.remove(tags, t_idx) end + table.insert(tags, idx, t) + index_cache[scr][t.name] = idx + end + + -- set tag properties and push the new tag table + for i, tmp_tag in ipairs(tags) do + awful.tag.setscreen(tmp_tag, scr) + awful.tag.setproperty(tmp_tag, "index", i) + end + for prop, val in pairs(props) do awful.tag.setproperty(t, prop, val) end + + -- execute run/spawn + if awful.tag.getproperty(t, "initial") then + local spawn = args.spawn or preset.spawn or shifty.config.defaults.spawn + local run = args.run or preset.run or shifty.config.defaults.run + if spawn and args.matched ~= true then + awful.util.spawn_with_shell(spawn, scr) + end + if run then run(t) end + awful.tag.setproperty(t, "initial", nil) + end + + + return t +end + +function shift_next() set(awful.tag.selected(), {rel_index = 1}) end +function shift_prev() set(awful.tag.selected(), {rel_index = -1}) end + +--add : adds a tag +--@param args: table of optional arguments +function shifty.add(args) + if not args then args = {} end + local name = args.name or " " + + -- initialize a new tag object and its data structure + local t = awful.tag.add(name, { initial = true }) + + + -- apply tag settings + set(t, args) + + -- unless forbidden or if first tag on the screen, show the tag + if not (awful.tag.getproperty(t, "nopopup") or args.noswitch) or + #awful.tag.gettags(awful.tag.getscreen(t)) == 1 then + awful.tag.viewonly(t) + end + + -- get the name or rename + if args.name then + t.name = args.name + else + -- FIXME: hack to delay rename for un-named tags for + -- tackling taglist refresh which disabled prompt + -- from being rendered until input + awful.tag.setproperty(t, "initial", true) + local f + local tmr + if args.position then + f = function() shifty.rename(t, args.rename, true); tmr:stop() end + else + f = function() shifty.rename(t); tmr:stop() end + end + tmr = capi.timer({timeout = 0.01}) + tmr:connect_signal("timeout", f) + tmr:start() + end + + return t +end + +--del : delete a tag +--@param tag : the tag to be deleted [current tag] +function shifty.del(tag) + local scr = (tag and awful.tag.getscreen(tag)) or capi.mouse.screen or 1 + local tags = awful.tag.gettags(scr) + local sel = awful.tag.selected(scr) + local t = tag or sel + local idx = tag2index(scr, t) + + -- return if tag not empty (except sticky) + local clients = t:clients() + local sticky = 0 + for i, c in ipairs(clients) do + if c.sticky then sticky = sticky + 1 end + end + if #clients > sticky then return end + + -- store index for later + index_cache[scr][t.name] = idx + + -- remove tag + awful.tag.delete(t) + + -- if the current tag is being deleted, restore from history + if t == sel and #tags > 1 then + awful.tag.history.restore(scr, 1) + -- this is supposed to cycle if history is invalid? + -- e.g. if many tags are deleted in a row + if not awful.tag.selected(scr) then + awful.tag.viewonly(tags[awful.util.cycle(#tags, idx - 1)]) + end + end + + -- FIXME: what is this for?? + if capi.client.focus then capi.client.focus:raise() end +end + +--is_client_tagged : replicate behavior in tag.c - returns true if the +--given client is tagged with the given tag +function is_client_tagged(tag, client) + for i, c in ipairs(tag:clients()) do + if c == client then + return true + end + end + return false +end + +--match : handles app->tag matching, a replacement for the manage hook in +-- rc.lua +--@param c : client to be matched +function match(c, startup) + local nopopup, intrusive, nofocus, run, slave + local wfact, struts, geom, float + local target_tag_names, target_tags = {}, {} + local typ = c.type + local cls = c.class + local inst = c.instance + local role = c.role + local name = c.name + local keys = shifty.config.clientkeys or c:keys() or {} + local target_screen = capi.mouse.screen + + c.border_color = beautiful.border_normal + c.border_width = beautiful.border_width + + -- try matching client to config.apps + for i, a in ipairs(shifty.config.apps) do + if a.match then + local matched = false + -- match only class + if not matched and cls and a.match.class then + for k, w in ipairs(a.match.class) do + matched = cls:find(w) + if matched then + break + end + end + end + -- match only instance + if not matched and inst and a.match.instance then + for k, w in ipairs(a.match.instance) do + matched = inst:find(w) + if matched then + break + end + end + end + -- match only name + if not matched and name and a.match.name then + for k, w in ipairs(a.match.name) do + matched = name:find(w) + if matched then + break + end + end + end + -- match only role + if not matched and role and a.match.role then + for k, w in ipairs(a.match.role) do + matched = role:find(w) + if matched then + break + end + end + end + -- match only type + if not matched and typ and a.match.type then + for k, w in ipairs(a.match.type) do + matched = typ:find(w) + if matched then + break + end + end + end + -- check everything else against all attributes + if not matched then + for k, w in ipairs(a.match) do + matched = (cls and cls:find(w)) or + (inst and inst:find(w)) or + (name and name:find(w)) or + (role and role:find(w)) or + (typ and typ:find(w)) + if matched then + break + end + end + end + -- set attributes + if matched then + if a.screen then target_screen = a.screen end + if a.tag then + if type(a.tag) == "string" then + target_tag_names = {a.tag} + else + target_tag_names = a.tag + end + end + if a.startup and startup then + a = awful.util.table.join(a, a.startup) + end + if a.geometry ~=nil then + geom = {x = a.geometry[1], + y = a.geometry[2], + width = a.geometry[3], + height = a.geometry[4]} + end + if a.float ~= nil then float = a.float end + if a.slave ~=nil then slave = a.slave end + if a.border_width ~= nil then + c.border_width = a.border_width + end + if a.nopopup ~=nil then nopopup = a.nopopup end + if a.intrusive ~=nil then + intrusive = a.intrusive + end + if a.fullscreen ~=nil then + c.fullscreen = a.fullscreen + end + if a.honorsizehints ~=nil then + c.size_hints_honor = a.honorsizehints + end + if a.kill ~=nil then c:kill(); return end + if a.ontop ~= nil then c.ontop = a.ontop end + if a.above ~= nil then c.above = a.above end + if a.below ~= nil then c.below = a.below end + if a.buttons ~= nil then + c:buttons(a.buttons) + end + if a.nofocus ~= nil then nofocus = a.nofocus end + if a.keys ~= nil then + keys = awful.util.table.join(keys, a.keys) + end + if a.hidden ~= nil then c.hidden = a.hidden end + if a.minimized ~= nil then + c.minimized = a.minimized + end + if a.dockable ~= nil then + awful.client.dockable.set(c, a.dockable) + end + if a.urgent ~= nil then + c.urgent = a.urgent + end + if a.opacity ~= nil then + c.opacity = a.opacity + end + if a.run ~= nil then run = a.run end + if a.sticky ~= nil then c.sticky = a.sticky end + if a.wfact ~= nil then wfact = a.wfact end + if a.struts then struts = a.struts end + if a.skip_taskbar ~= nil then + c.skip_taskbar = a.skip_taskbar + end + if a.props then + for kk, vv in pairs(a.props) do + awful.client.property.set(c, kk, vv) + end + end + end + end + end + + -- set key bindings + c:keys(keys) + + -- Add titlebars to all clients when the float, remove when they are + -- tiled. + if shifty.config.float_bars then + shifty.create_titlebar(c) + + c:connect_signal("property::floating", function(c) + if awful.client.floating.get(c) then + awful.titlebar(c) + else + awful.titlebar(c, { size = 0 }) + end + awful.placement.no_offscreen(c) + end) + end + + -- set properties of floating clients + if float ~= nil then + awful.client.floating.set(c, float) + awful.placement.no_offscreen(c) + end + + local sel = awful.tag.selectedlist(target_screen) + if not target_tag_names or #target_tag_names == 0 then + -- if not matched to some names try putting + -- client in c.transient_for or current tags + if c.transient_for then + target_tags = c.transient_for:tags() + elseif #sel > 0 then + for i, t in ipairs(sel) do + local mc = awful.tag.getproperty(t, "max_clients") + if intrusive or + not (awful.tag.getproperty(t, "exclusive") or + (mc and mc >= #t:clients())) then + table.insert(target_tags, t) + end + end + end + end + + if (not target_tag_names or #target_tag_names == 0) and + (not target_tags or #target_tags == 0) then + -- if we still don't know any target names/tags guess + -- name from class or use default + if shifty.config.guess_name and cls then + target_tag_names = {cls:lower()} + else + target_tag_names = {shifty.config.default_name} + end + end + + if #target_tag_names > 0 and #target_tags == 0 then + -- translate target names to tag objects, creating + -- missing ones + for i, tn in ipairs(target_tag_names) do + local res = {} + for j, t in ipairs(name2tags(tn, target_screen) or + name2tags(tn) or {}) do + local mc = awful.tag.getproperty(t, "max_clients") + local tagged = is_client_tagged(t, c) + if intrusive or + not (mc and (((#t:clients() >= mc) and not + tagged) or + (#t:clients() > mc))) or + intrusive then + if awful.tag.getscreen(t) == mouse.screen then + table.insert(res, t) + end + end + end + if #res == 0 then + table.insert(target_tags, + shifty.add({name = tn, + noswitch = true, + matched = true})) + else + target_tags = awful.util.table.join(target_tags, res) + end + end + end + + -- set client's screen/tag if needed + target_screen = awful.tag.getscreen(target_tags[1]) or target_screen + if c.screen ~= target_screen then c.screen = target_screen end + if slave then awful.client.setslave(c) end + c:tags(target_tags) + + if wfact then awful.client.setwfact(wfact, c) end + if geom then c:geometry(geom) end + if struts then c:struts(struts) end + + local showtags = {} + local u = nil + if #target_tags > 0 and not startup then + -- switch or highlight + for i, t in ipairs(target_tags) do + if not (nopopup or awful.tag.getproperty(t, "nopopup")) then + table.insert(showtags, t) + elseif not startup then + c.urgent = true + end + end + if #showtags > 0 then + local ident = false + -- iterate selected tags and and see if any targets + -- currently selected + for kk, vv in pairs(showtags) do + for _, tag in pairs(sel) do + if tag == vv then + ident = true + end + end + end + if not ident then + awful.tag.viewmore(showtags, c.screen) + end + end + end + + if not (nofocus or c.hidden or c.minimized) then + --focus and raise accordingly or lower if supressed + if (target and target ~= sel) and + (awful.tag.getproperty(target, "nopopup") or nopopup) then + awful.client.focus.history.add(c) + else + capi.client.focus = c + end + c:raise() + else + c:lower() + end + + if shifty.config.sloppy then + -- Enable sloppy focus + c:connect_signal("mouse::enter", function(c) + if awful.client.focus.filter(c) and + awful.layout.get(c.screen) ~= awful.layout.suit.magnifier then + capi.client.focus = c + end + end) + end + + -- execute run function if specified + if run then run(c, target) end + +end + +--sweep : hook function that marks tags as used, visited, +--deserted also handles deleting used and empty tags +function sweep() + for s = 1, capi.screen.count() do + for i, t in ipairs(awful.tag.gettags(s)) do + local clients = t:clients() + local sticky = 0 + for i, c in ipairs(clients) do + if c.sticky then sticky = sticky + 1 end + end + if #clients == sticky then + if awful.tag.getproperty(t, "used") and + not awful.tag.getproperty(t, "persist") then + if awful.tag.getproperty(t, "deserted") or + not awful.tag.getproperty(t, "leave_kills") then + local delay = awful.tag.getproperty(t, "sweep_delay") + if delay then + local tmr + local f = function() + shifty.del(t); tmr:stop() + end + tmr = capi.timer({timeout = delay}) + tmr:connect_signal("timeout", f) + tmr:start() + else + if shifty.config.delete_deserted then + shifty.del(t) + end + end + else + if awful.tag.getproperty(t, "visited") and + not t.selected then + awful.tag.setproperty(t, "deserted", true) + end + end + end + else + awful.tag.setproperty(t, "used", true) + end + if t.selected then + awful.tag.setproperty(t, "visited", true) + end + end + end +end + +--getpos : returns a tag to match position +-- @param pos : the index to find +-- @return v : the tag (found or created) at position == 'pos' +function shifty.getpos(pos, scr_arg) + local v = nil + local existing = {} + local selected = nil + local scr = scr_arg or capi.mouse.screen or 1 + + -- search for existing tag assigned to pos + for i = 1, capi.screen.count() do + for j, t in ipairs(awful.tag.gettags(i)) do + if awful.tag.getproperty(t, "position") == pos then + table.insert(existing, t) + if t.selected and i == scr then + selected = #existing + end + end + end + end + + if #existing > 0 then + -- if there is no selected tag on current screen, look for the first one + if not selected then + for _, tag in pairs(existing) do + if awful.tag.getscreen(tag) == scr then return tag end + end + + -- no tag found, loop through the other tags + selected = #existing + end + + -- look for the next unselected tag + i = selected + repeat + i = awful.util.cycle(#existing, i + 1) + tag = existing[i] + + if (scr_arg == nil or awful.tag.getscreen(tag) == scr_arg) and not tag.selected then return tag end + until i == selected + + -- if the screen is not specified or + -- if a selected tag exists on the specified screen + -- return the selected tag + if scr_arg == nil or awful.tag.getscreen(existing[selected]) == scr then return existing[selected] end + + -- if scr_arg ~= nil and no tag exists on this screen, continue + end + + local screens = {} + for s = 1, capi.screen.count() do table.insert(screens, s) end + + -- search for preconf with 'pos' on current screen and create it + for i, j in pairs(shifty.config.tags) do + local tag_scr = j.screen or screens + if type(tag_scr) ~= 'table' then tag_scr = {tag_scr} end + + if j.position == pos and awful.util.table.hasitem(tag_scr, scr) then + return shifty.add({name = i, + position = pos, + noswitch = not switch}) + end + end + + -- not existing, not preconfigured + return shifty.add({position = pos, + rename = pos .. ':', + no_selectall = true, + noswitch = not switch}) +end + +--init : search config.tags for initial set of +--tags to open +function shifty.init() + local numscr = capi.screen.count() + + local screens = {} + for s = 1, capi.screen.count() do table.insert(screens, s) end + + for i, j in pairs(shifty.config.tags) do + local scr = j.screen or screens + if type(scr) ~= 'table' then + scr = {scr} + end + for _, s in pairs(scr) do + if j.init and (s <= numscr) then + shifty.add({name = i, + persist = true, + screen = s, + layout = j.layout, + mwfact = j.mwfact}) + end + end + end +end + +-- Create a titlebar for the given client +-- By default, make it invisible (size = 0) + +function shifty.create_titlebar(c) + -- Widgets that are aligned to the left + local left_layout = wibox.layout.fixed.horizontal() + left_layout:add(awful.titlebar.widget.iconwidget(c)) + + -- Widgets that are aligned to the right + local right_layout = wibox.layout.fixed.horizontal() + right_layout:add(awful.titlebar.widget.floatingbutton(c)) + right_layout:add(awful.titlebar.widget.maximizedbutton(c)) + right_layout:add(awful.titlebar.widget.stickybutton(c)) + right_layout:add(awful.titlebar.widget.ontopbutton(c)) + right_layout:add(awful.titlebar.widget.closebutton(c)) + + -- The title goes in the middle + local title = awful.titlebar.widget.titlewidget(c) + title:buttons(awful.util.table.join( + awful.button({ }, 1, function() + client.focus = c + c:raise() + awful.mouse.client.move(c) + end), + awful.button({ }, 3, function() + client.focus = c + c:raise() + awful.mouse.client.resize(c) + end) + )) + + -- Now bring it all together + local layout = wibox.layout.align.horizontal() + layout:set_left(left_layout) + layout:set_right(right_layout) + layout:set_middle(title) + + awful.titlebar(c, { size = 0 }):set_widget(layout) +end + +--count : utility function returns the index of a table element +--FIXME: this is currently used only in remove_dup, so is it really +--necessary? +function count(table, element) + local v = 0 + for i, e in pairs(table) do + if element == e then v = v + 1 end + end + return v +end + +--remove_dup : used by shifty.completion when more than one +--tag at a position exists +function remove_dup(table) + local v = {} + for i, entry in ipairs(table) do + if count(v, entry) == 0 then v[#v+ 1] = entry end + end + return v +end + +--completion : prompt completion +-- +function completion(cmd, cur_pos, ncomp, sources, matchers) + + -- get sources and matches tables + sources = sources or shifty.config.prompt_sources + matchers = matchers or shifty.config.prompt_matchers + + local get_source = { + -- gather names from config.tags + config_tags = function() + local ret = {} + for n, p in pairs(shifty.config.tags) do + table.insert(ret, n) + end + return ret + end, + -- gather names from config.apps + config_apps = function() + local ret = {} + for i, p in pairs(shifty.config.apps) do + if p.tag then + if type(p.tag) == "string" then + table.insert(ret, p.tag) + else + ret = awful.util.table.join(ret, p.tag) + end + end + end + return ret + end, + -- gather names from existing tags, starting with the + -- current screen + existing = function() + local ret = {} + for i = 1, capi.screen.count() do + local s = awful.util.cycle(capi.screen.count(), + capi.mouse.screen + i - 1) + local tags = awful.tag.gettags(s) + for j, t in pairs(tags) do + table.insert(ret, t.name) + end + end + return ret + end, + -- gather names from history + history = function() + local ret = {} + local f = io.open(awful.util.getdir("cache") .. + "/history_tags") + for name in f:lines() do table.insert(ret, name) end + f:close() + return ret + end, + } + + -- if empty, match all + if #cmd == 0 or cmd == " " then cmd = "" end + + -- match all up to the cursor if moved or no matchphrase + if matchp == "" or + cmd:sub(cur_pos, cur_pos+#matchp) ~= matchp then + matchp = cmd:sub(1, cur_pos) + end + + -- find matching commands + local matches = {} + for i, src in ipairs(sources) do + local source = get_source[src]() + for j, matcher in ipairs(matchers) do + for k, name in ipairs(source) do + if name:find(matcher .. matchp) then + table.insert(matches, name) + end + end + end + end + + -- no matches + if #matches == 0 then return cmd, cur_pos end + + -- remove duplicates + matches = remove_dup(matches) + + -- cycle + while ncomp > #matches do ncomp = ncomp - #matches end + + -- put cursor at the end of the matched phrase + if #matches == 1 then + cur_pos = #matches[ncomp] + 1 + else + cur_pos = matches[ncomp]:find(matchp) + #matchp + end + + -- return match and position + return matches[ncomp], cur_pos +end + +-- tagkeys : hook function that sets keybindings per tag +function tagkeys(s) + local sel = awful.tag.selected(s.index) + local keys = awful.tag.getproperty(sel, "keys") or + shifty.config.globalkeys + if keys and sel.selected then capi.root.keys(keys) end +end + +-- squash_keys: helper function which removes duplicate +-- keybindings by picking only the last one to be listed in keys +-- table arg +function squash_keys(keys) + local squashed = {} + local ret = {} + for i, k in ipairs(keys) do + squashed[table.concat(k.modifiers) .. k.key] = k + end + for i, k in pairs(squashed) do + table.insert(ret, k) + end + return ret +end + +-- getlayout: returns a layout by name +function getlayout(name) + for _, layout in ipairs(shifty.config.layouts) do + if awful.layout.getname(layout) == name then + return layout + end + end +end + +-- add signals before using them +-- Note: these signals are emitted when tag properties +-- are accessed through awful.tag.setproperty +capi.tag.add_signal("property::initial") +capi.tag.add_signal("property::used") +capi.tag.add_signal("property::visited") +capi.tag.add_signal("property::deserted") +capi.tag.add_signal("property::matched") +capi.tag.add_signal("property::selected") +capi.tag.add_signal("property::position") +capi.tag.add_signal("property::exclusive") +capi.tag.add_signal("property::persist") +capi.tag.add_signal("property::index") +capi.tag.add_signal("property::nopopup") +capi.tag.add_signal("property::leave_kills") +capi.tag.add_signal("property::max_clients") +capi.tag.add_signal("property::icon_only") +capi.tag.add_signal("property::sweep_delay") +capi.tag.add_signal("property::overload_keys") + +-- replace awful's default hook +capi.client.connect_signal("manage", match) +capi.client.connect_signal("unmanage", sweep) +capi.client.disconnect_signal("manage", awful.tag.withcurrent) + +for s = 1, capi.screen.count() do + awful.tag.attached_connect_signal(s, "property::selected", sweep) + awful.tag.attached_connect_signal(s, "tagged", sweep) + capi.screen[s]:connect_signal("tag::history::update", tagkeys) +end + +return shifty + |