511 lines
15 KiB
Lua
511 lines
15 KiB
Lua
---------------------------------------------------------------------------
|
|
-- @author Julien Danjou <julien@danjou.info>
|
|
-- @copyright 2008 Julien Danjou
|
|
-- @release @AWESOME_VERSION@
|
|
---------------------------------------------------------------------------
|
|
|
|
-- Grab environment we need
|
|
local util = require("awful.util")
|
|
local tostring = tostring
|
|
local pairs = pairs
|
|
local ipairs = ipairs
|
|
local table = table
|
|
local setmetatable = setmetatable
|
|
local capi =
|
|
{
|
|
tag = tag,
|
|
screen = screen,
|
|
mouse = mouse,
|
|
client = client
|
|
}
|
|
|
|
--- Useful functions for tag manipulation.
|
|
module("awful.tag")
|
|
|
|
-- Private data
|
|
local data = {}
|
|
data.history = {}
|
|
data.tags = setmetatable({}, { __mode = 'k' })
|
|
|
|
-- History functions
|
|
history = {}
|
|
history.limit = 20
|
|
|
|
--- Move a tag to an absolute position in the screen[]:tags() table.
|
|
-- @param new_index Integer absolute position in the table to insert.
|
|
function move(new_index, target_tag)
|
|
local target_tag = target_tag or selected()
|
|
local scr = target_tag.screen
|
|
local tmp_tags = capi.screen[scr]:tags()
|
|
|
|
if (not new_index) or (new_index < 1) or (new_index > #tmp_tags) then
|
|
return
|
|
end
|
|
|
|
for i, t in ipairs(tmp_tags) do
|
|
if t == target_tag then
|
|
table.remove(tmp_tags, i)
|
|
break
|
|
end
|
|
end
|
|
|
|
table.insert(tmp_tags, new_index, target_tag)
|
|
capi.screen[scr]:tags(tmp_tags)
|
|
end
|
|
|
|
--- Add a tag.
|
|
-- @param name The tag name, a string
|
|
-- @param props The tags properties, a table
|
|
-- @return The created tag
|
|
function add(name, props)
|
|
local properties = props or {}
|
|
local newtag = capi.tag{name = name}
|
|
newtag.screen = properties.screen or capi.mouse.screen
|
|
|
|
for k, v in pairs(properties) do
|
|
setproperty(newtag, k, v)
|
|
end
|
|
|
|
return newtag
|
|
end
|
|
|
|
--- Create a set of tags and attach it to a screen.
|
|
-- @param names The tag name, in a table
|
|
-- @param screen The tag screen, or 1 if not set.
|
|
-- @param layout The layout or layout table to set for this tags by default.
|
|
-- @return A table with all created tags.
|
|
function new(names, screen, layout)
|
|
local screen = screen or 1
|
|
local tags = {}
|
|
for id, name in ipairs(names) do
|
|
table.insert(tags, id, add(name, {screen = screen,
|
|
layout = (layout and layout[id]) or
|
|
layout}))
|
|
-- Select the first tag.
|
|
if id == 1 then
|
|
tags[id].selected = true
|
|
end
|
|
end
|
|
|
|
return tags
|
|
end
|
|
|
|
--- Find a suitable fallback tag.
|
|
-- @param screen The screen number to look for a tag on. [mouse.screen]
|
|
-- @param target A table of tags we consider unacceptable. [selectedlist(scr)]
|
|
function find_fallback(screen, invalids)
|
|
local scr = screen or capi.mouse.screen
|
|
local t = invalids or selectedlist(scr)
|
|
|
|
for _, v in pairs(capi.screen[scr]:tags()) do
|
|
if not util.table.hasitem(t, v) then return v end
|
|
end
|
|
end
|
|
|
|
--- Delete a tag.
|
|
-- @param target_tag Optional tag object to delete. [selected()]
|
|
-- @param fallback_tag Tag to assign stickied tags to. [~selected()]
|
|
-- @return Returns true if the tag is successfully deleted, nil otherwise.
|
|
-- If there are no clients exclusively on this tag then delete it. Any
|
|
-- stickied clients are assigned to the optional 'fallback_tag'.
|
|
-- If after deleting the tag there is no selected tag, try and restore from
|
|
-- history or select the first tag on the screen.
|
|
function delete(target_tag, fallback_tag)
|
|
-- abort if no tag is passed or currently selected
|
|
local target_tag = target_tag or selected()
|
|
if target_tag == nil then return end
|
|
|
|
local ntags = #capi.screen[target_tag.screen]:tags()
|
|
local target_scr = target_tag.screen
|
|
|
|
-- We can't use the target tag as a fallback.
|
|
local fallback_tag = fallback_tag
|
|
if fallback_tag == target_tag then return end
|
|
|
|
-- No fallback_tag provided, try and get one.
|
|
if fallback_tag == nil then
|
|
fallback_tag = find_fallback(target_scr, {target_tag})
|
|
end
|
|
|
|
-- Abort if we would have un-tagged clients.
|
|
local clients = target_tag:clients()
|
|
if ( #clients > 0 and ntags <= 1 ) or fallback_tag == nil then return end
|
|
|
|
-- Move the clients we can off of this tag.
|
|
for _, c in pairs(clients) do
|
|
|
|
-- If a client has only this tag, or stickied clients with
|
|
-- nowhere to go, abort.
|
|
if (not c.sticky and #c:tags() == 1) or
|
|
(c.sticky and fallback_tag == nil) then
|
|
return
|
|
else
|
|
c:tags({fallback_tag})
|
|
end
|
|
end
|
|
|
|
-- delete the tag
|
|
target_tag.screen = nil
|
|
|
|
-- If no tags are visible, try and view one.
|
|
if selected(target_scr) == nil and ntags > 0 then
|
|
history.restore()
|
|
if selected(target_scr) == nil then
|
|
capi.screen[target_scr]:tags()[1].selected = true
|
|
end
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
--- Update the tag history.
|
|
-- @param obj Screen object.
|
|
function history.update(obj)
|
|
local s = obj.index
|
|
local curtags = capi.screen[s]:tags()
|
|
-- create history table
|
|
if not data.history[s] then
|
|
data.history[s] = {}
|
|
-- limit history
|
|
elseif #data.history[s] >= history.limit then
|
|
for i = history.limit, #data.history[s] do
|
|
data.history[s][i] = nil
|
|
end
|
|
end
|
|
-- store previously selected tags in the history table
|
|
table.insert(data.history[s], 1, data.history[s].current)
|
|
data.history[s].previous = data.history[s][1]
|
|
-- store currently selected tags
|
|
data.history[s].current = setmetatable(selectedlist(s), { __mode = 'v' })
|
|
end
|
|
|
|
--- Revert tag history.
|
|
-- @param screen The screen number.
|
|
-- @param idx Index in history. Defaults to "previous" which is a special index
|
|
-- toggling between last two selected sets of tags. Number (eg 1) will go back
|
|
-- to the given index in history.
|
|
function history.restore(screen, idx)
|
|
local s = screen or capi.mouse.screen
|
|
local i = idx or "previous"
|
|
local sel = selectedlist(s)
|
|
-- do nothing if history empty
|
|
if not data.history[s] or not data.history[s][i] then return end
|
|
-- if all tags been deleted, try next entry
|
|
if #data.history[s][i] == 0 then
|
|
if i == "previous" then i = 0 end
|
|
history.restore(s, i + 1)
|
|
return
|
|
end
|
|
-- deselect all tags
|
|
viewnone(s)
|
|
-- select tags from the history entry
|
|
for _, t in ipairs(data.history[s][i]) do
|
|
t.selected = true
|
|
end
|
|
-- update currently selected tags table
|
|
data.history[s].current = data.history[s][i]
|
|
-- store previously selected tags
|
|
data.history[s].previous = setmetatable(sel, { __mode = 'v' })
|
|
-- remove the reverted history entry
|
|
if i ~= "previous" then table.remove(data.history[s], i) end
|
|
end
|
|
|
|
--- Return a table with all visible tags
|
|
-- @param s Screen number.
|
|
-- @return A table with all selected tags.
|
|
function selectedlist(s)
|
|
local screen = s or capi.mouse.screen
|
|
local tags = capi.screen[screen]:tags()
|
|
local vtags = {}
|
|
for i, t in pairs(tags) do
|
|
if t.selected then
|
|
vtags[#vtags + 1] = t
|
|
end
|
|
end
|
|
return vtags
|
|
end
|
|
|
|
--- Return only the first visible tag.
|
|
-- @param s Screen number.
|
|
function selected(s)
|
|
return selectedlist(s)[1]
|
|
end
|
|
|
|
--- Set master width factor.
|
|
-- @param mwfact Master width factor.
|
|
function setmwfact(mwfact, t)
|
|
local t = t or selected()
|
|
if mwfact >= 0 and mwfact <= 1 then
|
|
setproperty(t, "mwfact", mwfact)
|
|
end
|
|
end
|
|
|
|
--- Increase master width factor.
|
|
-- @param add Value to add to master width factor.
|
|
function incmwfact(add, t)
|
|
setmwfact(getmwfact(t) + add)
|
|
end
|
|
|
|
--- Get master width factor.
|
|
-- @param t Optional tag.
|
|
function getmwfact(t)
|
|
local t = t or selected()
|
|
return getproperty(t, "mwfact") or 0.5
|
|
end
|
|
|
|
--- Set the number of master windows.
|
|
-- @param nmaster The number of master windows.
|
|
-- @param t Optional tag.
|
|
function setnmaster(nmaster, t)
|
|
local t = t or selected()
|
|
if nmaster >= 0 then
|
|
setproperty(t, "nmaster", nmaster)
|
|
end
|
|
end
|
|
|
|
--- Get the number of master windows.
|
|
-- @param t Optional tag.
|
|
function getnmaster(t)
|
|
local t = t or selected()
|
|
return getproperty(t, "nmaster") or 1
|
|
end
|
|
|
|
--- Increase the number of master windows.
|
|
-- @param add Value to add to number of master windows.
|
|
function incnmaster(add, t)
|
|
setnmaster(getnmaster(t) + add)
|
|
end
|
|
|
|
|
|
--- Set the tag icon
|
|
-- @param icon the icon to set, either path or image object
|
|
-- @param tag the tag
|
|
function seticon(icon, tag)
|
|
local tag = tag or selected()
|
|
setproperty(tag, "icon", icon)
|
|
end
|
|
|
|
--- Get the tag icon
|
|
-- @param t the tag
|
|
function geticon(tag)
|
|
local tag = tag or selected()
|
|
return getproperty(tag, "icon")
|
|
end
|
|
|
|
--- Set number of column windows.
|
|
-- @param ncol The number of column.
|
|
function setncol(ncol, t)
|
|
local t = t or selected()
|
|
if ncol >= 1 then
|
|
setproperty(t, "ncol", ncol)
|
|
end
|
|
end
|
|
|
|
--- Get number of column windows.
|
|
-- @param t Optional tag.
|
|
function getncol(t)
|
|
local t = t or selected()
|
|
return getproperty(t, "ncol") or 1
|
|
end
|
|
|
|
--- Increase number of column windows.
|
|
-- @param add Value to add to number of column windows.
|
|
function incncol(add, t)
|
|
setncol(getncol(t) + add)
|
|
end
|
|
|
|
--- View no tag.
|
|
-- @param Optional screen number.
|
|
function viewnone(screen)
|
|
local tags = capi.screen[screen or capi.mouse.screen]:tags()
|
|
for i, t in pairs(tags) do
|
|
t.selected = false
|
|
end
|
|
end
|
|
|
|
--- View a tag by its taglist index.
|
|
-- @param i The relative index to see.
|
|
-- @param screen Optional screen number.
|
|
function viewidx(i, screen)
|
|
local screen = screen and screen.index or capi.mouse.screen
|
|
local tags = capi.screen[screen]:tags()
|
|
local showntags = {}
|
|
for k, t in ipairs(tags) do
|
|
if not getproperty(t, "hide") then
|
|
table.insert(showntags, t)
|
|
end
|
|
end
|
|
local sel = selected(screen)
|
|
viewnone(screen)
|
|
for k, t in ipairs(showntags) do
|
|
if t == sel then
|
|
showntags[util.cycle(#showntags, k + i)].selected = true
|
|
end
|
|
end
|
|
capi.screen[screen]:emit_signal("tag::history::update")
|
|
end
|
|
|
|
--- Get a tag's index in the screen[]:tags() table.
|
|
-- @param query_tag The tag object to find. [selected()]
|
|
-- @return The index of the tag, nil if the tag is not found.
|
|
function getidx(query_tag)
|
|
local query_tag = query_tag or selected()
|
|
if query_tag == nil then return end
|
|
|
|
for i, t in ipairs(capi.screen[query_tag.screen]:tags()) do
|
|
if t == query_tag then
|
|
return i
|
|
end
|
|
end
|
|
end
|
|
|
|
--- View next tag. This is the same as tag.viewidx(1).
|
|
-- @param screen The screen number.
|
|
function viewnext(screen)
|
|
return viewidx(1, screen)
|
|
end
|
|
|
|
--- View previous tag. This is the same a tag.viewidx(-1).
|
|
-- @param screen The screen number.
|
|
function viewprev(screen)
|
|
return viewidx(-1, screen)
|
|
end
|
|
|
|
--- View only a tag.
|
|
-- @param t The tag object.
|
|
function viewonly(t)
|
|
local tags = capi.screen[t.screen]:tags()
|
|
-- First, untag everyone except the viewed tag.
|
|
for _, tag in pairs(tags) do
|
|
if tag ~= t then
|
|
tag.selected = false
|
|
end
|
|
end
|
|
-- Then, set this one to selected.
|
|
-- We need to do that in 2 operations so we avoid flickering and several tag
|
|
-- selected at the same time.
|
|
t.selected = true
|
|
capi.screen[t.screen]:emit_signal("tag::history::update")
|
|
end
|
|
|
|
--- View only a set of tags.
|
|
-- @param tags A table with tags to view only.
|
|
-- @param screen Optional screen number of the tags.
|
|
function viewmore(tags, screen)
|
|
local screen_tags = capi.screen[screen or capi.mouse.screen]:tags()
|
|
for _, tag in ipairs(screen_tags) do
|
|
if not util.table.hasitem(tags, tag) then
|
|
tag.selected = false
|
|
end
|
|
end
|
|
for _, tag in ipairs(tags) do
|
|
tag.selected = true
|
|
end
|
|
capi.screen[screen]:emit_signal("tag::history::update")
|
|
end
|
|
|
|
--- Toggle selection of a tag
|
|
-- @param tag Tag to be toggled
|
|
function viewtoggle(t)
|
|
t.selected = not t.selected
|
|
capi.screen[t.screen]:emit_signal("tag::history::update")
|
|
end
|
|
|
|
--- Get tag data table.
|
|
-- @param tag The Tag.
|
|
-- @return The data table.
|
|
function getdata(tag)
|
|
return data.tags[tag]
|
|
end
|
|
|
|
--- Get a tag property.
|
|
-- @param tag The tag.
|
|
-- @param prop The property name.
|
|
-- @return The property.
|
|
function getproperty(tag, prop)
|
|
if data.tags[tag] then
|
|
return data.tags[tag][prop]
|
|
end
|
|
end
|
|
|
|
--- Set a tag property.
|
|
-- This properties are internal to awful. Some are used to draw taglist, or to
|
|
-- handle layout, etc.
|
|
-- @param tag The tag.
|
|
-- @param prop The property name.
|
|
-- @param value The value.
|
|
function setproperty(tag, prop, value)
|
|
if not data.tags[tag] then
|
|
data.tags[tag] = {}
|
|
end
|
|
data.tags[tag][prop] = value
|
|
tag:emit_signal("property::" .. prop)
|
|
end
|
|
|
|
--- Tag a client with the set of current tags.
|
|
-- @param c The client to tag.
|
|
-- @param startup Optional: don't do anything if true.
|
|
function withcurrent(c, startup)
|
|
if startup ~= true and c.sticky == false then
|
|
if #c:tags() == 0 then
|
|
c:tags(selectedlist(c.screen))
|
|
end
|
|
end
|
|
end
|
|
|
|
local function attached_add_signal_screen(screen, sig, func)
|
|
capi.screen[screen]:add_signal("tag::attach", function (s, tag)
|
|
tag:add_signal(sig, func)
|
|
end)
|
|
capi.screen[screen]:add_signal("tag::detach", function (s, tag)
|
|
tag:remove_signal(sig, func)
|
|
end)
|
|
for _, tag in ipairs(capi.screen[screen]:tags()) do
|
|
tag:add_signal(sig, func)
|
|
end
|
|
end
|
|
|
|
--- Add a signal to all attached tag and all tag that will be attached in the
|
|
-- future. When a tag is detach from the screen, its signal is removed.
|
|
-- @param screen The screen concerned, or all if nil.
|
|
function attached_add_signal(screen, ...)
|
|
if screen then
|
|
attached_add_signal_screen(screen, ...)
|
|
else
|
|
for screen = 1, capi.screen.count() do
|
|
attached_add_signal_screen(screen, ...)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Register standards signals
|
|
capi.client.add_signal("manage", function(c, startup)
|
|
-- If we are not managing this application at startup,
|
|
-- move it to the screen where the mouse is.
|
|
-- We only do it for "normal" windows (i.e. no dock, etc).
|
|
if not startup
|
|
and c.type ~= "desktop"
|
|
and c.type ~= "dock"
|
|
and c.type ~= "splash" then
|
|
if c.transient_for then
|
|
c.screen = c.transient_for.screen
|
|
if not c.sticky then
|
|
c:tags(c.transient_for:tags())
|
|
end
|
|
else
|
|
c.screen = capi.mouse.screen
|
|
end
|
|
end
|
|
c:add_signal("property::screen", withcurrent)
|
|
end)
|
|
|
|
capi.client.add_signal("manage", withcurrent)
|
|
|
|
for s = 1, capi.screen.count() do
|
|
capi.screen[s]:add_signal("tag::history::update", history.update)
|
|
end
|
|
|
|
setmetatable(_M, { __call = function (_, ...) return new(...) end })
|
|
|
|
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80
|