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