Advertisement
Guest User

shifty

a guest
Dec 11th, 2012
35
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 34.13 KB | None | 0 0
  1. --- Shifty: Dynamic tagging library for awesome3-git
  2. -- @author koniu <gkusnierz@gmail.com>
  3. -- @author resixian (aka bioe007) <resixian@gmail.com>
  4. --
  5. -- http://awesome.naquadah.org/wiki/index.php?title=Shifty
  6.  
  7. -- environment
  8. local type = type
  9. local ipairs = ipairs
  10. local table = table
  11. local string = string
  12. local beautiful = require("beautiful")
  13. local awful = require("awful")
  14. local pairs = pairs
  15. local io = io
  16. local tonumber = tonumber
  17. local dbg= dbg
  18. local capi = {
  19.     client = client,
  20.     tag = tag,
  21.     image = image,
  22.     screen = screen,
  23.     button = button,
  24.     mouse = mouse,
  25.     root = root,
  26.     timer = timer
  27. }
  28.  
  29. module("shifty")
  30.  
  31. -- variables
  32. config = {}
  33. config.tags = {}
  34. config.apps = {}
  35. config.defaults = {}
  36. config.float_bars = false
  37. config.guess_name = true
  38. config.guess_position = true
  39. config.remember_index = true
  40. config.sloppy = true
  41. config.default_name = "new"
  42. config.clientkeys = {}
  43. config.globalkeys = nil
  44. config.layouts = {}
  45. config.prompt_sources = {
  46.     "config_tags",
  47.     "config_apps",
  48.     "existing",
  49.     "history"
  50. }
  51. config.prompt_matchers = {
  52.     "^",
  53.     ":",
  54.     ""
  55. }
  56.  
  57. local matchp = ""
  58. local index_cache = {}
  59. for i = 1, capi.screen.count() do index_cache[i] = {} end
  60.  
  61. --name2tags: matches string 'name' to tag objects
  62. -- @param name : tag name to find
  63. -- @param scr : screen to look for tags on
  64. -- @return table of tag objects or nil
  65. function name2tags(name, scr)
  66.     local ret = {}
  67.     local a, b = scr or 1, scr or capi.screen.count()
  68.     for s = a, b do
  69.         for i, t in ipairs(capi.screen[s]:tags()) do
  70.             if name == t.name then
  71.                 table.insert(ret, t)
  72.             end
  73.         end
  74.     end
  75.     if #ret > 0 then return ret end
  76. end
  77.  
  78. function name2tag(name, scr, idx)
  79.     local ts = name2tags(name, scr)
  80.     if ts then return ts[idx or 1] end
  81. end
  82.  
  83. --tag2index: finds index of a tag object
  84. -- @param scr : screen number to look for tag on
  85. -- @param tag : the tag object to find
  86. -- @return the index [or zero] or end of the list
  87. function tag2index(scr, tag)
  88.     for i, t in ipairs(capi.screen[scr]:tags()) do
  89.         if t == tag then return i end
  90.     end
  91. end
  92.  
  93. --rename
  94. --@param tag: tag object to be renamed
  95. --@param prefix: if any prefix is to be added
  96. --@param no_selectall:
  97. function rename(tag, prefix, no_selectall)
  98.     local theme = beautiful.get()
  99.     local t = tag or awful.tag.selected(capi.mouse.screen)
  100.     local scr = t.screen
  101.     local bg = nil
  102.     local fg = nil
  103.     local text = prefix or t.name
  104.     local before = t.name
  105.  
  106.     if t == awful.tag.selected(scr) then
  107.         bg = theme.bg_focus or '#535d6c'
  108.         fg = theme.fg_urgent or '#ffffff'
  109.     else
  110.         bg = theme.bg_normal or '#222222'
  111.         fg = theme.fg_urgent or '#ffffff'
  112.     end
  113.  
  114.     awful.prompt.run({
  115.         fg_cursor = fg, bg_cursor = bg, ul_cursor = "single",
  116.         text = text, selectall = not no_selectall},
  117.         taglist[scr][tag2index(scr, t) * 2],
  118.         function (name) if name:len() > 0 then t.name = name; end end,
  119.         completion,
  120.         awful.util.getdir("cache") .. "/history_tags",
  121.         nil,
  122.         function ()
  123.             if t.name == before then
  124.                 if awful.tag.getproperty(t, "initial") then del(t) end
  125.             else
  126.                 awful.tag.setproperty(t, "initial", true)
  127.                 set(t)
  128.             end
  129.             tagkeys(capi.screen[scr])
  130.             t:emit_signal("property::name")
  131.         end
  132.         )
  133. end
  134.  
  135. --send: moves client to tag[idx]
  136. -- maybe this isn't needed here in shifty?
  137. -- @param idx the tag number to send a client to
  138. function send(idx)
  139.     local scr = capi.client.focus.screen or capi.mouse.screen
  140.     local sel = awful.tag.selected(scr)
  141.     local sel_idx = tag2index(scr, sel)
  142.     local tags = capi.screen[scr]:tags()
  143.     local target = awful.util.cycle(#tags, sel_idx + idx)
  144.     awful.client.movetotag(tags[target], capi.client.focus)
  145.     awful.tag.viewonly(tags[target])
  146. end
  147.  
  148. function send_next() send(1) end
  149. function send_prev() send(-1) end
  150.  
  151. --pos2idx: translate shifty position to tag index
  152. --@param pos: position (an integer)
  153. --@param scr: screen number
  154. function pos2idx(pos, scr)
  155.     local v = 1
  156.     if pos and scr then
  157.         for i = #capi.screen[scr]:tags() , 1, -1 do
  158.             local t = capi.screen[scr]:tags()[i]
  159.             if awful.tag.getproperty(t, "position") and
  160.                 awful.tag.getproperty(t, "position") <= pos then
  161.                 v = i + 1
  162.                 break
  163.             end
  164.         end
  165.     end
  166.     return v
  167. end
  168.  
  169. --select : helper function chooses the first non-nil argument
  170. --@param args - table of arguments
  171. function select(args)
  172.     for i, a in pairs(args) do
  173.         if a ~= nil then
  174.             return a
  175.         end
  176.     end
  177. end
  178.  
  179. --tagtoscr : move an entire tag to another screen
  180. --
  181. --@param scr : the screen to move tag to
  182. --@param t : the tag to be moved [awful.tag.selected()]
  183. --@return the tag
  184. function tagtoscr(scr, t)
  185.     -- break if called with an invalid screen number
  186.     if not scr or scr < 1 or scr > capi.screen.count() then return end
  187.     -- tag to move
  188.     local otag = t or awful.tag.selected()
  189.  
  190.     otag.screen = scr
  191.     -- set screen and then reset tag to order properly
  192.     if #otag:clients() > 0 then
  193.         for _ , c in ipairs(otag:clients()) do
  194.             if not c.sticky then
  195.                 c.screen = scr
  196.                 c:tags({otag})
  197.             else
  198.                 awful.client.toggletag(otag, c)
  199.             end
  200.         end
  201.     end
  202.     return otag
  203. end
  204.  
  205. --set : set a tags properties
  206. --@param t: the tag
  207. --@param args : a table of optional (?) tag properties
  208. --@return t - the tag object
  209. function set(t, args)
  210.     if not t then return end
  211.     if not args then args = {} end
  212.  
  213.     -- set the name
  214.     t.name = args.name or t.name
  215.  
  216.     -- attempt to load preset on initial run
  217.     local preset = (awful.tag.getproperty(t, "initial") and
  218.     config.tags[t.name]) or {}
  219.  
  220.     -- pick screen and get its tag table
  221.     local scr = args.screen or
  222.     (not t.screen and preset.screen) or
  223.     t.screen or
  224.     capi.mouse.screen
  225.  
  226.     local clientstomove = nil
  227.     if scr > capi.screen.count() then scr = capi.screen.count() end
  228.     if t.screen and scr ~= t.screen then
  229.         tagtoscr(scr, t)
  230.         t.screen = nil
  231.     end
  232.     local tags = capi.screen[scr]:tags()
  233.  
  234.     -- try to guess position from the name
  235.     local guessed_position = nil
  236.     if not (args.position or preset.position) and config.guess_position then
  237.         local num = t.name:find('^[1-9]')
  238.         if num then guessed_position = tonumber(t.name:sub(1, 1)) end
  239.     end
  240.  
  241.     -- allow preset.layout to be a table to provide a different layout per
  242.     -- screen for a given tag
  243.     local preset_layout = preset.layout
  244.     if preset_layout and preset_layout[scr] then
  245.         preset_layout = preset.layout[scr]
  246.     end
  247.  
  248.     -- select from args, preset, getproperty,
  249.     -- config.defaults.configs or defaults
  250.     local props = {
  251.         layout = select{args.layout, preset_layout,
  252.                         awful.tag.getproperty(t, "layout"),
  253.                         config.defaults.layout, awful.layout.suit.tile},
  254.         mwfact = select{args.mwfact, preset.mwfact,
  255.                         awful.tag.getproperty(t, "mwfact"),
  256.                         config.defaults.mwfact, 0.55},
  257.         nmaster = select{args.nmaster, preset.nmaster,
  258.                         awful.tag.getproperty(t, "nmaster"),
  259.                         config.defaults.nmaster, 1},
  260.         ncol = select{args.ncol, preset.ncol,
  261.                         awful.tag.getproperty(t, "ncol"),
  262.                         config.defaults.ncol, 1},
  263.         matched = select{args.matched, awful.tag.getproperty(t, "matched")},
  264.         exclusive = select{args.exclusive, preset.exclusive,
  265.                         awful.tag.getproperty(t, "exclusive"),
  266.                         config.defaults.exclusive},
  267.         persist = select{args.persist, preset.persist,
  268.                         awful.tag.getproperty(t, "persist"),
  269.                         config.defaults.persist},
  270.         nopopup = select{args.nopopup, preset.nopopup,
  271.                         awful.tag.getproperty(t, "nopopup"),
  272.                         config.defaults.nopopup},
  273.         leave_kills = select{args.leave_kills, preset.leave_kills,
  274.                         awful.tag.getproperty(t, "leave_kills"),
  275.                         config.defaults.leave_kills},
  276.         max_clients = select{args.max_clients, preset.max_clients,
  277.                         awful.tag.getproperty(t, "max_clients"),
  278.                         config.defaults.max_clients},
  279.         position = select{args.position, preset.position, guessed_position,
  280.                         awful.tag.getproperty(t, "position")},
  281.         icon = select{args.icon and capi.image(args.icon),
  282.                         preset.icon and capi.image(preset.icon),
  283.                         awful.tag.getproperty(t, "icon"),
  284.                     config.defaults.icon and capi.image(config.defaults.icon)},
  285.         icon_only = select{args.icon_only, preset.icon_only,
  286.                         awful.tag.getproperty(t, "icon_only"),
  287.                         config.defaults.icon_only},
  288.         sweep_delay = select{args.sweep_delay, preset.sweep_delay,
  289.                         awful.tag.getproperty(t, "sweep_delay"),
  290.                         config.defaults.sweep_delay},
  291.         overload_keys = select{args.overload_keys, preset.overload_keys,
  292.                         awful.tag.getproperty(t, "overload_keys"),
  293.                         config.defaults.overload_keys},
  294.     }
  295.  
  296.     -- get layout by name if given as string
  297.     if type(props.layout) == "string" then
  298.         props.layout = getlayout(props.layout)
  299.     end
  300.  
  301.     -- set keys
  302.     if args.keys or preset.keys then
  303.         local keys = awful.util.table.join(config.globalkeys,
  304.         args.keys or preset.keys)
  305.         if props.overload_keys then
  306.             props.keys = keys
  307.         else
  308.             props.keys = squash_keys(keys)
  309.         end
  310.     end
  311.  
  312.     -- calculate desired taglist index
  313.     local index = args.index or preset.index or config.defaults.index
  314.     local rel_index = args.rel_index or
  315.     preset.rel_index or
  316.     config.defaults.rel_index
  317.     local sel = awful.tag.selected(scr)
  318.     --TODO: what happens with rel_idx if no tags selected
  319.     local sel_idx = (sel and tag2index(scr, sel)) or 0
  320.     local t_idx = tag2index(scr, t)
  321.     local limit = (not t_idx and #tags + 1) or #tags
  322.     local idx = nil
  323.  
  324.     if rel_index then
  325.         idx = awful.util.cycle(limit, (t_idx or sel_idx) + rel_index)
  326.     elseif index then
  327.         idx = awful.util.cycle(limit, index)
  328.     elseif props.position then
  329.         idx = pos2idx(props.position, scr)
  330.         if t_idx and t_idx < idx then idx = idx - 1 end
  331.     elseif config.remember_index and index_cache[scr][t.name] then
  332.         idx = index_cache[scr][t.name]
  333.     elseif not t_idx then
  334.         idx = #tags + 1
  335.     end
  336.  
  337.     -- if we have a new index, remove from old index and insert
  338.     if idx then
  339.         if t_idx then table.remove(tags, t_idx) end
  340.         table.insert(tags, idx, t)
  341.         index_cache[scr][t.name] = idx
  342.     end
  343.  
  344.     -- set tag properties and push the new tag table
  345.     capi.screen[scr]:tags(tags)
  346.     for prop, val in pairs(props) do awful.tag.setproperty(t, prop, val) end
  347.  
  348.     -- execute run/spawn
  349.     if awful.tag.getproperty(t, "initial") then
  350.         local spawn = args.spawn or preset.spawn or config.defaults.spawn
  351.         local run = args.run or preset.run or config.defaults.run
  352.         if spawn and args.matched ~= true then
  353.             awful.util.spawn_with_shell(spawn, scr)
  354.         end
  355.         if run then run(t) end
  356.         awful.tag.setproperty(t, "initial", nil)
  357.     end
  358.  
  359.  
  360.     return t
  361. end
  362.  
  363. function shift_next() set(awful.tag.selected(), {rel_index = 1}) end
  364. function shift_prev() set(awful.tag.selected(), {rel_index = -1}) end
  365.  
  366. --add : adds a tag
  367. --@param args: table of optional arguments
  368. function add(args)
  369.     if not args then args = {} end
  370.     local name = args.name or " "
  371.  
  372.     -- initialize a new tag object and its data structure
  373.     local t = capi.tag{name = name}
  374.  
  375.     -- tell set() that this is the first time
  376.     awful.tag.setproperty(t, "initial", true)
  377.  
  378.     -- apply tag settings
  379.     set(t, args)
  380.  
  381.     -- unless forbidden or if first tag on the screen, show the tag
  382.     if not (awful.tag.getproperty(t, "nopopup") or args.noswitch) or
  383.         #capi.screen[t.screen]:tags() == 1 then
  384.         awful.tag.viewonly(t)
  385.     end
  386.  
  387.     -- get the name or rename
  388.     if args.name then
  389.         t.name = args.name
  390.     else
  391.         -- FIXME: hack to delay rename for un-named tags for
  392.         -- tackling taglist refresh which disabled prompt
  393.         -- from being rendered until input
  394.         awful.tag.setproperty(t, "initial", true)
  395.         local f
  396.         if args.position then
  397.             f = function() rename(t, args.rename, true); tmr:stop() end
  398.         else
  399.             f = function() rename(t); tmr:stop() end
  400.         end
  401.         tmr = capi.timer({timeout = 0.01})
  402.         tmr:add_signal("timeout", f)
  403.         tmr:start()
  404.     end
  405.  
  406.     return t
  407. end
  408.  
  409. --del : delete a tag
  410. --@param tag : the tag to be deleted [current tag]
  411. function del(tag)
  412.     local scr = (tag and tag.screen) or capi.mouse.screen or 1
  413.     local tags = capi.screen[scr]:tags()
  414.     local sel = awful.tag.selected(scr)
  415.     local t = tag or sel
  416.     local idx = tag2index(scr, t)
  417.  
  418.     -- return if tag not empty (except sticky)
  419.     local clients = t:clients()
  420.     local sticky = 0
  421.     for i, c in ipairs(clients) do
  422.         if c.sticky then sticky = sticky + 1 end
  423.     end
  424.     if #clients > sticky then return end
  425.  
  426.     -- store index for later
  427.     index_cache[scr][t.name] = idx
  428.  
  429.     -- remove tag
  430.     t.screen = nil
  431.  
  432.     -- if the current tag is being deleted, restore from history
  433.     if t == sel and #tags > 1 then
  434.         awful.tag.history.restore(scr, 1)
  435.         -- this is supposed to cycle if history is invalid?
  436.         -- e.g. if many tags are deleted in a row
  437.         if not awful.tag.selected(scr) then
  438.             awful.tag.viewonly(tags[awful.util.cycle(#tags, idx - 1)])
  439.         end
  440.     end
  441.  
  442.     -- FIXME: what is this for??
  443.     if capi.client.focus then capi.client.focus:raise() end
  444. end
  445.  
  446. --is_client_tagged : replicate behavior in tag.c - returns true if the
  447. --given client is tagged with the given tag
  448. function is_client_tagged(tag, client)
  449.     for i, c in ipairs(tag:clients()) do
  450.         if c == client then
  451.             return true
  452.         end
  453.     end
  454.     return false
  455. end
  456.  
  457. --match : handles app->tag matching, a replacement for the manage hook in
  458. --            rc.lua
  459. --@param c : client to be matched
  460. function match(c, startup)
  461.     local nopopup, intrusive, nofocus, run, slave
  462.     local wfact, struts, geom, float
  463.     local target_tag_names, target_tags = {}, {}
  464.     local typ = c.type
  465.     local cls = c.class
  466.     local inst = c.instance
  467.     local role = c.role
  468.     local name = c.name
  469.     local keys = config.clientkeys or c:keys() or {}
  470.     local target_screen = capi.mouse.screen
  471.  
  472.     c.border_color = beautiful.border_normal
  473.     c.border_width = beautiful.border_width
  474.  
  475.     -- try matching client to config.apps
  476.     for i, a in ipairs(config.apps) do
  477.         if a.match then
  478.             local matched = false
  479.             -- match only class
  480.             if not matched and cls and a.match.class then
  481.                 for k, w in ipairs(a.match.class) do
  482.                     matched = cls:find(w)
  483.                     if matched then
  484.                         break
  485.                     end
  486.                 end
  487.             end
  488.             -- match only instance
  489.             if not matched and inst and a.match.instance then
  490.                 for k, w in ipairs(a.match.instance) do
  491.                     matched = inst:find(w)
  492.                     if matched then
  493.                         break
  494.                     end
  495.                 end
  496.             end
  497.             -- match only name
  498.             if not matched and name and a.match.name then
  499.                 for k, w in ipairs(a.match.name) do
  500.                     matched = name:find(w)
  501.                     if matched then
  502.                         break
  503.                     end
  504.                 end
  505.             end
  506.             -- match only role
  507.             if not matched and role and a.match.role then
  508.                 for k, w in ipairs(a.match.role) do
  509.                     matched = role:find(w)
  510.                     if matched then
  511.                         break
  512.                     end
  513.                 end
  514.             end
  515.             -- match only type
  516.             if not matched and typ and a.match.type then
  517.                 for k, w in ipairs(a.match.type) do
  518.                     matched = typ:find(w)
  519.                     if matched then
  520.                         break
  521.                     end
  522.                 end
  523.             end
  524.             -- check everything else against all attributes
  525.             if not matched then
  526.                 for k, w in ipairs(a.match) do
  527.                     matched = (cls and cls:find(w)) or
  528.                             (inst and inst:find(w)) or
  529.                             (name and name:find(w)) or
  530.                             (role and role:find(w)) or
  531.                             (typ and typ:find(w))
  532.                     if matched then
  533.                         break
  534.                     end
  535.                 end
  536.             end
  537.             -- set attributes
  538.             if matched then
  539.                 if a.screen then target_screen = a.screen end
  540.                 if a.tag then
  541.                     if type(a.tag) == "string" then
  542.                         target_tag_names = {a.tag}
  543.                     else
  544.                         target_tag_names = a.tag
  545.                     end
  546.                 end
  547.                 if a.startup and startup then
  548.                     a = awful.util.table.join(a, a.startup)
  549.                 end
  550.                 if a.geometry ~=nil then
  551.                     geom = {x = a.geometry[1],
  552.                     y = a.geometry[2],
  553.                     width = a.geometry[3],
  554.                     height = a.geometry[4]}
  555.                 end
  556.                 if a.float ~= nil then float = a.float end
  557.                 if a.slave ~=nil then slave = a.slave end
  558.                 if a.border_width ~= nil then
  559.                     c.border_width = a.border_width
  560.                 end
  561.                 if a.nopopup ~=nil then nopopup = a.nopopup end
  562.                 if a.intrusive ~=nil then
  563.                     intrusive = a.intrusive
  564.                 end
  565.                 if a.fullscreen ~=nil then
  566.                     c.fullscreen = a.fullscreen
  567.                 end
  568.                 if a.honorsizehints ~=nil then
  569.                     c.size_hints_honor = a.honorsizehints
  570.                 end
  571.                 if a.kill ~=nil then c:kill(); return end
  572.                 if a.ontop ~= nil then c.ontop = a.ontop end
  573.                 if a.above ~= nil then c.above = a.above end
  574.                 if a.below ~= nil then c.below = a.below end
  575.                 if a.buttons ~= nil then
  576.                     c:buttons(a.buttons)
  577.                 end
  578.                 if a.nofocus ~= nil then nofocus = a.nofocus end
  579.                 if a.keys ~= nil then
  580.                     keys = awful.util.table.join(keys, a.keys)
  581.                 end
  582.                 if a.hidden ~= nil then c.hidden = a.hidden end
  583.                 if a.minimized ~= nil then
  584.                     c.minimized = a.minimized
  585.                 end
  586.                 if a.dockable ~= nil then
  587.                     awful.client.dockable.set(c, a.dockable)
  588.                 end
  589.                 if a.urgent ~= nil then
  590.                     c.urgent = a.urgent
  591.                 end
  592.                 if a.opacity ~= nil then
  593.                     c.opacity = a.opacity
  594.                 end
  595.                 if a.run ~= nil then run = a.run end
  596.                 if a.sticky ~= nil then c.sticky = a.sticky end
  597.                 if a.wfact ~= nil then wfact = a.wfact end
  598.                 if a.struts then struts = a.struts end
  599.                 if a.skip_taskbar ~= nil then
  600.                     c.skip_taskbar = a.skip_taskbar
  601.                 end
  602.                 if a.props then
  603.                     for kk, vv in pairs(a.props) do
  604.                         awful.client.property.set(c, kk, vv)
  605.                     end
  606.                 end
  607.             end
  608.         end
  609.     end
  610.  
  611.     -- set key bindings
  612.     c:keys(keys)
  613.  
  614.     -- Add titlebars to all clients when the float, remove when they are
  615.     -- tiled.
  616.     if config.float_bars then
  617.         c:add_signal("property::floating", function(c)
  618.             if awful.client.floating.get(c) then
  619.                 awful.titlebar.add(c, {modkey=modkey})
  620.             else
  621.                 awful.titlebar.remove(c)
  622.             end
  623.             awful.placement.no_offscreen(c)
  624.         end)
  625.     end
  626.  
  627.     -- set properties of floating clients
  628.     if float ~= nil then
  629.         awful.client.floating.set(c, float)
  630.         awful.placement.no_offscreen(c)
  631.     end
  632.  
  633.     local sel = awful.tag.selectedlist(target_screen)
  634.     if not target_tag_names or #target_tag_names == 0 then
  635.         -- if not matched to some names try putting
  636.         -- client in c.transient_for or current tags
  637.         if c.transient_for then
  638.             target_tags = c.transient_for:tags()
  639.         elseif #sel > 0 then
  640.             for i, t in ipairs(sel) do
  641.                 local mc = awful.tag.getproperty(t, "max_clients")
  642.                 if intrusive or
  643.                     not (awful.tag.getproperty(t, "exclusive") or
  644.                                     (mc and mc >= #t:clients())) then
  645.                     table.insert(target_tags, t)
  646.                 end
  647.             end
  648.         end
  649.     end
  650.  
  651.     if (not target_tag_names or #target_tag_names == 0) and
  652.         (not target_tags or #target_tags == 0) then
  653.         -- if we still don't know any target names/tags guess
  654.         -- name from class or use default
  655.         if config.guess_name and cls then
  656.             target_tag_names = {cls:lower()}
  657.         else
  658.             target_tag_names = {config.default_name}
  659.         end
  660.     end
  661.  
  662.     if #target_tag_names > 0 and #target_tags == 0 then
  663.         -- translate target names to tag objects, creating
  664.         -- missing ones
  665.         for i, tn in ipairs(target_tag_names) do
  666.             local res = {}
  667.             for j, t in ipairs(name2tags(tn, target_screen) or
  668.                 name2tags(tn) or {}) do
  669.                 local mc = awful.tag.getproperty(t, "max_clients")
  670.                 local tagged = is_client_tagged(t, c)
  671.                 if intrusive or
  672.                     not (mc and (((#t:clients() >= mc) and not
  673.                     tagged) or
  674.                     (#t:clients() > mc))) or
  675.                     intrusive then
  676.                     table.insert(res, t)
  677.                 end
  678.             end
  679.             if #res == 0 then
  680.                 table.insert(target_tags,
  681.                 add({name = tn,
  682.                 noswitch = true,
  683.                 matched = true}))
  684.             else
  685.                 target_tags = awful.util.table.join(target_tags, res)
  686.             end
  687.         end
  688.     end
  689.  
  690.     -- set client's screen/tag if needed
  691.     target_screen = target_tags[1].screen or target_screen
  692.     if c.screen ~= target_screen then c.screen = target_screen end
  693.     if slave then awful.client.setslave(c) end
  694.     c:tags(target_tags)
  695.  
  696.     if wfact then awful.client.setwfact(wfact, c) end
  697.     if geom then c:geometry(geom) end
  698.     if struts then c:struts(struts) end
  699.  
  700.     local showtags = {}
  701.     local u = nil
  702.     if #target_tags > 0 and not startup then
  703.         -- switch or highlight
  704.         for i, t in ipairs(target_tags) do
  705.             if not (nopopup or awful.tag.getproperty(t, "nopopup")) then
  706.                 table.insert(showtags, t)
  707.             elseif not startup then
  708.                 c.urgent = true
  709.             end
  710.         end
  711.         if #showtags > 0 then
  712.             local ident = false
  713.             -- iterate selected tags and and see if any targets
  714.             -- currently selected
  715.             for kk, vv in pairs(showtags) do
  716.                 for _, tag in pairs(sel) do
  717.                     if tag == vv then
  718.                         ident = true
  719.                     end
  720.                 end
  721.             end
  722.             if not ident then
  723.                 awful.tag.viewmore(showtags, c.screen)
  724.             end
  725.         end
  726.     end
  727.  
  728.     if not (nofocus or c.hidden or c.minimized) then
  729.         --focus and raise accordingly or lower if supressed
  730.         if (target and target ~= sel) and
  731.            (awful.tag.getproperty(target, "nopopup") or nopopup)  then
  732.             awful.client.focus.history.add(c)
  733.         else
  734.             capi.client.focus = c
  735.         end
  736.         c:raise()
  737.     else
  738.         c:lower()
  739.     end
  740.  
  741.     if config.sloppy then
  742.         -- Enable sloppy focus
  743.         c:add_signal("mouse::enter", function(c)
  744.             if awful.client.focus.filter(c) and
  745.                 awful.layout.get(c.screen) ~= awful.layout.suit.magnifier then
  746.                 capi.client.focus = c
  747.             end
  748.         end)
  749.     end
  750.  
  751.     -- execute run function if specified
  752.     if run then run(c, target) end
  753.  
  754. end
  755.  
  756. --sweep : hook function that marks tags as used, visited,
  757. --deserted also handles deleting used and empty tags
  758. function sweep()
  759.     for s = 1, capi.screen.count() do
  760.         for i, t in ipairs(capi.screen[s]:tags()) do
  761.             local clients = t:clients()
  762.             local sticky = 0
  763.             for i, c in ipairs(clients) do
  764.                 if c.sticky then sticky = sticky + 1 end
  765.             end
  766.             if #clients == sticky then
  767.                 if awful.tag.getproperty(t, "used") and
  768.                     not awful.tag.getproperty(t, "persist") then
  769.                     if awful.tag.getproperty(t, "deserted") or
  770.                         not awful.tag.getproperty(t, "leave_kills") then
  771.                         local delay = awful.tag.getproperty(t, "sweep_delay")
  772.                         if delay then
  773.                             local f = function()
  774.                                         del(t); tmr:stop()
  775.                                     end
  776.                             tmr = capi.timer({timeout = delay})
  777.                             tmr:add_signal("timeout", f)
  778.                             tmr:start()
  779.                         else
  780.                             del(t)
  781.                         end
  782.                     else
  783.                         if awful.tag.getproperty(t, "visited") and
  784.                             not t.selected then
  785.                             awful.tag.setproperty(t, "deserted", true)
  786.                         end
  787.                     end
  788.                 end
  789.             else
  790.                 awful.tag.setproperty(t, "used", true)
  791.             end
  792.             if t.selected then
  793.                 awful.tag.setproperty(t, "visited", true)
  794.             end
  795.         end
  796.     end
  797. end
  798.  
  799. --getpos : returns a tag to match position
  800. -- @param pos : the index to find
  801. -- @return v : the tag (found or created) at position == 'pos'
  802. function getpos(pos, scr_arg)
  803.     local v = nil
  804.     local existing = {}
  805.     local selected = nil
  806.     local scr = scr_arg or capi.mouse.screen or 1
  807.  
  808.     -- search for existing tag assigned to pos
  809.     for i = 1, capi.screen.count() do
  810.         for j, t in ipairs(capi.screen[i]:tags()) do
  811.             if awful.tag.getproperty(t, "position") == pos then
  812.                 table.insert(existing, t)
  813.                 if t.selected and i == scr then
  814.                     selected = #existing
  815.                 end
  816.             end
  817.         end
  818.     end
  819.  
  820.     if #existing > 0 then
  821.         -- if making another of an existing tag, return the end of
  822.         -- the list the optional 2nd argument decides if we return
  823.         -- only
  824.         if scr_arg ~= nil then
  825.             for _, tag in pairs(existing) do
  826.                 if tag.screen == scr_arg then return tag end
  827.             end
  828.             -- no tag with a position and scr_arg match found, clear
  829.             -- v and allow the subseqeunt conditions to be evaluated
  830.             v = nil
  831.         else
  832.             v = (selected and
  833.                     existing[awful.util.cycle(#existing, selected + 1)]) or
  834.                     existing[1]
  835.         end
  836.  
  837.     end
  838.     if not v then
  839.         -- search for preconf with 'pos' and create it
  840.         for i, j in pairs(config.tags) do
  841.             if j.position == pos then
  842.                 v = add({name = i,
  843.                         position = pos,
  844.                         noswitch = not switch})
  845.             end
  846.         end
  847.     end
  848.     if not v then
  849.         -- not existing, not preconfigured
  850.         v = add({position = pos,
  851.                 rename = pos .. ':',
  852.                 no_selectall = true,
  853.                 noswitch = not switch})
  854.     end
  855.     return v
  856. end
  857.  
  858. --init : search shifty.config.tags for initial set of
  859. --tags to open
  860. function init()
  861.     local numscr = capi.screen.count()
  862.  
  863.     for i, j in pairs(config.tags) do
  864.         local scr = j.screen or {1}
  865.         if type(scr) ~= 'table' then
  866.             scr = {scr}
  867.         end
  868.         for _, s in pairs(scr) do
  869.             if j.init and (s <= numscr) then
  870.                 add({name = i,
  871.                     persist = true,
  872.                     screen = s,
  873.                     layout = j.layout,
  874.                     mwfact = j.mwfact})
  875.             end
  876.         end
  877.     end
  878. end
  879.  
  880. --count : utility function returns the index of a table element
  881. --FIXME: this is currently used only in remove_dup, so is it really
  882. --necessary?
  883. function count(table, element)
  884.     local v = 0
  885.     for i, e in pairs(table) do
  886.         if element == e then v = v + 1 end
  887.     end
  888.     return v
  889. end
  890.  
  891. --remove_dup : used by shifty.completion when more than one
  892. --tag at a position exists
  893. function remove_dup(table)
  894.     local v = {}
  895.     for i, entry in ipairs(table) do
  896.         if count(v, entry) == 0 then v[#v+ 1] = entry end
  897.     end
  898.     return v
  899. end
  900.  
  901. --completion : prompt completion
  902. --
  903. function completion(cmd, cur_pos, ncomp, sources, matchers)
  904.  
  905.     -- get sources and matches tables
  906.     sources = sources or config.prompt_sources
  907.     matchers = matchers or config.prompt_matchers
  908.  
  909.     local get_source = {
  910.         -- gather names from config.tags
  911.         config_tags = function()
  912.             local ret = {}
  913.             for n, p in pairs(config.tags) do
  914.                 table.insert(ret, n)
  915.             end
  916.             return ret
  917.         end,
  918.         -- gather names from config.apps
  919.         config_apps = function()
  920.             local ret = {}
  921.             for i, p in pairs(config.apps) do
  922.                 if p.tag then
  923.                     if type(p.tag) == "string" then
  924.                         table.insert(ret, p.tag)
  925.                     else
  926.                         ret = awful.util.table.join(ret, p.tag)
  927.                     end
  928.                 end
  929.             end
  930.             return ret
  931.         end,
  932.         -- gather names from existing tags, starting with the
  933.         -- current screen
  934.         existing = function()
  935.             local ret = {}
  936.             for i = 1, capi.screen.count() do
  937.                 local s = awful.util.cycle(capi.screen.count(),
  938.                                             capi.mouse.screen + i - 1)
  939.                 local tags = capi.screen[s]:tags()
  940.                 for j, t in pairs(tags) do
  941.                     table.insert(ret, t.name)
  942.                 end
  943.             end
  944.             return ret
  945.         end,
  946.         -- gather names from history
  947.         history = function()
  948.             local ret = {}
  949.             local f = io.open(awful.util.getdir("cache") ..
  950.                                     "/history_tags")
  951.             for name in f:lines() do table.insert(ret, name) end
  952.             f:close()
  953.             return ret
  954.         end,
  955.     }
  956.  
  957.     -- if empty, match all
  958.     if #cmd == 0 or cmd == " " then cmd = "" end
  959.  
  960.     -- match all up to the cursor if moved or no matchphrase
  961.     if matchp == "" or
  962.         cmd:sub(cur_pos, cur_pos+#matchp) ~= matchp then
  963.         matchp = cmd:sub(1, cur_pos)
  964.     end
  965.  
  966.     -- find matching commands
  967.     local matches = {}
  968.     for i, src in ipairs(sources) do
  969.         local source = get_source[src]()
  970.         for j, matcher in ipairs(matchers) do
  971.             for k, name in ipairs(source) do
  972.                 if name:find(matcher .. matchp) then
  973.                     table.insert(matches, name)
  974.                 end
  975.             end
  976.         end
  977.     end
  978.  
  979.     -- no matches
  980.     if #matches == 0 then return cmd, cur_pos end
  981.  
  982.     -- remove duplicates
  983.     matches = remove_dup(matches)
  984.  
  985.     -- cycle
  986.     while ncomp > #matches do ncomp = ncomp - #matches end
  987.  
  988.     -- put cursor at the end of the matched phrase
  989.     if #matches == 1 then
  990.         cur_pos = #matches[ncomp] + 1
  991.     else
  992.         cur_pos = matches[ncomp]:find(matchp) + #matchp
  993.     end
  994.  
  995.     -- return match and position
  996.     return matches[ncomp], cur_pos
  997. end
  998.  
  999. -- tagkeys : hook function that sets keybindings per tag
  1000. function tagkeys(s)
  1001.     local sel = awful.tag.selected(s.index)
  1002.     local keys = awful.tag.getproperty(sel, "keys") or
  1003.                     config.globalkeys
  1004.     if keys and sel.selected then capi.root.keys(keys) end
  1005. end
  1006.  
  1007. -- squash_keys: helper function which removes duplicate
  1008. -- keybindings by picking only the last one to be listed in keys
  1009. -- table arg
  1010. function squash_keys(keys)
  1011.     local squashed = {}
  1012.     local ret = {}
  1013.     for i, k in ipairs(keys) do
  1014.         squashed[table.concat(k.modifiers) .. k.key] = k
  1015.     end
  1016.     for i, k in pairs(squashed) do
  1017.         table.insert(ret, k)
  1018.     end
  1019.     return ret
  1020. end
  1021.  
  1022. -- getlayout: returns a layout by name
  1023. function getlayout(name)
  1024.     for _, layout in ipairs(config.layouts) do
  1025.         if awful.layout.getname(layout) == name then
  1026.             return layout
  1027.         end
  1028.     end
  1029. end
  1030.  
  1031. -- signals
  1032. capi.client.add_signal("manage", match)
  1033. capi.client.add_signal("unmanage", sweep)
  1034. capi.client.remove_signal("manage", awful.tag.withcurrent)
  1035.  
  1036. for s = 1, capi.screen.count() do
  1037.     awful.tag.attached_add_signal(s, "property::selected", sweep)
  1038.     awful.tag.attached_add_signal(s, "tagged", sweep)
  1039.     capi.screen[s]:add_signal("tag::history::update", tagkeys)
  1040. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement