mirror of
https://github.com/awesomeWM/awesome
synced 2024-11-17 07:47:41 +01:00
ec8db18289
Setting a tag's screen to what it already is shouldn't have any bad effects. However, this code messed up the tag order and selection status. Fix this by returning early if the tag already has the right screen. Signed-off-by: Uli Schlachter <psychon@znc.in>
617 lines
18 KiB
Lua
617 lines
18 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,
|
|
root = root
|
|
}
|
|
|
|
--- Useful functions for tag manipulation.
|
|
-- awful.tag
|
|
local tag = { mt = {} }
|
|
|
|
-- Private data
|
|
local data = {}
|
|
data.history = {}
|
|
data.tags = setmetatable({}, { __mode = 'k' })
|
|
|
|
-- History functions
|
|
tag.history = {}
|
|
tag.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.
|
|
-- @param target_tag The tag that should be moved. If null, the currently
|
|
-- selected tag is used.
|
|
function tag.move(new_index, target_tag)
|
|
local target_tag = target_tag or tag.selected()
|
|
local scr = tag.getscreen(target_tag)
|
|
local tmp_tags = tag.gettags(scr)
|
|
|
|
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)
|
|
|
|
for i, tmp_tag in ipairs(tmp_tags) do
|
|
tag.setscreen(tmp_tag, scr)
|
|
tag.setproperty(tmp_tag, "index", i)
|
|
end
|
|
end
|
|
|
|
--- Add a tag.
|
|
-- @param name The tag name, a string
|
|
-- @param props The tags properties, a table
|
|
-- @return The created tag
|
|
function tag.add(name, props)
|
|
local properties = props or {}
|
|
local newtag = capi.tag{ name = name, activated = true }
|
|
properties.screen = properties.screen or capi.mouse.screen
|
|
|
|
for k, v in pairs(properties) do
|
|
tag.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 tag.new(names, screen, layout)
|
|
local screen = screen or 1
|
|
local tags = {}
|
|
for id, name in ipairs(names) do
|
|
table.insert(tags, id, tag.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 invalids A table of tags we consider unacceptable. [selectedlist(scr)]
|
|
function tag.find_fallback(screen, invalids)
|
|
local scr = screen or capi.mouse.screen
|
|
local t = invalids or tag.selectedlist(scr)
|
|
|
|
for _, v in pairs(tag.gettags(scr)) 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 tag.delete(target_tag, fallback_tag)
|
|
-- abort if no tag is passed or currently selected
|
|
local target_tag = target_tag or tag.selected()
|
|
if target_tag == nil then return end
|
|
|
|
local target_scr = tag.getscreen(target_tag)
|
|
local ntags = #tag.gettags(target_scr)
|
|
|
|
-- 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 = 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
|
|
data.tags[target_tag].screen = nil
|
|
target_tag.activated = false
|
|
|
|
-- If no tags are visible, try and view one.
|
|
if tag.selected(target_scr) == nil and ntags > 0 then
|
|
tag.history.restore(nil, 1)
|
|
if tag.selected(target_scr) == nil then
|
|
tag.gettags(target_scr)[1].selected = true
|
|
end
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
--- Update the tag history.
|
|
-- @param obj Screen object.
|
|
function tag.history.update(obj)
|
|
local s = obj.index
|
|
local curtags = tag.selectedlist(s)
|
|
-- create history table
|
|
if not data.history[s] then
|
|
data.history[s] = {}
|
|
else
|
|
if data.history[s].current then
|
|
-- Check that the list is not identical
|
|
local identical = true
|
|
for idx, _tag in ipairs(data.history[s].current) do
|
|
if curtags[idx] ~= _tag then
|
|
identical = false
|
|
break
|
|
end
|
|
end
|
|
|
|
-- Do not update history the table are identical
|
|
if identical then return end
|
|
end
|
|
|
|
-- Limit history
|
|
if #data.history[s] >= tag.history.limit then
|
|
for i = tag.history.limit, #data.history[s] do
|
|
data.history[s][i] = nil
|
|
end
|
|
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(curtags, { __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 tag.history.restore(screen, idx)
|
|
local s = screen or capi.mouse.screen
|
|
local i = idx or "previous"
|
|
local sel = tag.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
|
|
tag.history.restore(s, i + 1)
|
|
return
|
|
end
|
|
-- deselect all tags
|
|
tag.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
|
|
|
|
--- Get a list of all tags on a screen
|
|
-- @param s Screen number
|
|
-- @return A table with all available tags
|
|
function tag.gettags(s)
|
|
local tags = {}
|
|
for i, t in ipairs(root.tags()) do
|
|
if tag.getscreen(t) == s then
|
|
table.insert(tags, t)
|
|
end
|
|
end
|
|
|
|
local without_index = 0
|
|
for _, t in ipairs(tags) do
|
|
if not tag.getproperty(t, "index") then
|
|
without_index = without_index + 1
|
|
end
|
|
end
|
|
if without_index > 0 then
|
|
for _, t in ipairs(tags) do
|
|
if not tag.getproperty(t, "index") then
|
|
tag.setproperty(t, "index", (#tags - without_index + 1))
|
|
without_index = without_index - 1
|
|
end
|
|
end
|
|
end
|
|
|
|
table.sort(tags, function(a, b) return tag.getproperty(a, "index") < tag.getproperty(b, "index") end)
|
|
return tags
|
|
end
|
|
|
|
--- Set a tag's screen
|
|
-- @param t tag object
|
|
-- @param s Screen number
|
|
function tag.setscreen(t, s)
|
|
local s = s or capi.mouse.screen
|
|
local old_screen = tag.getproperty(t, "screen")
|
|
if s == old_screen then return end
|
|
|
|
-- Keeping the old index make very little sense when changing screen
|
|
tag.setproperty(t, "index", nil)
|
|
|
|
-- Change the screen
|
|
tag.setproperty(t, "screen", s)
|
|
|
|
-- Make sure the client's screen matches its tags
|
|
for k,c in ipairs(t:clients()) do
|
|
c.screen = s --Move all clients
|
|
c:tags({t})
|
|
end
|
|
tag.history.restore(old_screen,1)
|
|
end
|
|
|
|
--- Get a tag's screen
|
|
-- @param t tag object
|
|
-- @return Screen number
|
|
function tag.getscreen(t)
|
|
return tag.getproperty(t, "screen")
|
|
end
|
|
|
|
--- Return a table with all visible tags
|
|
-- @param s Screen number.
|
|
-- @return A table with all selected tags.
|
|
function tag.selectedlist(s)
|
|
local screen = s or capi.mouse.screen
|
|
local tags = tag.gettags(screen)
|
|
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 tag.selected(s)
|
|
return tag.selectedlist(s)[1]
|
|
end
|
|
|
|
--- Set master width factor.
|
|
-- @param mwfact Master width factor.
|
|
-- @param t The tag to modify, if null tag.selected() is used.
|
|
function tag.setmwfact(mwfact, t)
|
|
local t = t or tag.selected()
|
|
if mwfact >= 0 and mwfact <= 1 then
|
|
tag.setproperty(t, "mwfact", mwfact)
|
|
end
|
|
end
|
|
|
|
--- Increase master width factor.
|
|
-- @param add Value to add to master width factor.
|
|
-- @param t The tag to modify, if null tag.selected() is used.
|
|
function tag.incmwfact(add, t)
|
|
tag.setmwfact(tag.getmwfact(t) + add, t)
|
|
end
|
|
|
|
--- Get master width factor.
|
|
-- @param t Optional tag.
|
|
function tag.getmwfact(t)
|
|
local t = t or tag.selected()
|
|
return tag.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 tag.setnmaster(nmaster, t)
|
|
local t = t or tag.selected()
|
|
if nmaster >= 0 then
|
|
tag.setproperty(t, "nmaster", nmaster)
|
|
end
|
|
end
|
|
|
|
--- Get the number of master windows.
|
|
-- @param t Optional tag.
|
|
function tag.getnmaster(t)
|
|
local t = t or tag.selected()
|
|
return tag.getproperty(t, "nmaster") or 1
|
|
end
|
|
|
|
--- Increase the number of master windows.
|
|
-- @param add Value to add to number of master windows.
|
|
-- @param t The tag to modify, if null tag.selected() is used.
|
|
function tag.incnmaster(add, t)
|
|
tag.setnmaster(tag.getnmaster(t) + add, t)
|
|
end
|
|
|
|
|
|
--- Set the tag icon
|
|
-- @param icon the icon to set, either path or image object
|
|
-- @param _tag the tag
|
|
function tag.seticon(icon, _tag)
|
|
local _tag = _tag or tag.selected()
|
|
tag.setproperty(_tag, "icon", icon)
|
|
end
|
|
|
|
--- Get the tag icon
|
|
-- @param _tag the tag
|
|
function tag.geticon(_tag)
|
|
local _tag = _tag or tag.selected()
|
|
return tag.getproperty(_tag, "icon")
|
|
end
|
|
|
|
--- Set number of column windows.
|
|
-- @param ncol The number of column.
|
|
-- @param t The tag to modify, if null tag.selected() is used.
|
|
function tag.setncol(ncol, t)
|
|
local t = t or tag.selected()
|
|
if ncol >= 1 then
|
|
tag.setproperty(t, "ncol", ncol)
|
|
end
|
|
end
|
|
|
|
--- Get number of column windows.
|
|
-- @param t Optional tag.
|
|
function tag.getncol(t)
|
|
local t = t or tag.selected()
|
|
return tag.getproperty(t, "ncol") or 1
|
|
end
|
|
|
|
--- Increase number of column windows.
|
|
-- @param add Value to add to number of column windows.
|
|
-- @param t The tag to modify, if null tag.selected() is used.
|
|
function tag.incncol(add, t)
|
|
tag.setncol(tag.getncol(t) + add, t)
|
|
end
|
|
|
|
--- View no tag.
|
|
-- @param Optional screen number.
|
|
function tag.viewnone(screen)
|
|
local tags = tag.gettags(screen or capi.mouse.screen)
|
|
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 tag.viewidx(i, screen)
|
|
local screen = screen or capi.mouse.screen
|
|
local tags = tag.gettags(screen)
|
|
local showntags = {}
|
|
for k, t in ipairs(tags) do
|
|
if not tag.getproperty(t, "hide") then
|
|
table.insert(showntags, t)
|
|
end
|
|
end
|
|
local sel = tag.selected(screen)
|
|
tag.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 gettags() table.
|
|
-- @param query_tag The tag object to find. [selected()]
|
|
-- @return The index of the tag, nil if the tag is not found.
|
|
function tag.getidx(query_tag)
|
|
local query_tag = query_tag or tag.selected()
|
|
if query_tag == nil then return end
|
|
|
|
for i, t in ipairs(tag.gettags(tag.getscreen(query_tag))) 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 tag.viewnext(screen)
|
|
return tag.viewidx(1, screen)
|
|
end
|
|
|
|
--- View previous tag. This is the same a tag.viewidx(-1).
|
|
-- @param screen The screen number.
|
|
function tag.viewprev(screen)
|
|
return tag.viewidx(-1, screen)
|
|
end
|
|
|
|
--- View only a tag.
|
|
-- @param t The tag object.
|
|
function tag.viewonly(t)
|
|
local tags = tag.gettags(tag.getscreen(t))
|
|
-- 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[tag.getscreen(t)]: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 tag.viewmore(tags, screen)
|
|
local screen = screen or capi.mouse.screen
|
|
local screen_tags = tag.gettags(screen)
|
|
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 tag.viewtoggle(t)
|
|
t.selected = not t.selected
|
|
capi.screen[tag.getscreen(t)]:emit_signal("tag::history::update")
|
|
end
|
|
|
|
--- Get tag data table.
|
|
-- @param tag The Tag.
|
|
-- @return The data table.
|
|
function tag.getdata(_tag)
|
|
return data.tags[_tag]
|
|
end
|
|
|
|
--- Get a tag property.
|
|
-- @param _tag The tag.
|
|
-- @param prop The property name.
|
|
-- @return The property.
|
|
function tag.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 tag.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.
|
|
function tag.withcurrent(c)
|
|
local tags = {}
|
|
for k, t in ipairs(c:tags()) do
|
|
if tag.getscreen(t) == c.screen then
|
|
table.insert(tags, t)
|
|
end
|
|
end
|
|
if #tags == 0 then
|
|
tags = tag.selectedlist(c.screen)
|
|
end
|
|
if #tags == 0 then
|
|
tags = tag.gettags(c.screen)
|
|
end
|
|
if #tags ~= 0 then
|
|
c:tags(tags)
|
|
end
|
|
end
|
|
|
|
local function attached_connect_signal_screen(screen, sig, func)
|
|
capi.tag.connect_signal(sig, function(_tag, ...)
|
|
if tag.getscreen(_tag) == screen then
|
|
func(_tag)
|
|
end
|
|
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 tag.attached_connect_signal(screen, ...)
|
|
if screen then
|
|
attached_connect_signal_screen(screen, ...)
|
|
else
|
|
capi.tag.connect_signal(...)
|
|
end
|
|
end
|
|
|
|
-- Register standard signals.
|
|
capi.client.connect_signal("manage", function(c)
|
|
-- 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 awesome.startup and c.type ~= "desktop" and c.type ~= "dock" 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:connect_signal("property::screen", tag.withcurrent)
|
|
end)
|
|
|
|
capi.client.connect_signal("manage", tag.withcurrent)
|
|
capi.tag.connect_signal("request::select", tag.viewonly)
|
|
|
|
capi.tag.add_signal("property::hide")
|
|
capi.tag.add_signal("property::icon")
|
|
capi.tag.add_signal("property::layout")
|
|
capi.tag.add_signal("property::mwfact")
|
|
capi.tag.add_signal("property::ncol")
|
|
capi.tag.add_signal("property::nmaster")
|
|
capi.tag.add_signal("property::windowfact")
|
|
capi.tag.add_signal("property::screen")
|
|
capi.tag.add_signal("property::index")
|
|
|
|
capi.screen.add_signal("tag::history::update")
|
|
for s = 1, capi.screen.count() do
|
|
capi.screen[s]:connect_signal("tag::history::update", tag.history.update)
|
|
end
|
|
|
|
function tag.mt:__call(...)
|
|
return tag.new(...)
|
|
end
|
|
|
|
return setmetatable(tag, tag.mt)
|
|
|
|
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
|