tag.withcurrent(): Also tag sticky clients (FS#934)
[awesome.git] / lib / awful / tag.lua.in
blob009924e3e1f338dc620275957dc1d077885e5fb4
1 ---------------------------------------------------------------------------
2 -- @author Julien Danjou <julien@danjou.info>
3 -- @copyright 2008 Julien Danjou
4 -- @release @AWESOME_VERSION@
5 ---------------------------------------------------------------------------
7 -- Grab environment we need
8 local util = require("awful.util")
9 local tostring = tostring
10 local pairs = pairs
11 local ipairs = ipairs
12 local table = table
13 local setmetatable = setmetatable
14 local capi =
16 tag = tag,
17 screen = screen,
18 mouse = mouse,
19 client = client
22 --- Useful functions for tag manipulation.
23 module("awful.tag")
25 -- Private data
26 local data = {}
27 data.history = {}
28 data.tags = setmetatable({}, { __mode = 'k' })
30 -- History functions
31 history = {}
32 history.limit = 20
34 --- Move a tag to an absolute position in the screen[]:tags() table.
35 -- @param new_index Integer absolute position in the table to insert.
36 function move(new_index, target_tag)
37 local target_tag = target_tag or selected()
38 local scr = target_tag.screen
39 local tmp_tags = capi.screen[scr]:tags()
41 if (not new_index) or (new_index < 1) or (new_index > #tmp_tags) then
42 return
43 end
45 for i, t in ipairs(tmp_tags) do
46 if t == target_tag then
47 table.remove(tmp_tags, i)
48 break
49 end
50 end
52 table.insert(tmp_tags, new_index, target_tag)
53 capi.screen[scr]:tags(tmp_tags)
54 end
56 --- Add a tag.
57 -- @param name The tag name, a string
58 -- @param props The tags properties, a table
59 -- @return The created tag
60 function add(name, props)
61 local properties = props or {}
62 local newtag = capi.tag{name = name}
63 newtag.screen = properties.screen or capi.mouse.screen
65 for k, v in pairs(properties) do
66 setproperty(newtag, k, v)
67 end
69 return newtag
70 end
72 --- Create a set of tags and attach it to a screen.
73 -- @param names The tag name, in a table
74 -- @param screen The tag screen, or 1 if not set.
75 -- @param layout The layout or layout table to set for this tags by default.
76 -- @return A table with all created tags.
77 function new(names, screen, layout)
78 local screen = screen or 1
79 local tags = {}
80 for id, name in ipairs(names) do
81 table.insert(tags, id, add(name, {screen = screen,
82 layout = (layout and layout[id]) or
83 layout}))
84 -- Select the first tag.
85 if id == 1 then
86 tags[id].selected = true
87 end
88 end
90 return tags
91 end
93 --- Find a suitable fallback tag.
94 -- @param screen The screen number to look for a tag on. [mouse.screen]
95 -- @param target A table of tags we consider unacceptable. [selectedlist(scr)]
96 function find_fallback(screen, invalids)
97 local scr = screen or capi.mouse.screen
98 local t = invalids or selectedlist(scr)
100 for _, v in pairs(capi.screen[scr]:tags()) do
101 if not util.table.hasitem(t, v) then return v end
105 --- Delete a tag.
106 -- @param target_tag Optional tag object to delete. [selected()]
107 -- @param fallback_tag Tag to assign stickied tags to. [~selected()]
108 -- @return Returns true if the tag is successfully deleted, nil otherwise.
109 -- If there are no clients exclusively on this tag then delete it. Any
110 -- stickied clients are assigned to the optional 'fallback_tag'.
111 -- If after deleting the tag there is no selected tag, try and restore from
112 -- history or select the first tag on the screen.
113 function delete(target_tag, fallback_tag)
114 -- abort if no tag is passed or currently selected
115 local target_tag = target_tag or selected()
116 if target_tag == nil then return end
118 local ntags = #capi.screen[target_tag.screen]:tags()
119 local target_scr = target_tag.screen
121 -- We can't use the target tag as a fallback.
122 local fallback_tag = fallback_tag
123 if fallback_tag == target_tag then return end
125 -- No fallback_tag provided, try and get one.
126 if fallback_tag == nil then
127 fallback_tag = find_fallback(target_scr, {target_tag})
130 -- Abort if we would have un-tagged clients.
131 local clients = target_tag:clients()
132 if ( #clients > 0 and ntags <= 1 ) or fallback_tag == nil then return end
134 -- Move the clients we can off of this tag.
135 for _, c in pairs(clients) do
137 -- If a client has only this tag, or stickied clients with
138 -- nowhere to go, abort.
139 if (not c.sticky and #c:tags() == 1) or
140 (c.sticky and fallback_tag == nil) then
141 return
142 else
143 c:tags({fallback_tag})
147 -- delete the tag
148 target_tag.screen = nil
150 -- If no tags are visible, try and view one.
151 if selected(target_scr) == nil and ntags > 0 then
152 history.restore()
153 if selected(target_scr) == nil then
154 capi.screen[target_scr]:tags()[1].selected = true
158 return true
161 --- Update the tag history.
162 -- @param obj Screen object.
163 function history.update(obj)
164 local s = obj.index
165 local curtags = selectedlist(s)
166 -- create history table
167 if not data.history[s] then
168 data.history[s] = {}
169 else
170 if data.history[s].current then
171 -- Check that the list is not identical
172 local identical = true
173 for idx, tag in ipairs(data.history[s].current) do
174 if curtags[idx] ~= tag then
175 identical = false
176 break
180 -- Do not update history the table are identical
181 if identical then return end
184 -- Limit history
185 if #data.history[s] >= history.limit then
186 for i = history.limit, #data.history[s] do
187 data.history[s][i] = nil
192 -- store previously selected tags in the history table
193 table.insert(data.history[s], 1, data.history[s].current)
194 data.history[s].previous = data.history[s][1]
195 -- store currently selected tags
196 data.history[s].current = setmetatable(curtags, { __mode = 'v' })
199 --- Revert tag history.
200 -- @param screen The screen number.
201 -- @param idx Index in history. Defaults to "previous" which is a special index
202 -- toggling between last two selected sets of tags. Number (eg 1) will go back
203 -- to the given index in history.
204 function history.restore(screen, idx)
205 local s = screen or capi.mouse.screen
206 local i = idx or "previous"
207 local sel = selectedlist(s)
208 -- do nothing if history empty
209 if not data.history[s] or not data.history[s][i] then return end
210 -- if all tags been deleted, try next entry
211 if #data.history[s][i] == 0 then
212 if i == "previous" then i = 0 end
213 history.restore(s, i + 1)
214 return
216 -- deselect all tags
217 viewnone(s)
218 -- select tags from the history entry
219 for _, t in ipairs(data.history[s][i]) do
220 t.selected = true
222 -- update currently selected tags table
223 data.history[s].current = data.history[s][i]
224 -- store previously selected tags
225 data.history[s].previous = setmetatable(sel, { __mode = 'v' })
226 -- remove the reverted history entry
227 if i ~= "previous" then table.remove(data.history[s], i) end
230 --- Return a table with all visible tags
231 -- @param s Screen number.
232 -- @return A table with all selected tags.
233 function selectedlist(s)
234 local screen = s or capi.mouse.screen
235 local tags = capi.screen[screen]:tags()
236 local vtags = {}
237 for i, t in pairs(tags) do
238 if t.selected then
239 vtags[#vtags + 1] = t
242 return vtags
245 --- Return only the first visible tag.
246 -- @param s Screen number.
247 function selected(s)
248 return selectedlist(s)[1]
251 --- Set master width factor.
252 -- @param mwfact Master width factor.
253 function setmwfact(mwfact, t)
254 local t = t or selected()
255 if mwfact >= 0 and mwfact <= 1 then
256 setproperty(t, "mwfact", mwfact)
260 --- Increase master width factor.
261 -- @param add Value to add to master width factor.
262 function incmwfact(add, t)
263 setmwfact(getmwfact(t) + add)
266 --- Get master width factor.
267 -- @param t Optional tag.
268 function getmwfact(t)
269 local t = t or selected()
270 return getproperty(t, "mwfact") or 0.5
273 --- Set the number of master windows.
274 -- @param nmaster The number of master windows.
275 -- @param t Optional tag.
276 function setnmaster(nmaster, t)
277 local t = t or selected()
278 if nmaster >= 0 then
279 setproperty(t, "nmaster", nmaster)
283 --- Get the number of master windows.
284 -- @param t Optional tag.
285 function getnmaster(t)
286 local t = t or selected()
287 return getproperty(t, "nmaster") or 1
290 --- Increase the number of master windows.
291 -- @param add Value to add to number of master windows.
292 function incnmaster(add, t)
293 setnmaster(getnmaster(t) + add)
297 --- Set the tag icon
298 -- @param icon the icon to set, either path or image object
299 -- @param tag the tag
300 function seticon(icon, tag)
301 local tag = tag or selected()
302 setproperty(tag, "icon", icon)
305 --- Get the tag icon
306 -- @param t the tag
307 function geticon(tag)
308 local tag = tag or selected()
309 return getproperty(tag, "icon")
312 --- Set number of column windows.
313 -- @param ncol The number of column.
314 function setncol(ncol, t)
315 local t = t or selected()
316 if ncol >= 1 then
317 setproperty(t, "ncol", ncol)
321 --- Get number of column windows.
322 -- @param t Optional tag.
323 function getncol(t)
324 local t = t or selected()
325 return getproperty(t, "ncol") or 1
328 --- Increase number of column windows.
329 -- @param add Value to add to number of column windows.
330 function incncol(add, t)
331 setncol(getncol(t) + add)
334 --- View no tag.
335 -- @param Optional screen number.
336 function viewnone(screen)
337 local tags = capi.screen[screen or capi.mouse.screen]:tags()
338 for i, t in pairs(tags) do
339 t.selected = false
343 --- View a tag by its taglist index.
344 -- @param i The relative index to see.
345 -- @param screen Optional screen number.
346 function viewidx(i, screen)
347 local screen = screen and screen.index or capi.mouse.screen
348 local tags = capi.screen[screen]:tags()
349 local showntags = {}
350 for k, t in ipairs(tags) do
351 if not getproperty(t, "hide") then
352 table.insert(showntags, t)
355 local sel = selected(screen)
356 viewnone(screen)
357 for k, t in ipairs(showntags) do
358 if t == sel then
359 showntags[util.cycle(#showntags, k + i)].selected = true
362 capi.screen[screen]:emit_signal("tag::history::update")
365 --- Get a tag's index in the screen[]:tags() table.
366 -- @param query_tag The tag object to find. [selected()]
367 -- @return The index of the tag, nil if the tag is not found.
368 function getidx(query_tag)
369 local query_tag = query_tag or selected()
370 if query_tag == nil then return end
372 for i, t in ipairs(capi.screen[query_tag.screen]:tags()) do
373 if t == query_tag then
374 return i
379 --- View next tag. This is the same as tag.viewidx(1).
380 -- @param screen The screen number.
381 function viewnext(screen)
382 return viewidx(1, screen)
385 --- View previous tag. This is the same a tag.viewidx(-1).
386 -- @param screen The screen number.
387 function viewprev(screen)
388 return viewidx(-1, screen)
391 --- View only a tag.
392 -- @param t The tag object.
393 function viewonly(t)
394 local tags = capi.screen[t.screen]:tags()
395 -- First, untag everyone except the viewed tag.
396 for _, tag in pairs(tags) do
397 if tag ~= t then
398 tag.selected = false
401 -- Then, set this one to selected.
402 -- We need to do that in 2 operations so we avoid flickering and several tag
403 -- selected at the same time.
404 t.selected = true
405 capi.screen[t.screen]:emit_signal("tag::history::update")
408 --- View only a set of tags.
409 -- @param tags A table with tags to view only.
410 -- @param screen Optional screen number of the tags.
411 function viewmore(tags, screen)
412 local screen_tags = capi.screen[screen or capi.mouse.screen]:tags()
413 for _, tag in ipairs(screen_tags) do
414 if not util.table.hasitem(tags, tag) then
415 tag.selected = false
418 for _, tag in ipairs(tags) do
419 tag.selected = true
421 capi.screen[screen]:emit_signal("tag::history::update")
424 --- Toggle selection of a tag
425 -- @param tag Tag to be toggled
426 function viewtoggle(t)
427 t.selected = not t.selected
428 capi.screen[t.screen]:emit_signal("tag::history::update")
431 --- Get tag data table.
432 -- @param tag The Tag.
433 -- @return The data table.
434 function getdata(tag)
435 return data.tags[tag]
438 --- Get a tag property.
439 -- @param tag The tag.
440 -- @param prop The property name.
441 -- @return The property.
442 function getproperty(tag, prop)
443 if data.tags[tag] then
444 return data.tags[tag][prop]
448 --- Set a tag property.
449 -- This properties are internal to awful. Some are used to draw taglist, or to
450 -- handle layout, etc.
451 -- @param tag The tag.
452 -- @param prop The property name.
453 -- @param value The value.
454 function setproperty(tag, prop, value)
455 if not data.tags[tag] then
456 data.tags[tag] = {}
458 data.tags[tag][prop] = value
459 tag:emit_signal("property::" .. prop)
462 --- Tag a client with the set of current tags.
463 -- @param c The client to tag.
464 -- @param startup Optional: don't do anything if true.
465 function withcurrent(c, startup)
466 if startup ~= true then
467 if #c:tags() == 0 then
468 c:tags(selectedlist(c.screen))
473 local function attached_connect_signal_screen(screen, sig, func)
474 capi.screen[screen]:connect_signal("tag::attach", function (s, tag)
475 tag:connect_signal(sig, func)
476 end)
477 capi.screen[screen]:connect_signal("tag::detach", function (s, tag)
478 tag:disconnect_signal(sig, func)
479 end)
480 for _, tag in ipairs(capi.screen[screen]:tags()) do
481 tag:connect_signal(sig, func)
485 --- Add a signal to all attached tag and all tag that will be attached in the
486 -- future. When a tag is detach from the screen, its signal is removed.
487 -- @param screen The screen concerned, or all if nil.
488 function attached_connect_signal(screen, ...)
489 if screen then
490 attached_connect_signal_screen(screen, ...)
491 else
492 for screen = 1, capi.screen.count() do
493 attached_connect_signal_screen(screen, ...)
498 -- Register standards signals
499 capi.client.connect_signal("manage", function(c, startup)
500 -- If we are not managing this application at startup,
501 -- move it to the screen where the mouse is.
502 -- We only do it for "normal" windows (i.e. no dock, etc).
503 if not startup
504 and c.type ~= "desktop"
505 and c.type ~= "dock"
506 and c.type ~= "splash" then
507 if c.transient_for then
508 c.screen = c.transient_for.screen
509 if not c.sticky then
510 c:tags(c.transient_for:tags())
512 else
513 c.screen = capi.mouse.screen
516 c:connect_signal("property::screen", withcurrent)
517 end)
519 capi.client.connect_signal("manage", withcurrent)
521 capi.tag.add_signal("property::hide")
522 capi.tag.add_signal("property::icon")
523 capi.tag.add_signal("property::layout")
524 capi.tag.add_signal("property::mwfact")
525 capi.tag.add_signal("property::ncol")
526 capi.tag.add_signal("property::nmaster")
527 capi.tag.add_signal("property::windowfact")
529 for s = 1, capi.screen.count() do
530 capi.screen[s]:add_signal("tag::history::update")
531 capi.screen[s]:connect_signal("tag::history::update", history.update)
534 setmetatable(_M, { __call = function (_, ...) return new(...) end })
536 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80