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
13 local setmetatable
= setmetatable
23 --- Useful functions for tag manipulation.
25 local tag = { mt
= {} }
30 data
.tags
= setmetatable({}, { __mode
= 'k' })
34 tag.history
.limit
= 20
36 --- Move a tag to an absolute position in the screen[]:tags() table.
37 -- @param new_index Integer absolute position in the table to insert.
38 -- @param target_tag The tag that should be moved. If null, the currently
39 -- selected tag is used.
40 function tag.move(new_index
, target_tag
)
41 local target_tag
= target_tag
or tag.selected()
42 local scr
= tag.getscreen(target_tag
)
43 local tmp_tags
= tag.gettags(scr
)
45 if (not new_index
) or (new_index
< 1) or (new_index
> #tmp_tags
) then
49 for i
, t
in ipairs(tmp_tags
) do
50 if t
== target_tag
then
51 table.remove(tmp_tags
, i
)
56 table.insert(tmp_tags
, new_index
, target_tag
)
58 for i
, tmp_tag
in ipairs(tmp_tags
) do
59 tag.setproperty(tmp_tag
, "index", i
)
60 tag.setscreen(tmp_tag
, scr
)
65 -- @param name The tag name, a string
66 -- @param props The tags properties, a table
67 -- @return The created tag
68 function tag.add(name
, props
)
69 local properties
= props
or {}
70 local newtag = capi
.tag{ name
= name
, activated
= true }
71 properties
.screen
= properties
.screen
or capi
.mouse
.screen
73 for k
, v
in pairs(properties
) do
74 tag.setproperty(newtag, k
, v
)
80 --- Create a set of tags and attach it to a screen.
81 -- @param names The tag name, in a table
82 -- @param screen The tag screen, or 1 if not set.
83 -- @param layout The layout or layout table to set for this tags by default.
84 -- @return A table with all created tags.
85 function tag.new(names
, screen
, layout
)
86 local screen
= screen
or 1
88 for id
, name
in ipairs(names
) do
89 table.insert(tags
, id
, tag.add(name
, {screen
= screen
,
90 layout
= (layout
and layout
[id
]) or
92 -- Select the first tag.
94 tags
[id
].selected
= true
101 --- Find a suitable fallback tag.
102 -- @param screen The screen number to look for a tag on. [mouse.screen]
103 -- @param invalids A table of tags we consider unacceptable. [selectedlist(scr)]
104 function tag.find_fallback(screen
, invalids
)
105 local scr
= screen
or capi
.mouse
.screen
106 local t
= invalids
or tag.selectedlist(scr
)
108 for _
, v
in pairs(tag.gettags(scr
)) do
109 if not util
.table.hasitem(t
, v
) then return v
end
114 -- @param target_tag Optional tag object to delete. [selected()]
115 -- @param fallback_tag Tag to assign stickied tags to. [~selected()]
116 -- @return Returns true if the tag is successfully deleted, nil otherwise.
117 -- If there are no clients exclusively on this tag then delete it. Any
118 -- stickied clients are assigned to the optional 'fallback_tag'.
119 -- If after deleting the tag there is no selected tag, try and restore from
120 -- history or select the first tag on the screen.
121 function tag.delete(target_tag
, fallback_tag
)
122 -- abort if no tag is passed or currently selected
123 local target_tag
= target_tag
or tag.selected()
124 if target_tag
== nil then return end
126 local target_scr
= tag.getscreen(target_tag
)
127 local ntags
= #tag.gettags(target_scr
)
129 -- We can't use the target tag as a fallback.
130 local fallback_tag
= fallback_tag
131 if fallback_tag
== target_tag
then return end
133 -- No fallback_tag provided, try and get one.
134 if fallback_tag
== nil then
135 fallback_tag
= tag.find_fallback(target_scr
, {target_tag
})
138 -- Abort if we would have un-tagged clients.
139 local clients
= target_tag
:clients()
140 if ( #clients
> 0 and ntags
<= 1 ) or fallback_tag
== nil then return end
142 -- Move the clients we can off of this tag.
143 for _
, c
in pairs(clients
) do
145 -- If a client has only this tag, or stickied clients with
146 -- nowhere to go, abort.
147 if (not c
.sticky
and #c
:tags() == 1) or
148 (c
.sticky
and fallback_tag
== nil) then
151 c
:tags({fallback_tag
})
156 data
.tags
[target_tag
].screen
= nil
158 -- If no tags are visible, try and view one.
159 if tag.selected(target_scr
) == nil and ntags
> 0 then
160 tag.history
.restore()
161 if tag.selected(target_scr
) == nil then
162 tag.gettags(target_scr
)[1].selected
= true
169 --- Update the tag history.
170 -- @param obj Screen object.
171 function tag.history
.update(obj
)
173 local curtags
= tag.selectedlist(s
)
174 -- create history table
175 if not data
.history
[s
] then
178 if data
.history
[s
].current
then
179 -- Check that the list is not identical
180 local identical
= true
181 for idx
, _tag
in ipairs(data
.history
[s
].current
) do
182 if curtags
[idx
] ~= _tag
then
188 -- Do not update history the table are identical
189 if identical
then return end
193 if #data
.history
[s
] >= tag.history
.limit
then
194 for i
= tag.history
.limit
, #data
.history
[s
] do
195 data
.history
[s
][i
] = nil
200 -- store previously selected tags in the history table
201 table.insert(data
.history
[s
], 1, data
.history
[s
].current
)
202 data
.history
[s
].previous
= data
.history
[s
][1]
203 -- store currently selected tags
204 data
.history
[s
].current
= setmetatable(curtags
, { __mode
= 'v' })
207 --- Revert tag history.
208 -- @param screen The screen number.
209 -- @param idx Index in history. Defaults to "previous" which is a special index
210 -- toggling between last two selected sets of tags. Number (eg 1) will go back
211 -- to the given index in history.
212 function tag.history
.restore(screen
, idx
)
213 local s
= screen
or capi
.mouse
.screen
214 local i
= idx
or "previous"
215 local sel
= tag.selectedlist(s
)
216 -- do nothing if history empty
217 if not data
.history
[s
] or not data
.history
[s
][i
] then return end
218 -- if all tags been deleted, try next entry
219 if #data
.history
[s
][i
] == 0 then
220 if i
== "previous" then i
= 0 end
221 tag.history
.restore(s
, i
+ 1)
226 -- select tags from the history entry
227 for _
, t
in ipairs(data
.history
[s
][i
]) do
230 -- update currently selected tags table
231 data
.history
[s
].current
= data
.history
[s
][i
]
232 -- store previously selected tags
233 data
.history
[s
].previous
= setmetatable(sel
, { __mode
= 'v' })
234 -- remove the reverted history entry
235 if i
~= "previous" then table.remove(data
.history
[s
], i
) end
238 --- Get a list of all tags on a screen
239 -- @param s Screen number
240 -- @return A table with all available tags
241 function tag.gettags(s
)
243 for i
, t
in ipairs(root
.tags()) do
244 if tag.getscreen(t
) == s
then
245 table.insert(tags
, t
)
249 local without_index
= 0
250 for _
, t
in ipairs(tags
) do
251 if not tag.getproperty(t
, "index") then
252 without_index
= without_index
+ 1
255 if without_index
> 0 then
256 for _
, t
in ipairs(tags
) do
257 if not tag.getproperty(t
, "index") then
258 tag.setproperty(t
, "index", (#tags
- without_index
+ 1))
259 without_index
= without_index
- 1
264 table.sort(tags
, function(a
, b
) return tag.getproperty(a
, "index") < tag.getproperty(b
, "index") end)
268 --- Set a tag's screen
269 -- @param t tag object
270 -- @param s Screen number
271 function tag.setscreen(t
, s
)
272 tag.setproperty(t
, "screen", s
)
275 --- Get a tag's screen
276 -- @param t tag object
277 -- @return Screen number
278 function tag.getscreen(t
)
279 return tag.getproperty(t
, "screen")
282 --- Return a table with all visible tags
283 -- @param s Screen number.
284 -- @return A table with all selected tags.
285 function tag.selectedlist(s
)
286 local screen
= s
or capi
.mouse
.screen
287 local tags
= tag.gettags(screen
)
289 for i
, t
in pairs(tags
) do
291 vtags
[#vtags
+ 1] = t
297 --- Return only the first visible tag.
298 -- @param s Screen number.
299 function tag.selected(s
)
300 return tag.selectedlist(s
)[1]
303 --- Set master width factor.
304 -- @param mwfact Master width factor.
305 -- @param t The tag to modify, if null tag.selected() is used.
306 function tag.setmwfact(mwfact
, t
)
307 local t
= t
or tag.selected()
308 if mwfact
>= 0 and mwfact
<= 1 then
309 tag.setproperty(t
, "mwfact", mwfact
)
313 --- Increase master width factor.
314 -- @param add Value to add to master width factor.
315 -- @param t The tag to modify, if null tag.selected() is used.
316 function tag.incmwfact(add
, t
)
317 tag.setmwfact(tag.getmwfact(t
) + add
, t
)
320 --- Get master width factor.
321 -- @param t Optional tag.
322 function tag.getmwfact(t
)
323 local t
= t
or tag.selected()
324 return tag.getproperty(t
, "mwfact") or 0.5
327 --- Set the number of master windows.
328 -- @param nmaster The number of master windows.
329 -- @param t Optional tag.
330 function tag.setnmaster(nmaster
, t
)
331 local t
= t
or tag.selected()
333 tag.setproperty(t
, "nmaster", nmaster
)
337 --- Get the number of master windows.
338 -- @param t Optional tag.
339 function tag.getnmaster(t
)
340 local t
= t
or tag.selected()
341 return tag.getproperty(t
, "nmaster") or 1
344 --- Increase the number of master windows.
345 -- @param add Value to add to number of master windows.
346 -- @param t The tag to modify, if null tag.selected() is used.
347 function tag.incnmaster(add
, t
)
348 tag.setnmaster(tag.getnmaster(t
) + add
, t
)
353 -- @param icon the icon to set, either path or image object
354 -- @param _tag the tag
355 function tag.seticon(icon
, _tag
)
356 local _tag
= _tag
or tag.selected()
357 tag.setproperty(_tag
, "icon", icon
)
361 -- @param _tag the tag
362 function tag.geticon(_tag
)
363 local _tag
= _tag
or tag.selected()
364 return tag.getproperty(_tag
, "icon")
367 --- Set number of column windows.
368 -- @param ncol The number of column.
369 -- @param t The tag to modify, if null tag.selected() is used.
370 function tag.setncol(ncol
, t
)
371 local t
= t
or tag.selected()
373 tag.setproperty(t
, "ncol", ncol
)
377 --- Get number of column windows.
378 -- @param t Optional tag.
379 function tag.getncol(t
)
380 local t
= t
or tag.selected()
381 return tag.getproperty(t
, "ncol") or 1
384 --- Increase number of column windows.
385 -- @param add Value to add to number of column windows.
386 -- @param t The tag to modify, if null tag.selected() is used.
387 function tag.incncol(add
, t
)
388 tag.setncol(tag.getncol(t
) + add
, t
)
392 -- @param Optional screen number.
393 function tag.viewnone(screen
)
394 local tags
= tag.gettags(screen
or capi
.mouse
.screen
)
395 for i
, t
in pairs(tags
) do
400 --- View a tag by its taglist index.
401 -- @param i The relative index to see.
402 -- @param screen Optional screen number.
403 function tag.viewidx(i
, screen
)
404 local screen
= screen
or capi
.mouse
.screen
405 local tags
= tag.gettags(screen
)
407 for k
, t
in ipairs(tags
) do
408 if not tag.getproperty(t
, "hide") then
409 table.insert(showntags
, t
)
412 local sel
= tag.selected(screen
)
414 for k
, t
in ipairs(showntags
) do
416 showntags
[util
.cycle(#showntags
, k
+ i
)].selected
= true
419 capi
.screen
[screen
]:emit_signal("tag::history::update")
422 --- Get a tag's index in the gettags() table.
423 -- @param query_tag The tag object to find. [selected()]
424 -- @return The index of the tag, nil if the tag is not found.
425 function tag.getidx(query_tag
)
426 local query_tag
= query_tag
or tag.selected()
427 if query_tag
== nil then return end
429 for i
, t
in ipairs(tag.gettags(tag.getscreen(query_tag
))) do
430 if t
== query_tag
then
436 --- View next tag. This is the same as tag.viewidx(1).
437 -- @param screen The screen number.
438 function tag.viewnext(screen
)
439 return tag.viewidx(1, screen
)
442 --- View previous tag. This is the same a tag.viewidx(-1).
443 -- @param screen The screen number.
444 function tag.viewprev(screen
)
445 return tag.viewidx(-1, screen
)
449 -- @param t The tag object.
450 function tag.viewonly(t
)
451 local tags
= tag.gettags(tag.getscreen(t
))
452 -- First, untag everyone except the viewed tag.
453 for _
, _tag
in pairs(tags
) do
455 _tag
.selected
= false
458 -- Then, set this one to selected.
459 -- We need to do that in 2 operations so we avoid flickering and several tag
460 -- selected at the same time.
462 capi
.screen
[tag.getscreen(t
)]:emit_signal("tag::history::update")
465 --- View only a set of tags.
466 -- @param tags A table with tags to view only.
467 -- @param screen Optional screen number of the tags.
468 function tag.viewmore(tags
, screen
)
469 local screen_tags
= tag.gettags(screen
or capi
.mouse
.screen
)
470 for _
, _tag
in ipairs(screen_tags
) do
471 if not util
.table.hasitem(tags
, _tag
) then
472 _tag
.selected
= false
475 for _
, _tag
in ipairs(tags
) do
478 capi
.screen
[screen
]:emit_signal("tag::history::update")
481 --- Toggle selection of a tag
482 -- @param tag Tag to be toggled
483 function tag.viewtoggle(t
)
484 t
.selected
= not t
.selected
485 capi
.screen
[tag.getscreen(t
)]:emit_signal("tag::history::update")
488 --- Get tag data table.
489 -- @param tag The Tag.
490 -- @return The data table.
491 function tag.getdata(_tag
)
492 return data
.tags
[_tag
]
495 --- Get a tag property.
496 -- @param _tag The tag.
497 -- @param prop The property name.
498 -- @return The property.
499 function tag.getproperty(_tag
, prop
)
500 if data
.tags
[_tag
] then
501 return data
.tags
[_tag
][prop
]
505 --- Set a tag property.
506 -- This properties are internal to awful. Some are used to draw taglist, or to
507 -- handle layout, etc.
508 -- @param _tag The tag.
509 -- @param prop The property name.
510 -- @param value The value.
511 function tag.setproperty(_tag
, prop
, value
)
512 if not data
.tags
[_tag
] then
515 data
.tags
[_tag
][prop
] = value
516 _tag
:emit_signal("property::" .. prop
)
519 --- Tag a client with the set of current tags.
520 -- @param c The client to tag.
521 -- @param startup Optional: don't do anything if true.
522 function tag.withcurrent(c
, startup
)
523 if startup
~= true then
525 for k
, t
in ipairs(c
:tags()) do
526 if tag.getscreen(t
) == c
.screen
then
527 table.insert(tags
, t
)
531 tags
= tag.selectedlist(c
.screen
)
537 local function attached_connect_signal_screen(screen
, sig
, func
)
538 capi
.tag.connect_signal(sig
, function(_tag
, ...)
539 if tag.getscreen(_tag
) == screen
then
545 --- Add a signal to all attached tag and all tag that will be attached in the
546 -- future. When a tag is detach from the screen, its signal is removed.
547 -- @param screen The screen concerned, or all if nil.
548 function tag.attached_connect_signal(screen
, ...)
550 attached_connect_signal_screen(screen
, ...)
552 capi
.tag.connect_signal(...)
556 -- Register standards signals
557 capi
.client
.connect_signal("manage", function(c
, startup
)
558 -- If we are not managing this application at startup,
559 -- move it to the screen where the mouse is.
560 -- We only do it for "normal" windows (i.e. no dock, etc).
561 if not startup
and c
.type ~= "desktop" and c
.type ~= "dock" then
562 if c
.transient_for
then
563 c
.screen
= c
.transient_for
.screen
565 c
:tags(c
.transient_for
:tags())
568 c
.screen
= capi
.mouse
.screen
571 c
:connect_signal("property::screen", tag.withcurrent
)
574 capi
.client
.connect_signal("manage", tag.withcurrent
)
576 capi
.tag.add_signal("property::hide")
577 capi
.tag.add_signal("property::icon")
578 capi
.tag.add_signal("property::layout")
579 capi
.tag.add_signal("property::mwfact")
580 capi
.tag.add_signal("property::ncol")
581 capi
.tag.add_signal("property::nmaster")
582 capi
.tag.add_signal("property::windowfact")
583 capi
.tag.add_signal("property::screen")
584 capi
.tag.add_signal("property::index")
586 for s
= 1, capi
.screen
.count() do
587 capi
.screen
[s
]:add_signal("tag::history::update")
588 capi
.screen
[s
]:connect_signal("tag::history::update", tag.history
.update
)
591 function tag.mt
:__call(...)
595 return setmetatable(tag, tag.mt
)
597 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80