1---------------------------------------------------------------------------
2--- Useful functions for tag manipulation.
3--
4-- @author Julien Danjou <julien@danjou.info>
5-- @copyright 2008 Julien Danjou
6-- @module tag
7---------------------------------------------------------------------------
8
9-- Grab environment we need
10local gdebug = require("gears.debug")
11local ascreen = require("awful.screen")
12local beautiful = require("beautiful")
13local gmath = require("gears.math")
14local object = require("gears.object")
15local timer = require("gears.timer")
16local gtable = require("gears.table")
17local alayout = nil
18local pairs = pairs
19local ipairs = ipairs
20local table = table
21local setmetatable = setmetatable
22local capi =
23{
24    tag = tag,
25    screen = screen,
26    mouse = mouse,
27    client = client,
28    root = root
29}
30
31local function get_screen(s)
32    return s and capi.screen[s]
33end
34
35local tag = {object = {},  mt = {} }
36
37-- Private data
38local data = {}
39data.history = {}
40
41-- History functions
42tag.history = {}
43tag.history.limit = 20
44
45-- Default values
46local defaults = {}
47
48-- The gap between clients (in points).
49defaults.gap                 = 0
50
51-- The default gap_count.
52defaults.gap_single_client   = true
53
54-- The default master fill policy.
55defaults.master_fill_policy  = "expand"
56
57-- The default master width factor.
58defaults.master_width_factor = 0.5
59
60-- The default master count.
61defaults.master_count        = 1
62
63-- The default column count.
64defaults.column_count        = 1
65
66-- screen.tags depend on index, it cannot be used by awful.tag
67local function raw_tags(scr)
68    local tmp_tags = {}
69    for _, t in ipairs(root.tags()) do
70        if get_screen(t.screen) == scr then
71            table.insert(tmp_tags, t)
72        end
73    end
74
75    return tmp_tags
76end
77
78local function custom_layouts(self)
79    local cls = tag.getproperty(self, "_custom_layouts")
80
81    if not cls then
82        cls = {}
83        tag.setproperty(self, "_custom_layouts", cls)
84    end
85
86    return cls
87end
88
89-- Update the "user visible" list of layouts. If `from` and `to` are not the
90-- same, then `from` will be replaced. This is necessary for either the layouts
91-- defined as a function (called "template" below) and object oriented, stateful
92-- layouts where the original entry is only a constructor.
93local function update_layouts(self, from, to)
94    if not to then return end
95
96    alayout = alayout or require("awful.layout")
97    local override = tag.getproperty(self, "_layouts")
98
99    local pos = from and gtable.hasitem(override or {}, from) or nil
100
101    -- There is an override and the layout template is part of it, replace by
102    -- the instance.
103    if override and pos and from ~= to then
104        assert(type(pos) == 'number')
105        override[pos] = to
106        self:emit_signal("property::layouts")
107        return
108    end
109
110    -- Only add to the custom_layouts and preserve the ability to globally
111    -- set the layouts.
112    if override and not pos then
113        table.insert(override, to)
114        self:emit_signal("property::layouts")
115        return
116    end
117
118    pos = from and gtable.hasitem(alayout.layouts, from) or nil
119
120    local cls = custom_layouts(self)
121
122    -- The new layout is part of the global layouts. Fork the list.
123    if pos and from ~= to then
124        local cloned = gtable.clone(alayout.layouts, false)
125        cloned[pos] = to
126        gtable.merge(cloned, cls)
127        self.layouts = cloned
128        return
129    end
130
131    if pos then return end
132
133    if gtable.hasitem(cls, to) then return end
134
135    -- This layout is unknown, add it to the custom list
136    table.insert(cls, to)
137    self:emit_signal("property::layouts")
138end
139
140--- The number of elements kept in the history.
141-- @tfield integer awful.tag.history.limit
142-- @tparam[opt=20] integer limit
143
144--- The tag index.
145--
146-- The index is the position as shown in the `awful.widget.taglist`.
147--
148-- **Signal:**
149--
150-- * *property::index*
151--
152-- @property index
153-- @param integer
154-- @treturn number The tag index.
155
156function tag.object.set_index(self, idx)
157    local scr = get_screen(tag.getproperty(self, "screen"))
158
159    -- screen.tags cannot be used as it depend on index
160    local tmp_tags = raw_tags(scr)
161
162    -- sort the tags by index
163    table.sort(tmp_tags, function(a, b)
164        local ia, ib = tag.getproperty(a, "index"), tag.getproperty(b, "index")
165        return (ia or math.huge) < (ib or math.huge)
166    end)
167
168    if (not idx) or (idx < 1) or (idx > #tmp_tags) then
169        return
170    end
171
172    local rm_index = nil
173
174    for i, t in ipairs(tmp_tags) do
175        if t == self then
176            table.remove(tmp_tags, i)
177            rm_index = i
178            break
179        end
180    end
181
182    table.insert(tmp_tags, idx, self)
183    for i = idx < rm_index and idx or rm_index, #tmp_tags do
184        local tmp_tag = tmp_tags[i]
185        tag.object.set_screen(tmp_tag, scr)
186        tag.setproperty(tmp_tag, "index", i)
187    end
188end
189
190function tag.object.get_index(query_tag)
191
192    local idx = tag.getproperty(query_tag, "index")
193
194    if idx then return idx end
195
196    -- Get an unordered list of tags
197    local tags = raw_tags(query_tag.screen)
198
199    -- Too bad, lets compute it
200    for i, t in ipairs(tags) do
201        if t == query_tag then
202            tag.setproperty(t, "index", i)
203            return i
204        end
205    end
206end
207
208--- Move a tag to an absolute position in the screen[]:tags() table.
209-- @deprecated awful.tag.move
210-- @param new_index Integer absolute position in the table to insert.
211-- @param target_tag The tag that should be moved. If null, the currently
212-- selected tag is used.
213-- @see index
214function tag.move(new_index, target_tag)
215    gdebug.deprecate("Use t.index = new_index instead of awful.tag.move", {deprecated_in=4})
216
217    target_tag = target_tag or ascreen.focused().selected_tag
218    tag.object.set_index(target_tag, new_index)
219end
220
221--- Swap 2 tags
222-- @function tag.swap
223-- @param tag2 The second tag
224-- @see client.swap
225function tag.object.swap(self, tag2)
226    local idx1, idx2 = tag.object.get_index(self), tag.object.get_index(tag2)
227    local scr2, scr1 = tag.getproperty(tag2, "screen"), tag.getproperty(self, "screen")
228
229    -- If they are on the same screen, avoid recomputing the whole table
230    -- for nothing.
231    if scr1 == scr2 then
232        tag.setproperty(self, "index", idx2)
233        tag.setproperty(tag2, "index", idx1)
234    else
235        tag.object.set_screen(tag2, scr1)
236        tag.object.set_index (tag2, idx1)
237        tag.object.set_screen(self, scr2)
238        tag.object.set_index (self, idx2)
239    end
240end
241
242--- Swap 2 tags
243-- @deprecated awful.tag.swap
244-- @see tag.swap
245-- @param tag1 The first tag
246-- @param tag2 The second tag
247function tag.swap(tag1, tag2)
248    gdebug.deprecate("Use t:swap(tag2) instead of awful.tag.swap", {deprecated_in=4})
249
250    tag.object.swap(tag1, tag2)
251end
252
253--- Add a tag.
254--
255-- This function allow to create tags from a set of properties:
256--
257--    local t = awful.tag.add("my new tag", {
258--        screen = screen.primary,
259--        layout = awful.layout.suit.max,
260--    })
261--
262-- @function awful.tag.add
263-- @param name The tag name, a string
264-- @param props The tags inital properties, a table
265-- @return The created tag
266-- @see tag.delete
267function tag.add(name, props)
268    local properties = props or {}
269
270    -- Be sure to set the screen before the tag is activated to avoid function
271    -- connected to property::activated to be called without a valid tag.
272    -- set properties cannot be used as this has to be set before the first
273    -- signal is sent
274    properties.screen = get_screen(properties.screen or ascreen.focused())
275    -- Index is also required
276    properties.index = properties.index or #raw_tags(properties.screen)+1
277
278    local newtag = capi.tag{ name = name }
279
280    -- Start with a fresh property table to avoid collisions with unsupported data
281    newtag.data.awful_tag_properties = {screen=properties.screen, index=properties.index}
282
283    newtag.activated = true
284
285    for k, v in pairs(properties) do
286        -- `rawget` doesn't work on userdata, `:clients()` is the only relevant
287        -- entry.
288        if k == "clients" or tag.object[k] then
289            newtag[k](newtag, v)
290        else
291            newtag[k] = v
292        end
293    end
294
295    return newtag
296end
297
298--- Create a set of tags and attach it to a screen.
299-- @function awful.tag.new
300-- @param names The tag name, in a table
301-- @param screen The tag screen, or 1 if not set.
302-- @param layout The layout or layout table to set for this tags by default.
303-- @return A table with all created tags.
304function tag.new(names, screen, layout)
305    screen = get_screen(screen or 1)
306    -- True if `layout` should be used as the layout of each created tag
307    local have_single_layout = (not layout) or (layout.arrange and layout.name)
308    local tags = {}
309    for id, name in ipairs(names) do
310        local l = layout
311        if not have_single_layout then
312            l = layout[id] or layout[1]
313        end
314        table.insert(tags, id, tag.add(name, {screen = screen, layout = l}))
315        -- Select the first tag.
316        if id == 1 then
317            tags[id].selected = true
318        end
319    end
320
321    return tags
322end
323
324--- Find a suitable fallback tag.
325-- @function awful.tag.find_fallback
326-- @param screen The screen to look for a tag on. [awful.screen.focused()]
327-- @param invalids A table of tags we consider unacceptable. [selectedlist(scr)]
328function tag.find_fallback(screen, invalids)
329    local scr = screen or ascreen.focused()
330    local t = invalids or scr.selected_tags
331
332    for _, v in pairs(scr.tags) do
333        if not gtable.hasitem(t, v) then return v end
334    end
335end
336
337--- Delete a tag.
338--
339-- To delete the current tag:
340--
341--    mouse.screen.selected_tag:delete()
342--
343-- @function tag.delete
344-- @see awful.tag.add
345-- @see awful.tag.find_fallback
346-- @tparam[opt=awful.tag.find_fallback()] tag fallback_tag Tag to assign
347--  stickied tags to.
348-- @tparam[opt=false] boolean force Move even non-sticky clients to the fallback
349-- tag.
350-- @return Returns true if the tag is successfully deleted.
351-- If there are no clients exclusively on this tag then delete it. Any
352-- stickied clients are assigned to the optional 'fallback_tag'.
353-- If after deleting the tag there is no selected tag, try and restore from
354-- history or select the first tag on the screen.
355function tag.object.delete(self, fallback_tag, force)
356    -- abort if the taf isn't currently activated
357    if not self.activated then return false end
358
359    local target_scr = get_screen(tag.getproperty(self, "screen"))
360    local tags       = target_scr.tags
361    local idx        = tag.object.get_index(self)
362    local ntags      = #tags
363
364    -- We can't use the target tag as a fallback.
365    if fallback_tag == self then return false end
366
367    -- No fallback_tag provided, try and get one.
368    if fallback_tag == nil then
369        fallback_tag = tag.find_fallback(target_scr, {self})
370    end
371
372    -- Abort if we would have un-tagged clients.
373    local clients = self:clients()
374    if #clients > 0 and fallback_tag == nil then return false end
375
376    -- Move the clients we can off of this tag.
377    for _, c in pairs(clients) do
378        local nb_tags = #c:tags()
379
380        -- If a client has only this tag, or stickied clients with
381        -- nowhere to go, abort.
382        if (not c.sticky and nb_tags == 1 and not force) then
383            return
384        -- If a client has multiple tags, then do not move it to fallback
385        elseif nb_tags < 2 then
386            c:tags({fallback_tag})
387        end
388    end
389
390    -- delete the tag
391    self.data.awful_tag_properties.screen = nil
392    self.activated = false
393
394    -- Update all indexes
395    for i=idx+1, #tags do
396        tag.setproperty(tags[i], "index", i-1)
397    end
398
399    -- If no tags are visible (and we did not delete the lasttag), try and
400    -- view one. The > 1 is because ntags is no longer synchronized with the
401    -- current count.
402    if target_scr.selected_tag == nil and ntags > 1 then
403        tag.history.restore(target_scr, 1)
404        if target_scr.selected_tag == nil then
405            local other_tag = tags[tags[1] == self and 2 or 1]
406            if other_tag then
407                other_tag.selected = true
408            end
409        end
410    end
411
412    return true
413end
414
415--- Delete a tag.
416-- @deprecated awful.tag.delete
417-- @see tag.delete
418-- @param target_tag Optional tag object to delete. [selected()]
419-- @param fallback_tag Tag to assign stickied tags to. [~selected()]
420-- @return Returns true if the tag is successfully deleted, nil otherwise.
421-- If there are no clients exclusively on this tag then delete it. Any
422-- stickied clients are assigned to the optional 'fallback_tag'.
423-- If after deleting the tag there is no selected tag, try and restore from
424-- history or select the first tag on the screen.
425function tag.delete(target_tag, fallback_tag)
426    gdebug.deprecate("Use t:delete(fallback_tag) instead of awful.tag.delete", {deprecated_in=4})
427
428    return tag.object.delete(target_tag, fallback_tag)
429end
430
431--- Update the tag history.
432-- @function awful.tag.history.update
433-- @param obj Screen object.
434function tag.history.update(obj)
435    local s = get_screen(obj)
436    local curtags = s.selected_tags
437    -- create history table
438    if not data.history[s] then
439        data.history[s] = {}
440    else
441        if data.history[s].current then
442            -- Check that the list is not identical
443            local identical = #data.history[s].current == #curtags
444            if identical then
445                for idx, _tag in ipairs(data.history[s].current) do
446                    if curtags[idx] ~= _tag then
447                        identical = false
448                        break
449                    end
450                end
451            end
452
453            -- Do not update history the table are identical
454            if identical then return end
455        end
456
457        -- Limit history
458        if #data.history[s] >= tag.history.limit then
459            for i = tag.history.limit, #data.history[s] do
460                data.history[s][i] = nil
461            end
462        end
463    end
464
465    -- store previously selected tags in the history table
466    table.insert(data.history[s], 1, data.history[s].current)
467    data.history[s].previous = data.history[s][1]
468    -- store currently selected tags
469    data.history[s].current = setmetatable(curtags, { __mode = 'v' })
470end
471
472--- Revert tag history.
473-- @function awful.tag.history.restore
474-- @param screen The screen.
475-- @param idx Index in history. Defaults to "previous" which is a special index
476-- toggling between last two selected sets of tags. Number (eg 1) will go back
477-- to the given index in history.
478function tag.history.restore(screen, idx)
479    local s = get_screen(screen or ascreen.focused())
480    local i = idx or "previous"
481    local sel = s.selected_tags
482    -- do nothing if history empty
483    if not data.history[s] or not data.history[s][i] then return end
484    -- if all tags been deleted, try next entry
485    if #data.history[s][i] == 0 then
486        if i == "previous" then i = 0 end
487        tag.history.restore(s, i + 1)
488        return
489    end
490    -- deselect all tags
491    tag.viewnone(s)
492    -- select tags from the history entry
493    for _, t in ipairs(data.history[s][i]) do
494        if t.activated and t.screen then
495            t.selected = true
496        end
497    end
498    -- update currently selected tags table
499    data.history[s].current = data.history[s][i]
500    -- store previously selected tags
501    data.history[s].previous = setmetatable(sel, { __mode = 'v' })
502    -- remove the reverted history entry
503    if i ~= "previous" then table.remove(data.history[s], i) end
504
505    s:emit_signal("tag::history::update")
506end
507
508--- Get a list of all tags on a screen
509-- @deprecated awful.tag.gettags
510-- @tparam screen s Screen
511-- @return A table with all available tags
512-- @see screen.tags
513function tag.gettags(s)
514    gdebug.deprecate("Use s.tags instead of awful.tag.gettags", {deprecated_in=4})
515
516    s = get_screen(s)
517
518    return s and s.tags or {}
519end
520
521--- Find a tag by name.
522-- @tparam screen s The screen of the tag
523-- @tparam string name The name of the tag
524-- @return The tag found, or `nil`
525-- @usage -- For the current screen
526-- local t = awful.tag.find_by_name(awful.screen.focused(), "name")
527--
528-- -- For a screen index
529-- local t = awful.tag.find_by_name(screen[1], "name")
530--
531-- -- For all screens
532-- local t = awful.tag.find_by_name(nil, "name")
533function tag.find_by_name(s, name)
534    --TODO v5: swap the arguments and make screen [opt]
535    local tags = s and s.tags or root.tags()
536    for _, t in ipairs(tags) do
537        if name == t.name then
538            return t
539        end
540    end
541end
542
543--- The tag screen.
544--
545-- **Signal:**
546--
547-- * *property::screen*
548--
549-- @property screen
550-- @param screen
551-- @see screen
552
553function tag.object.set_screen(t, s)
554
555    s = get_screen(s or ascreen.focused())
556    local sel = t.selected
557    local old_screen = get_screen(tag.getproperty(t, "screen"))
558
559    if s == old_screen then return end
560
561    -- Keeping the old index make very little sense when changing screen
562    tag.setproperty(t, "index", nil)
563
564    -- Change the screen
565    tag.setproperty(t, "screen", s)
566    if s then
567        tag.setproperty(t, "index", #s:get_tags(true))
568    end
569
570    -- Make sure the client's screen matches its tags
571    for _,c in ipairs(t:clients()) do
572        c.screen = s --Move all clients
573        c:tags({t})
574    end
575
576    if old_screen then
577        -- Update all indexes
578        for i,t2 in ipairs(old_screen.tags) do
579            tag.setproperty(t2, "index", i)
580        end
581
582        -- Restore the old screen history if the tag was selected
583        if sel then
584            tag.history.restore(old_screen, 1)
585        end
586    end
587end
588
589--- Set a tag's screen
590-- @deprecated awful.tag.setscreen
591-- @see screen
592-- @param s Screen
593-- @param t tag object
594function tag.setscreen(s, t)
595    -- For API consistency, the arguments have been swapped for Awesome 3.6
596    -- this method is already deprecated, so be silent and swap the args
597    if type(t) == "number" then
598        s, t = t, s
599    end
600
601    gdebug.deprecate("Use t.screen = s instead of awful.tag.setscreen(t, s)", {deprecated_in=4})
602
603    tag.object.set_screen(t, s)
604end
605
606--- Get a tag's screen
607-- @deprecated awful.tag.getscreen
608-- @see screen
609-- @param[opt] t tag object
610-- @return Screen number
611function tag.getscreen(t)
612    gdebug.deprecate("Use t.screen instead of awful.tag.getscreen(t)", {deprecated_in=4})
613
614    -- A new getter is not required
615
616    t = t or ascreen.focused().selected_tag
617    local prop = tag.getproperty(t, "screen")
618    return prop and prop.index
619end
620
621--- Return a table with all visible tags
622-- @deprecated awful.tag.selectedlist
623-- @param s Screen.
624-- @return A table with all selected tags.
625-- @see screen.selected_tags
626function tag.selectedlist(s)
627    gdebug.deprecate("Use s.selected_tags instead of awful.tag.selectedlist", {deprecated_in=4})
628
629    s = get_screen(s or ascreen.focused())
630
631    return s.selected_tags
632end
633
634--- Return only the first visible tag.
635-- @deprecated awful.tag.selected
636-- @param s Screen.
637-- @see screen.selected_tag
638function tag.selected(s)
639    gdebug.deprecate("Use s.selected_tag instead of awful.tag.selected", {deprecated_in=4})
640
641    s = get_screen(s or ascreen.focused())
642
643    return s.selected_tag
644end
645
646--- The default master width factor
647--
648-- @beautiful beautiful.master_width_factor
649-- @param number (default: 0.5)
650-- @see master_width_factor
651-- @see gap
652
653--- The tag master width factor.
654--
655-- The master width factor is one of the 5 main properties used to configure
656-- the `layout`. Each layout interpret (or ignore) this property differenly.
657--
658-- See the layout suit documentation for information about how the master width
659-- factor is used.
660--
661-- **Signal:**
662--
663-- * *property::mwfact* (deprecated)
664-- * *property::master_width_factor*
665--
666-- @property master_width_factor
667-- @param number Between 0 and 1
668-- @see master_count
669-- @see column_count
670-- @see master_fill_policy
671-- @see gap
672
673function tag.object.set_master_width_factor(t, mwfact)
674    if mwfact >= 0 and mwfact <= 1 then
675        tag.setproperty(t, "mwfact", mwfact)
676        tag.setproperty(t, "master_width_factor", mwfact)
677    end
678end
679
680function tag.object.get_master_width_factor(t)
681    return tag.getproperty(t, "master_width_factor")
682        or beautiful.master_width_factor
683        or defaults.master_width_factor
684end
685
686--- Set master width factor.
687-- @deprecated awful.tag.setmwfact
688-- @see master_fill_policy
689-- @see master_width_factor
690-- @param mwfact Master width factor.
691-- @param t The tag to modify, if null tag.selected() is used.
692function tag.setmwfact(mwfact, t)
693    gdebug.deprecate("Use t.master_width_factor = mwfact instead of awful.tag.setmwfact", {deprecated_in=4})
694
695    tag.object.set_master_width_factor(t or ascreen.focused().selected_tag, mwfact)
696end
697
698--- Increase master width factor.
699-- @function awful.tag.incmwfact
700-- @see master_width_factor
701-- @param add Value to add to master width factor.
702-- @param t The tag to modify, if null tag.selected() is used.
703function tag.incmwfact(add, t)
704    t = t or t or ascreen.focused().selected_tag
705    tag.object.set_master_width_factor(t, tag.object.get_master_width_factor(t) + add)
706end
707
708--- Get master width factor.
709-- @deprecated awful.tag.getmwfact
710-- @see master_width_factor
711-- @see master_fill_policy
712-- @param[opt] t The tag.
713function tag.getmwfact(t)
714    gdebug.deprecate("Use t.master_width_factor instead of awful.tag.getmwfact", {deprecated_in=4})
715
716    return tag.object.get_master_width_factor(t or ascreen.focused().selected_tag)
717end
718
719--- An ordered list of layouts.
720-- `awful.tag.layout` Is usually defined in `rc.lua`. It store the list of
721-- layouts used when selecting the previous and next layouts. This is the
722-- default:
723--
724--     -- Table of layouts to cover with awful.layout.inc, order matters.
725--     awful.layout.layouts = {
726--         awful.layout.suit.floating,
727--         awful.layout.suit.tile,
728--         awful.layout.suit.tile.left,
729--         awful.layout.suit.tile.bottom,
730--         awful.layout.suit.tile.top,
731--         awful.layout.suit.fair,
732--         awful.layout.suit.fair.horizontal,
733--         awful.layout.suit.spiral,
734--         awful.layout.suit.spiral.dwindle,
735--         awful.layout.suit.max,
736--         awful.layout.suit.max.fullscreen,
737--         awful.layout.suit.magnifier,
738--         awful.layout.suit.corner.nw,
739--         -- awful.layout.suit.corner.ne,
740--         -- awful.layout.suit.corner.sw,
741--         -- awful.layout.suit.corner.se,
742--     }
743--
744-- @field awful.tag.layouts
745
746--- The tag client layout.
747--
748-- This property hold the layout. A layout can be either stateless or stateful.
749-- Stateless layouts are used by default by Awesome. They tile clients without
750-- any other overhead. They take an ordered list of clients and place them on
751-- the screen. Stateful layouts create an object instance for each tags and
752-- can store variables and metadata. Because of this, they are able to change
753-- over time and be serialized (saved).
754--
755-- Both types of layouts have valid usage scenarios.
756--
757-- **Stateless layouts:**
758--
759-- These layouts are stored in `awful.layout.suit`. They expose a table with 2
760-- fields:
761--
762-- * **name** (*string*): The layout name. This should be unique.
763-- * **arrange** (*function*): The function called when the clients need to be
764--     placed. The only parameter is a table or arguments returned by
765--     `awful.layout.parameters`
766--
767-- **Stateful layouts:**
768--
769-- The stateful layouts API is the same as stateless, but they are a function
770-- returining a layout instead of a layout itself. They also should have an
771-- `is_dynamic = true` property. If they don't, `awful.tag` will create a new
772-- instance everytime the layout is set. If they do, the instance will be
773-- cached and re-used.
774--
775-- **Signal:**
776--
777-- * *property::layout*
778--
779-- @property layout
780-- @see awful.tag.layouts
781-- @tparam layout|function layout A layout table or a constructor function
782-- @return The layout
783
784--- The (proposed) list of available layouts for this tag.
785--
786-- This property allows to define a subset (or superset) of layouts available
787-- in the "rotation table". In the default configuration file, `Mod4+Space`
788-- and `Mod4+Shift+Space` are used to switch between tags. The
789-- `awful.widget.layoutlist` also uses this as its default layout filter.
790--
791-- By default, it will be the same as `awful.layout.layouts` unless there the
792-- a layout not present is used. If that's the case they will be added at the
793-- front of the list.
794--
795-- @property layouts
796-- @param table
797-- @see awful.layout.layouts
798-- @see layout
799
800function tag.object.set_layout(t, layout)
801
802    local template = nil
803
804    -- Check if the signature match a stateful layout
805    if type(layout) == "function" or (
806        type(layout) == "table"
807        and getmetatable(layout)
808        and getmetatable(layout).__call
809    ) then
810        if not t.dynamic_layout_cache then
811            t.dynamic_layout_cache = {}
812        end
813
814        local instance = t.dynamic_layout_cache[layout] or layout(t)
815
816        -- Always make sure the layout is notified it is enabled
817        if tag.getproperty(t, "screen").selected_tag == t and instance.wake_up then
818            instance:wake_up()
819        end
820
821        -- Avoid creating the same layout twice, use layout:reset() to reset
822        if instance.is_dynamic then
823            t.dynamic_layout_cache[layout] = instance
824        end
825
826        template = layout
827        layout = instance
828    end
829
830    tag.setproperty(t, "layout", layout)
831
832    update_layouts(t, template or layout, layout)
833
834    return layout
835end
836
837function tag.object.get_layouts(self)
838    local override = tag.getproperty(self, "_layouts")
839
840    if override then
841        return override
842    end
843
844    -- Required to get the default/fallback list of layouts
845    alayout = alayout or require("awful.layout")
846
847    local cls = custom_layouts(self)
848
849    -- Without the clone, the custom_layouts would grow
850    return #cls > 0 and gtable.merge(gtable.clone(cls, false), alayout.layouts) or
851        alayout.layouts
852end
853
854function tag.object.set_layouts(self, layouts)
855    tag.setproperty(self, "_custom_layouts", {})
856    tag.setproperty(self, "_layouts", gtable.clone(layouts, false))
857
858    local cur = tag.getproperty(self, "layout")
859    update_layouts(self, cur, cur)
860
861    self:emit_signal("property::layouts")
862end
863
864function tag.object.get_layout(t)
865    local l = tag.getproperty(t, "layout")
866    if l then return l end
867
868    local layouts = tag.getproperty(t, "_layouts")
869
870    return layouts and layouts[1]
871        or require("awful.layout.suit.floating")
872end
873
874--- Set layout.
875-- @deprecated awful.tag.setlayout
876-- @see layout
877-- @param layout a layout table or a constructor function
878-- @param t The tag to modify
879-- @return The layout
880function tag.setlayout(layout, t)
881    gdebug.deprecate("Use t.layout = layout instead of awful.tag.setlayout", {deprecated_in=4})
882
883    return tag.object.set_layout(t, layout)
884end
885
886--- Define if the tag must be deleted when the last client is untagged.
887--
888-- This is useful to create "throw-away" tags for operation like 50/50
889-- side-by-side views.
890--
891--    local t = awful.tag.add("Temporary", {
892--         screen   = client.focus.screen,
893--         volatile = true,
894--         clients  = {
895--             client.focus,
896--             awful.client.focus.history.get(client.focus.screen, 1)
897--         }
898--    }
899--
900-- **Signal:**
901--
902-- * *property::volatile*
903--
904-- @property volatile
905-- @param boolean
906
907-- Volatile accessors are implicit
908
909--- Set if the tag must be deleted when the last client is untagged
910-- @deprecated awful.tag.setvolatile
911-- @see volatile
912-- @tparam boolean volatile If the tag must be deleted when the last client is untagged
913-- @param t The tag to modify, if null tag.selected() is used.
914function tag.setvolatile(volatile, t)
915    gdebug.deprecate("Use t.volatile = volatile instead of awful.tag.setvolatile", {deprecated_in=4})
916
917    tag.setproperty(t, "volatile", volatile)
918end
919
920--- Get if the tag must be deleted when the last client closes
921-- @deprecated awful.tag.getvolatile
922-- @see volatile
923-- @param t The tag to modify, if null tag.selected() is used.
924-- @treturn boolean If the tag will be deleted when the last client is untagged
925function tag.getvolatile(t)
926    gdebug.deprecate("Use t.volatile instead of awful.tag.getvolatile", {deprecated_in=4})
927
928    return tag.getproperty(t, "volatile") or false
929end
930
931--- The default gap.
932--
933-- @beautiful beautiful.useless_gap
934-- @param number (default: 0)
935-- @see gap
936-- @see gap_single_client
937
938--- The gap (spacing, also called `useless_gap`) between clients.
939--
940-- This property allow to waste space on the screen in the name of style,
941-- unicorns and readability.
942--
943-- **Signal:**
944--
945-- * *property::useless_gap*
946--
947-- @property gap
948-- @param number The value has to be greater than zero.
949-- @see gap_single_client
950
951function tag.object.set_gap(t, useless_gap)
952    if useless_gap >= 0 then
953        tag.setproperty(t, "useless_gap", useless_gap)
954    end
955end
956
957function tag.object.get_gap(t)
958    return tag.getproperty(t, "useless_gap")
959        or beautiful.useless_gap
960        or defaults.gap
961end
962
963--- Set the spacing between clients
964-- @deprecated awful.tag.setgap
965-- @see gap
966-- @param useless_gap The spacing between clients
967-- @param t The tag to modify, if null tag.selected() is used.
968function tag.setgap(useless_gap, t)
969    gdebug.deprecate("Use t.gap = useless_gap instead of awful.tag.setgap", {deprecated_in=4})
970
971    tag.object.set_gap(t or ascreen.focused().selected_tag, useless_gap)
972end
973
974--- Increase the spacing between clients
975-- @function awful.tag.incgap
976-- @see gap
977-- @param add Value to add to the spacing between clients
978-- @param t The tag to modify, if null tag.selected() is used.
979function tag.incgap(add, t)
980    t = t or t or ascreen.focused().selected_tag
981    tag.object.set_gap(t, tag.object.get_gap(t) + add)
982end
983
984--- Enable gaps for a single client.
985--
986-- @beautiful beautiful.gap_single_client
987-- @param boolean (default: true)
988-- @see gap
989-- @see gap_single_client
990
991--- Enable gaps for a single client.
992--
993-- **Signal:**
994--
995-- * *property::gap\_single\_client*
996--
997-- @property gap_single_client
998-- @param boolean Enable gaps for a single client
999
1000function tag.object.set_gap_single_client(t, gap_single_client)
1001    tag.setproperty(t, "gap_single_client", gap_single_client == true)
1002end
1003
1004function tag.object.get_gap_single_client(t)
1005    local val = tag.getproperty(t, "gap_single_client")
1006    if val ~= nil then
1007        return val
1008    end
1009    val = beautiful.gap_single_client
1010    if val ~= nil then
1011        return val
1012    end
1013    return defaults.gap_single_client
1014end
1015
1016--- Get the spacing between clients.
1017-- @deprecated awful.tag.getgap
1018-- @see gap
1019-- @tparam[opt=tag.selected()] tag t The tag.
1020-- @tparam[opt] int numclients Number of (tiled) clients.  Passing this will
1021--   return 0 for a single client.  You can override this function to change
1022--   this behavior.
1023function tag.getgap(t, numclients)
1024    gdebug.deprecate("Use t.gap instead of awful.tag.getgap", {deprecated_in=4})
1025
1026    if numclients == 1 then
1027        return 0
1028    end
1029
1030    return tag.object.get_gap(t or ascreen.focused().selected_tag)
1031end
1032
1033--- The default fill policy.
1034--
1035-- ** Possible values**:
1036--
1037-- * *expand*: Take all the space
1038-- * *master_width_factor*: Only take the ratio defined by the
1039--   `master_width_factor`
1040--
1041-- @beautiful beautiful.master_fill_policy
1042-- @param string (default: "expand")
1043-- @see master_fill_policy
1044
1045--- Set size fill policy for the master client(s).
1046--
1047-- ** Possible values**:
1048--
1049-- * *expand*: Take all the space
1050-- * *master_width_factor*: Only take the ratio defined by the
1051--   `master_width_factor`
1052--
1053-- **Signal:**
1054--
1055-- * *property::master_fill_policy*
1056--
1057-- @property master_fill_policy
1058-- @param string "expand" or "master_width_factor"
1059
1060function tag.object.get_master_fill_policy(t)
1061    return tag.getproperty(t, "master_fill_policy")
1062        or beautiful.master_fill_policy
1063        or defaults.master_fill_policy
1064end
1065
1066--- Set size fill policy for the master client(s)
1067-- @deprecated awful.tag.setmfpol
1068-- @see master_fill_policy
1069-- @tparam string policy Can be set to
1070-- "expand" (fill all the available workarea) or
1071-- "master_width_factor" (fill only an area inside the master width factor)
1072-- @tparam[opt=tag.selected()] tag t The tag to modify
1073function tag.setmfpol(policy, t)
1074    gdebug.deprecate("Use t.master_fill_policy = policy instead of awful.tag.setmfpol", {deprecated_in=4})
1075
1076    t = t or ascreen.focused().selected_tag
1077    tag.setproperty(t, "master_fill_policy", policy)
1078end
1079
1080--- Toggle size fill policy for the master client(s)
1081-- between "expand" and "master_width_factor".
1082-- @function awful.tag.togglemfpol
1083-- @see master_fill_policy
1084-- @tparam tag t The tag to modify, if null tag.selected() is used.
1085function tag.togglemfpol(t)
1086    t = t or ascreen.focused().selected_tag
1087
1088    if tag.getmfpol(t) == "expand" then
1089        tag.setproperty(t, "master_fill_policy", "master_width_factor")
1090    else
1091        tag.setproperty(t, "master_fill_policy", "expand")
1092    end
1093end
1094
1095--- Get size fill policy for the master client(s)
1096-- @deprecated awful.tag.getmfpol
1097-- @see master_fill_policy
1098-- @tparam[opt=tag.selected()] tag t The tag
1099-- @treturn string Possible values are
1100-- "expand" (fill all the available workarea, default one) or
1101-- "master_width_factor" (fill only an area inside the master width factor)
1102function tag.getmfpol(t)
1103    gdebug.deprecate("Use t.master_fill_policy instead of awful.tag.getmfpol", {deprecated_in=4})
1104
1105    t = t or ascreen.focused().selected_tag
1106    return tag.getproperty(t, "master_fill_policy")
1107        or beautiful.master_fill_policy
1108        or defaults.master_fill_policy
1109end
1110
1111--- The default number of master windows.
1112--
1113-- @beautiful beautiful.master_count
1114-- @param integer (default: 1)
1115-- @see master_count
1116
1117--- Set the number of master windows.
1118--
1119-- **Signal:**
1120--
1121-- * *property::nmaster* (deprecated)
1122-- * *property::master_count*
1123--
1124-- @property master_count
1125-- @param integer nmaster Only positive values are accepted
1126
1127function tag.object.set_master_count(t, nmaster)
1128    if nmaster >= 0 then
1129        tag.setproperty(t, "nmaster", nmaster)
1130        tag.setproperty(t, "master_count", nmaster)
1131    end
1132end
1133
1134function tag.object.get_master_count(t)
1135    return tag.getproperty(t, "master_count")
1136        or beautiful.master_count
1137        or defaults.master_count
1138end
1139
1140---
1141-- @deprecated awful.tag.setnmaster
1142-- @see master_count
1143-- @param nmaster The number of master windows.
1144-- @param[opt] t The tag.
1145function tag.setnmaster(nmaster, t)
1146    gdebug.deprecate("Use t.master_count = nmaster instead of awful.tag.setnmaster", {deprecated_in=4})
1147
1148    tag.object.set_master_count(t or ascreen.focused().selected_tag, nmaster)
1149end
1150
1151--- Get the number of master windows.
1152-- @deprecated awful.tag.getnmaster
1153-- @see master_count
1154-- @param[opt] t The tag.
1155function tag.getnmaster(t)
1156    gdebug.deprecate("Use t.master_count instead of awful.tag.setnmaster", {deprecated_in=4})
1157
1158    t = t or ascreen.focused().selected_tag
1159    return tag.getproperty(t, "master_count") or 1
1160end
1161
1162--- Increase the number of master windows.
1163-- @function awful.tag.incnmaster
1164-- @see master_count
1165-- @param add Value to add to number of master windows.
1166-- @param[opt] t The tag to modify, if null tag.selected() is used.
1167-- @tparam[opt=false] boolean sensible Limit nmaster based on the number of
1168--   visible tiled windows?
1169function tag.incnmaster(add, t, sensible)
1170    t = t or ascreen.focused().selected_tag
1171
1172    if sensible then
1173        local screen = get_screen(tag.getproperty(t, "screen"))
1174        local ntiled = #screen.tiled_clients
1175
1176        local nmaster = tag.object.get_master_count(t)
1177        if nmaster > ntiled then
1178            nmaster = ntiled
1179        end
1180
1181        local newnmaster = nmaster + add
1182        if newnmaster > ntiled then
1183            newnmaster = ntiled
1184        end
1185        tag.object.set_master_count(t, newnmaster)
1186    else
1187        tag.object.set_master_count(t, tag.object.get_master_count(t) + add)
1188    end
1189end
1190
1191--- Set the tag icon.
1192--
1193-- **Signal:**
1194--
1195-- * *property::icon*
1196--
1197-- @property icon
1198-- @tparam path|surface icon The icon
1199
1200-- accessors are implicit.
1201
1202--- Set the tag icon
1203-- @deprecated awful.tag.seticon
1204-- @see icon
1205-- @param icon the icon to set, either path or image object
1206-- @param _tag the tag
1207function tag.seticon(icon, _tag)
1208    gdebug.deprecate("Use t.icon = icon instead of awful.tag.seticon", {deprecated_in=4})
1209
1210    _tag = _tag or ascreen.focused().selected_tag
1211    tag.setproperty(_tag, "icon", icon)
1212end
1213
1214--- Get the tag icon
1215-- @deprecated awful.tag.geticon
1216-- @see icon
1217-- @param _tag the tag
1218function tag.geticon(_tag)
1219    gdebug.deprecate("Use t.icon instead of awful.tag.geticon", {deprecated_in=4})
1220
1221    _tag = _tag or ascreen.focused().selected_tag
1222    return tag.getproperty(_tag, "icon")
1223end
1224
1225--- The default number of columns.
1226--
1227-- @beautiful beautiful.column_count
1228-- @param integer (default: 1)
1229-- @see column_count
1230
1231--- Set the number of columns.
1232--
1233-- **Signal:**
1234--
1235-- * *property::ncol* (deprecated)
1236-- * *property::column_count*
1237--
1238-- @property column_count
1239-- @tparam integer ncol Has to be greater than 1
1240
1241function tag.object.set_column_count(t, ncol)
1242    if ncol >= 1 then
1243        tag.setproperty(t, "ncol", ncol)
1244        tag.setproperty(t, "column_count", ncol)
1245    end
1246end
1247
1248function tag.object.get_column_count(t)
1249    return tag.getproperty(t, "column_count")
1250        or beautiful.column_count
1251        or defaults.column_count
1252end
1253
1254--- Set number of column windows.
1255-- @deprecated awful.tag.setncol
1256-- @see column_count
1257-- @param ncol The number of column.
1258-- @param t The tag to modify, if null tag.selected() is used.
1259function tag.setncol(ncol, t)
1260    gdebug.deprecate("Use t.column_count = new_index instead of awful.tag.setncol", {deprecated_in=4})
1261
1262    t = t or ascreen.focused().selected_tag
1263    if ncol >= 1 then
1264        tag.setproperty(t, "ncol", ncol)
1265        tag.setproperty(t, "column_count", ncol)
1266    end
1267end
1268
1269--- Get number of column windows.
1270-- @deprecated awful.tag.getncol
1271-- @see column_count
1272-- @param[opt] t The tag.
1273function tag.getncol(t)
1274    gdebug.deprecate("Use t.column_count instead of awful.tag.getncol", {deprecated_in=4})
1275
1276    t = t or ascreen.focused().selected_tag
1277    return tag.getproperty(t, "column_count") or 1
1278end
1279
1280--- Increase number of column windows.
1281-- @function awful.tag.incncol
1282-- @param add Value to add to number of column windows.
1283-- @param[opt] t The tag to modify, if null tag.selected() is used.
1284-- @tparam[opt=false] boolean sensible Limit column_count based on the number
1285--   of visible tiled windows?
1286function tag.incncol(add, t, sensible)
1287    t = t or ascreen.focused().selected_tag
1288
1289    if sensible then
1290        local screen = get_screen(tag.getproperty(t, "screen"))
1291        local ntiled = #screen.tiled_clients
1292        local nmaster = tag.object.get_master_count(t)
1293        local nsecondary = ntiled - nmaster
1294
1295        local ncol = tag.object.get_column_count(t)
1296        if ncol > nsecondary then
1297            ncol = nsecondary
1298        end
1299
1300        local newncol = ncol + add
1301        if newncol > nsecondary then
1302            newncol = nsecondary
1303        end
1304
1305        tag.object.set_column_count(t, newncol)
1306    else
1307        tag.object.set_column_count(t, tag.object.get_column_count(t) + add)
1308    end
1309end
1310
1311--- View no tag.
1312-- @function awful.tag.viewnone
1313-- @tparam[opt] int|screen screen The screen.
1314function tag.viewnone(screen)
1315    screen = screen or ascreen.focused()
1316    local tags = screen.tags
1317    for _, t in pairs(tags) do
1318        t.selected = false
1319    end
1320end
1321
1322--- View a tag by its taglist index.
1323--
1324-- This is equivalent to `screen.tags[i]:view_only()`
1325-- @function awful.tag.viewidx
1326-- @see screen.tags
1327-- @param i The **relative** index to see.
1328-- @param[opt] screen The screen.
1329function tag.viewidx(i, screen)
1330    screen = get_screen(screen or ascreen.focused())
1331    local tags = screen.tags
1332    local showntags = {}
1333    for _, t in ipairs(tags) do
1334        if not tag.getproperty(t, "hide") then
1335            table.insert(showntags, t)
1336        end
1337    end
1338    local sel = screen.selected_tag
1339    tag.viewnone(screen)
1340    for k, t in ipairs(showntags) do
1341        if t == sel then
1342            showntags[gmath.cycle(#showntags, k + i)].selected = true
1343        end
1344    end
1345    screen:emit_signal("tag::history::update")
1346end
1347
1348--- Get a tag's index in the gettags() table.
1349-- @deprecated awful.tag.getidx
1350-- @see index
1351-- @param query_tag The tag object to find. [selected()]
1352-- @return The index of the tag, nil if the tag is not found.
1353function tag.getidx(query_tag)
1354    gdebug.deprecate("Use t.index instead of awful.tag.getidx", {deprecated_in=4})
1355
1356    return tag.object.get_index(query_tag or ascreen.focused().selected_tag)
1357end
1358
1359--- View next tag. This is the same as tag.viewidx(1).
1360-- @function awful.tag.viewnext
1361-- @param screen The screen.
1362function tag.viewnext(screen)
1363    return tag.viewidx(1, screen)
1364end
1365
1366--- View previous tag. This is the same a tag.viewidx(-1).
1367-- @function awful.tag.viewprev
1368-- @param screen The screen.
1369function tag.viewprev(screen)
1370    return tag.viewidx(-1, screen)
1371end
1372
1373--- View only a tag.
1374-- @function tag.view_only
1375-- @see selected
1376function tag.object.view_only(self)
1377    local tags = self.screen.tags
1378    -- First, untag everyone except the viewed tag.
1379    for _, _tag in pairs(tags) do
1380        if _tag ~= self then
1381            _tag.selected = false
1382        end
1383    end
1384    -- Then, set this one to selected.
1385    -- We need to do that in 2 operations so we avoid flickering and several tag
1386    -- selected at the same time.
1387    self.selected = true
1388    capi.screen[self.screen]:emit_signal("tag::history::update")
1389end
1390
1391--- View only a tag.
1392-- @deprecated awful.tag.viewonly
1393-- @see tag.view_only
1394-- @param t The tag object.
1395function tag.viewonly(t)
1396    gdebug.deprecate("Use t:view_only() instead of awful.tag.viewonly", {deprecated_in=4})
1397
1398    tag.object.view_only(t)
1399end
1400
1401--- View only a set of tags.
1402--
1403-- If `maximum` is set, there will be a limit on the number of new tag being
1404-- selected. The tags already selected do not count. To do nothing if one or
1405-- more of the tags are already selected, set `maximum` to zero.
1406--
1407-- @function awful.tag.viewmore
1408-- @param tags A table with tags to view only.
1409-- @param[opt] screen The screen of the tags.
1410-- @tparam[opt=#tags] number maximum The maximum number of tags to select.
1411function tag.viewmore(tags, screen, maximum)
1412    maximum = maximum or #tags
1413    local selected = 0
1414    screen = get_screen(screen or ascreen.focused())
1415    local screen_tags = screen.tags
1416    for _, _tag in ipairs(screen_tags) do
1417        if not gtable.hasitem(tags, _tag) then
1418            _tag.selected = false
1419        elseif _tag.selected then
1420            selected = selected + 1
1421        end
1422    end
1423    for _, _tag in ipairs(tags) do
1424        if selected == 0 and maximum == 0 then
1425            _tag.selected = true
1426            break
1427        end
1428
1429        if selected >= maximum then break end
1430
1431        if not _tag.selected then
1432            selected = selected + 1
1433            _tag.selected = true
1434        end
1435    end
1436    screen:emit_signal("tag::history::update")
1437end
1438
1439--- Toggle selection of a tag
1440-- @function awful.tag.viewtoggle
1441-- @see selected
1442-- @tparam tag t Tag to be toggled
1443function tag.viewtoggle(t)
1444    t.selected = not t.selected
1445    capi.screen[tag.getproperty(t, "screen")]:emit_signal("tag::history::update")
1446end
1447
1448--- Get tag data table.
1449--
1450-- Do not use.
1451--
1452-- @deprecated awful.tag.getdata
1453-- @tparam tag _tag The tag.
1454-- @return The data table.
1455function tag.getdata(_tag)
1456    return _tag.data.awful_tag_properties
1457end
1458
1459--- Get a tag property.
1460--
1461-- Use `_tag.prop` directly.
1462--
1463-- @deprecated awful.tag.getproperty
1464-- @tparam tag _tag The tag.
1465-- @tparam string prop The property name.
1466-- @return The property.
1467function tag.getproperty(_tag, prop)
1468    if not _tag then return end -- FIXME: Turn this into an error?
1469    if _tag.data.awful_tag_properties then
1470       return _tag.data.awful_tag_properties[prop]
1471    end
1472end
1473
1474--- Set a tag property.
1475-- This properties are internal to awful. Some are used to draw taglist, or to
1476-- handle layout, etc.
1477--
1478-- Use `_tag.prop = value`
1479--
1480-- @deprecated awful.tag.setproperty
1481-- @param _tag The tag.
1482-- @param prop The property name.
1483-- @param value The value.
1484function tag.setproperty(_tag, prop, value)
1485    if not _tag.data.awful_tag_properties then
1486        _tag.data.awful_tag_properties = {}
1487    end
1488
1489    if _tag.data.awful_tag_properties[prop] ~= value then
1490        _tag.data.awful_tag_properties[prop] = value
1491        _tag:emit_signal("property::" .. prop)
1492    end
1493end
1494
1495--- Tag a client with the set of current tags.
1496-- @deprecated awful.tag.withcurrent
1497-- @param c The client to tag.
1498function tag.withcurrent(c)
1499    gdebug.deprecate("Use c:to_selected_tags() instead of awful.tag.selectedlist", {deprecated_in=4})
1500
1501    -- It can't use c:to_selected_tags() because awful.tag is loaded before
1502    -- awful.client
1503
1504    local tags = {}
1505    for _, t in ipairs(c:tags()) do
1506        if get_screen(tag.getproperty(t, "screen")) == get_screen(c.screen) then
1507            table.insert(tags, t)
1508        end
1509    end
1510    if #tags == 0 then
1511        tags = c.screen.selected_tags
1512    end
1513    if #tags == 0 then
1514        tags = c.screen.tags
1515    end
1516    if #tags ~= 0 then
1517        c:tags(tags)
1518    end
1519end
1520
1521local function attached_connect_signal_screen(screen, sig, func)
1522    screen = get_screen(screen)
1523    capi.tag.connect_signal(sig, function(_tag)
1524        if get_screen(tag.getproperty(_tag, "screen")) == screen then
1525            func(_tag)
1526        end
1527    end)
1528end
1529
1530--- Add a signal to all attached tags and all tags that will be attached in the
1531-- future. When a tag is detached from the screen, its signal is removed.
1532--
1533-- @function awful.tag.attached_connect_signal
1534-- @screen screen The screen concerned, or all if nil.
1535-- @tparam[opt] string signal The signal name.
1536-- @tparam[opt] function Callback
1537function tag.attached_connect_signal(screen, ...)
1538    if screen then
1539        attached_connect_signal_screen(screen, ...)
1540    else
1541        capi.tag.connect_signal(...)
1542    end
1543end
1544
1545-- Register standard signals.
1546capi.client.connect_signal("property::screen", function(c)
1547    -- First, the delayed timer is necessary to avoid a race condition with
1548    -- awful.rules. It is also messing up the tags before the user have a chance
1549    -- to set them manually.
1550    timer.delayed_call(function()
1551        if not c.valid then
1552            return
1553        end
1554        local tags, new_tags = c:tags(), {}
1555
1556        for _, t in ipairs(tags) do
1557            if t.screen == c.screen then
1558                table.insert(new_tags, t)
1559            end
1560        end
1561
1562        if #new_tags == 0 then
1563            c:emit_signal("request::tag", nil, {reason="screen"})
1564        elseif #new_tags < #tags then
1565            c:tags(new_tags)
1566        end
1567    end)
1568end)
1569
1570-- Keep track of the number of urgent clients.
1571local function update_urgent(t, modif)
1572    local count = tag.getproperty(t, "urgent_count") or 0
1573    count = (count + modif) >= 0 and (count + modif) or 0
1574    tag.setproperty(t, "urgent"      , count > 0)
1575    tag.setproperty(t, "urgent_count", count    )
1576end
1577
1578-- Update the urgent counter when a client is tagged.
1579local function client_tagged(c, t)
1580    if c.urgent then
1581        update_urgent(t, 1)
1582    end
1583end
1584
1585-- Update the urgent counter when a client is untagged.
1586local function client_untagged(c, t)
1587    if c.urgent then
1588        update_urgent(t, -1)
1589    end
1590
1591    if #t:clients() == 0 and tag.getproperty(t, "volatile") then
1592        tag.object.delete(t)
1593    end
1594end
1595
1596-- Count the urgent clients.
1597local function urgent_callback(c)
1598    for _,t in ipairs(c:tags()) do
1599        update_urgent(t, c.urgent and 1 or -1)
1600    end
1601end
1602
1603capi.client.connect_signal("property::urgent", urgent_callback)
1604capi.client.connect_signal("untagged", client_untagged)
1605capi.client.connect_signal("tagged", client_tagged)
1606capi.tag.connect_signal("request::select", tag.object.view_only)
1607
1608--- True when a tagged client is urgent
1609-- @signal property::urgent
1610-- @see client.urgent
1611
1612--- The number of urgent tagged clients
1613-- @signal property::urgent_count
1614-- @see client.urgent
1615
1616--- Emitted when a screen is removed.
1617-- This can be used to salvage existing tags by moving them to a new
1618-- screen (or creating a virtual screen). By default, there is no
1619-- handler for this request. The tags will be deleted. To prevent
1620-- this, an handler for this request must simply set a new screen
1621-- for the tag.
1622-- @signal request::screen
1623
1624--- Emitted after `request::screen` if no new screen has been set.
1625-- The tag will be deleted, this is a last chance to move its clients
1626-- before they are sent to a fallback tag. Connect to `request::screen`
1627-- if you wish to salvage the tag.
1628-- @signal removal-pending
1629
1630capi.screen.connect_signal("tag::history::update", tag.history.update)
1631
1632-- Make sure the history is set early
1633timer.delayed_call(function()
1634    for s in capi.screen do
1635        s:emit_signal("tag::history::update")
1636    end
1637end)
1638
1639capi.screen.connect_signal("removed", function(s)
1640    -- First give other code a chance to move the tag to another screen
1641    for _, t in pairs(s.tags) do
1642        t:emit_signal("request::screen")
1643    end
1644    -- Everything that's left: Tell everyone that these tags go away (other code
1645    -- could e.g. save clients)
1646    for _, t in pairs(s.tags) do
1647        t:emit_signal("removal-pending")
1648    end
1649    -- Give other code yet another change to save clients
1650    for _, c in pairs(capi.client.get(s)) do
1651        c:emit_signal("request::tag", nil, { reason = "screen-removed" })
1652    end
1653    -- Then force all clients left to go somewhere random
1654    local fallback = nil
1655    for other_screen in capi.screen do
1656        if #other_screen.tags > 0 then
1657            fallback = other_screen.tags[1]
1658            break
1659        end
1660    end
1661    for _, t in pairs(s.tags) do
1662        t:delete(fallback, true)
1663    end
1664    -- If any tag survived until now, forcefully get rid of it
1665    for _, t in pairs(s.tags) do
1666        t.activated = false
1667
1668        if t.data.awful_tag_properties then
1669            t.data.awful_tag_properties.screen = nil
1670        end
1671    end
1672end)
1673
1674function tag.mt:__call(...)
1675    return tag.new(...)
1676end
1677
1678-- Extend the luaobject
1679-- `awful.tag.setproperty` currently handle calling the setter method itself
1680-- while `awful.tag.getproperty`.
1681object.properties(capi.tag, {
1682    getter_class    = tag.object,
1683    setter_class    = tag.object,
1684    getter_fallback = tag.getproperty,
1685    setter_fallback = tag.setproperty,
1686})
1687
1688return setmetatable(tag, tag.mt)
1689
1690-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
1691