mirror of
https://github.com/awesomeWM/awesome
synced 2024-11-17 07:47:41 +01:00
819daca18c
The only caller of focus.history.add() should be the "focus" signal, so at that time the unfocusable client already does have the focus. At that point, there is no reason why this shouldn't be recorded in the focus history. (And yes, unfocusable clients can be focused. Don't ask.) This should hopefully fix FS#778. Signed-off-by: Uli Schlachter <psychon@znc.in>
944 lines
26 KiB
Lua
944 lines
26 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 tag = require("awful.tag")
|
|
local pairs = pairs
|
|
local type = type
|
|
local ipairs = ipairs
|
|
local table = table
|
|
local math = math
|
|
local setmetatable = setmetatable
|
|
local capi =
|
|
{
|
|
client = client,
|
|
mouse = mouse,
|
|
screen = screen,
|
|
}
|
|
|
|
--- Useful client manipulation functions.
|
|
module("awful.client")
|
|
|
|
-- Private data
|
|
data = {}
|
|
data.focus = {}
|
|
data.urgent = {}
|
|
data.marked = {}
|
|
data.properties = setmetatable({}, { __mode = 'k' })
|
|
|
|
-- Functions
|
|
urgent = {}
|
|
focus = {}
|
|
focus.history = {}
|
|
swap = {}
|
|
floating = {}
|
|
dockable = {}
|
|
property = {}
|
|
|
|
---
|
|
-- Jump to the given client. Takes care of focussing the screen, the right tag,
|
|
-- etc.
|
|
-- @param c the client to jump to
|
|
-- @param merge If true then merge tags when clients are not visible.
|
|
function jumpto(c, merge)
|
|
local s = capi.client.focus and capi.client.focus.screen or capi.mouse.screen
|
|
-- focus the screen
|
|
if s ~= c.screen then
|
|
capi.mouse.screen = c.screen
|
|
end
|
|
|
|
-- Try to make client visible, this also covers e.g. sticky
|
|
local t = c:tags()[1]
|
|
if t and not c:isvisible() then
|
|
if merge then
|
|
t.selected = true
|
|
else
|
|
tag.viewonly(t)
|
|
end
|
|
end
|
|
|
|
-- focus the client
|
|
capi.client.focus = c
|
|
c:raise()
|
|
end
|
|
|
|
--- Get the first client that got the urgent hint.
|
|
-- @return The first urgent client.
|
|
function urgent.get()
|
|
if #data.urgent > 0 then
|
|
return data.urgent[1]
|
|
else
|
|
-- fallback behaviour: iterate through clients and get the first urgent
|
|
local clients = capi.client.get()
|
|
for k, cl in pairs(clients) do
|
|
if cl.urgent then
|
|
return cl
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Jump to the client that received the urgent hint first.
|
|
-- @param merge If true then merge tags when clients are not visible.
|
|
function urgent.jumpto(merge)
|
|
local c = urgent.get()
|
|
if c then
|
|
jumpto(c, merge)
|
|
end
|
|
end
|
|
|
|
--- Adds client to urgent stack.
|
|
-- @param c The client object.
|
|
-- @param prop The property which is updated.
|
|
function urgent.add(c, prop)
|
|
if type(c) == "client" and prop == "urgent" and c.urgent then
|
|
table.insert(data.urgent, c)
|
|
end
|
|
end
|
|
|
|
--- Remove client from urgent stack.
|
|
-- @param c The client object.
|
|
function urgent.delete(c)
|
|
for k, cl in ipairs(data.urgent) do
|
|
if c == cl then
|
|
table.remove(data.urgent, k)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Remove a client from the focus history
|
|
-- @param c The client that must be removed.
|
|
function focus.history.delete(c)
|
|
for k, v in ipairs(data.focus) do
|
|
if v == c then
|
|
table.remove(data.focus, k)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Filter out window that we do not want handled by focus.
|
|
-- This usually means that desktop, dock and splash windows are
|
|
-- not registered and cannot get focus.
|
|
-- @param c A client.
|
|
-- @return The same client if it's ok, nil otherwise.
|
|
function focus.filter(c)
|
|
if c.type == "desktop"
|
|
or c.type == "dock"
|
|
or c.type == "splash"
|
|
or not c.focusable then
|
|
return nil
|
|
end
|
|
return c
|
|
end
|
|
|
|
--- Update client focus history.
|
|
-- @param c The client that has been focused.
|
|
function focus.history.add(c)
|
|
-- Remove the client if its in stack
|
|
focus.history.delete(c)
|
|
-- Record the client has latest focused
|
|
table.insert(data.focus, 1, c)
|
|
end
|
|
|
|
--- Get the latest focused client for a screen in history.
|
|
-- @param screen The screen number to look for.
|
|
-- @param idx The index: 0 will return first candidate,
|
|
-- 1 will return second, etc.
|
|
-- @return A client.
|
|
function focus.history.get(screen, idx)
|
|
-- When this counter is equal to idx, we return the client
|
|
local counter = 0
|
|
local vc = visible(screen)
|
|
for k, c in ipairs(data.focus) do
|
|
if c.screen == screen then
|
|
for j, vcc in ipairs(vc) do
|
|
if vcc == c then
|
|
if counter == idx then
|
|
return c
|
|
end
|
|
-- We found one, increment the counter only.
|
|
counter = counter + 1
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
-- Argh nobody found in history, give the first one visible if there is one
|
|
-- that passes the filter.
|
|
if counter == 0 then
|
|
for k, v in ipairs(vc) do
|
|
if focus.filter(v) then
|
|
return v
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Focus the previous client in history.
|
|
function focus.history.previous()
|
|
local sel = capi.client.focus
|
|
local s
|
|
if sel then
|
|
s = sel.screen
|
|
else
|
|
s = capi.mouse.screen
|
|
end
|
|
local c = focus.history.get(s, 1)
|
|
if c then capi.client.focus = c end
|
|
end
|
|
|
|
--- Get visible clients from a screen.
|
|
-- @param screen The screen number, or nil for all screens.
|
|
-- @return A table with all visible clients.
|
|
function visible(screen)
|
|
local cls = capi.client.get(screen)
|
|
local vcls = {}
|
|
for k, c in pairs(cls) do
|
|
if c:isvisible() then
|
|
table.insert(vcls, c)
|
|
end
|
|
end
|
|
return vcls
|
|
end
|
|
|
|
--- Get visible and tiled clients
|
|
-- @param screen The screen number, or nil for all screens.
|
|
-- @return A tabl with all visible and tiled clients.
|
|
function tiled(screen)
|
|
local clients = visible(screen)
|
|
local tclients = {}
|
|
-- Remove floating clients
|
|
for k, c in pairs(clients) do
|
|
if not floating.get(c) then
|
|
table.insert(tclients, c)
|
|
end
|
|
end
|
|
return tclients
|
|
end
|
|
|
|
--- Get a client by its relative index to the focused window.
|
|
-- @usage Set i to 1 to get next, -1 to get previous.
|
|
-- @param i The index.
|
|
-- @param c Optional client.
|
|
-- @return A client, or nil if no client is available.
|
|
function next(i, c)
|
|
-- Get currently focused client
|
|
local sel = c or capi.client.focus
|
|
if sel then
|
|
-- Get all visible clients
|
|
local cls = visible(sel.screen)
|
|
local fcls = {}
|
|
-- Remove all non-normal clients
|
|
for idx, c in ipairs(cls) do
|
|
if focus.filter(c) or c == sel then
|
|
table.insert(fcls, c)
|
|
end
|
|
end
|
|
cls = fcls
|
|
-- Loop upon each client
|
|
for idx, c in ipairs(cls) do
|
|
if c == sel then
|
|
-- Cycle
|
|
return cls[util.cycle(#cls, idx + i)]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Return true whether client B is in the right direction
|
|
-- compared to client A.
|
|
-- @param dir The direction.
|
|
-- @param cA The first client.
|
|
-- @param cB The second client.
|
|
-- @return True if B is in the direction of A.
|
|
local function is_in_direction(dir, cA, cB)
|
|
local gA = cA:geometry()
|
|
local gB = cB:geometry()
|
|
if dir == "up" then
|
|
return gA.y > gB.y
|
|
elseif dir == "down" then
|
|
return gA.y < gB.y
|
|
elseif dir == "left" then
|
|
return gA.x > gB.x
|
|
elseif dir == "right" then
|
|
return gA.x < gB.x
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- Calculate distance between two points.
|
|
-- i.e: if we want to move to the right, we will take the right border
|
|
-- of the currently focused client and the left side of the checked client.
|
|
-- This avoid the focus of an upper client when you move to the right in a
|
|
-- tilebottom layout with nmaster=2 and 5 clients open, for instance.
|
|
-- @param dir The direction.
|
|
-- @param cA The first client.
|
|
-- @param cB The second client.
|
|
-- @return The distance between the clients.
|
|
local function calculate_distance(dir, cA, cB)
|
|
local gA = cA:geometry()
|
|
local gB = cB:geometry()
|
|
|
|
if dir == "up" then
|
|
gB.y = gB.y + gB.height
|
|
elseif dir == "down" then
|
|
gA.y = gA.y + gA.height
|
|
elseif dir == "left" then
|
|
gB.x = gB.x + gB.width
|
|
elseif dir == "right" then
|
|
gA.x = gA.x + gA.width
|
|
end
|
|
|
|
return math.sqrt(math.pow(gB.x - gA.x, 2) + math.pow(gB.y - gA.y, 2))
|
|
end
|
|
|
|
-- Get the nearest client in the given direction.
|
|
-- @param dir The direction, can be either "up", "down", "left" or "right".
|
|
-- @param c Optional client to get a client relative to. Else focussed is used.
|
|
local function get_client_in_direction(dir, c)
|
|
local sel = c or capi.client.focus
|
|
if sel then
|
|
local geometry = sel:geometry()
|
|
local dist, dist_min
|
|
local target = nil
|
|
local cls = visible(sel.screen)
|
|
|
|
-- We check each client.
|
|
for i, c in ipairs(cls) do
|
|
-- Check geometry to see if client is located in the right direction.
|
|
if is_in_direction(dir, sel, c) then
|
|
|
|
-- Calculate distance between focused client and checked client.
|
|
dist = calculate_distance(dir, sel, c)
|
|
|
|
-- If distance is shorter then keep the client.
|
|
if not target or dist < dist_min then
|
|
target = c
|
|
dist_min = dist
|
|
end
|
|
end
|
|
end
|
|
|
|
return target
|
|
end
|
|
end
|
|
|
|
--- Focus a client by the given direction.
|
|
-- @param dir The direction, can be either "up", "down", "left" or "right".
|
|
-- @param c Optional client.
|
|
function focus.bydirection(dir, c)
|
|
local sel = c or capi.client.focus
|
|
if sel then
|
|
local target = get_client_in_direction(dir, sel)
|
|
|
|
-- If we found a client to focus, then do it.
|
|
if target then
|
|
capi.client.focus = target
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Focus a client by its relative index.
|
|
-- @param i The index.
|
|
-- @param c Optional client.
|
|
function focus.byidx(i, c)
|
|
local target = next(i, c)
|
|
if target then
|
|
capi.client.focus = target
|
|
end
|
|
end
|
|
|
|
--- Swap a client with another client in the given direction
|
|
-- @param dir The direction, can be either "up", "down", "left" or "right".
|
|
-- @param c Optional client.
|
|
function swap.bydirection(dir, c)
|
|
local sel = c or capi.client.focus
|
|
if sel then
|
|
local target = get_client_in_direction(dir, sel)
|
|
|
|
-- If we found a client to swap with, then go for it
|
|
if target then
|
|
target:swap(sel)
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Swap a client by its relative index.
|
|
-- @param i The index.
|
|
-- @param c Optional client, otherwise focused one is used.
|
|
function swap.byidx(i, c)
|
|
local sel = c or capi.client.focus
|
|
local target = next(i, sel)
|
|
if target then
|
|
target:swap(sel)
|
|
end
|
|
end
|
|
|
|
--- Cycle clients.
|
|
-- @param clockwise True to cycle clients clockwise.
|
|
-- @param screen Optional screen where to cycle clients.
|
|
function cycle(clockwise, screen)
|
|
local screen = screen or capi.mouse.screen
|
|
local cls = visible(screen)
|
|
-- We can't rotate without at least 2 clients, buddy.
|
|
if #cls >= 2 then
|
|
local c = table.remove(cls, 1)
|
|
if clockwise then
|
|
for i = #cls, 1, -1 do
|
|
c:swap(cls[i])
|
|
end
|
|
else
|
|
for _, rc in pairs(cls) do
|
|
c:swap(rc)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Get the master window.
|
|
-- @param screen Optional screen number, otherwise screen mouse is used.
|
|
-- @return The master window.
|
|
function getmaster(screen)
|
|
local s = screen or capi.mouse.screen
|
|
return visible(s)[1]
|
|
end
|
|
|
|
--- Set the client as slave: put it at the end of other windows.
|
|
-- @param c The window to set as slave.
|
|
function setslave(c)
|
|
local cls = visible(c.screen)
|
|
for k, v in pairs(cls) do
|
|
c:swap(v)
|
|
end
|
|
end
|
|
|
|
--- Move/resize a client relative to current coordinates.
|
|
-- @param x The relative x coordinate.
|
|
-- @param y The relative y coordinate.
|
|
-- @param w The relative width.
|
|
-- @param h The relative height.
|
|
-- @param c The optional client, otherwise focused one is used.
|
|
function moveresize(x, y, w, h, c)
|
|
local sel = c or capi.client.focus
|
|
local geometry = sel:geometry()
|
|
geometry['x'] = geometry['x'] + x
|
|
geometry['y'] = geometry['y'] + y
|
|
geometry['width'] = geometry['width'] + w
|
|
geometry['height'] = geometry['height'] + h
|
|
sel:geometry(geometry)
|
|
end
|
|
|
|
--- Move a client to a tag.
|
|
-- @param target The tag to move the client to.
|
|
-- @param c Optional client to move, otherwise the focused one is used.
|
|
function movetotag(target, c)
|
|
local sel = c or capi.client.focus
|
|
if sel and target.screen then
|
|
-- Set client on the same screen as the tag.
|
|
sel.screen = target.screen
|
|
sel:tags({ target })
|
|
end
|
|
end
|
|
|
|
--- Toggle a tag on a client.
|
|
-- @param target The tag to toggle.
|
|
-- @param c Optional client to toggle, otherwise the focused one is used.
|
|
function toggletag(target, c)
|
|
local sel = c or capi.client.focus
|
|
-- Check that tag and client screen are identical
|
|
if sel and sel.screen == target.screen then
|
|
local tags = sel:tags()
|
|
local index = nil;
|
|
for i, v in ipairs(tags) do
|
|
if v == target then
|
|
index = i
|
|
break
|
|
end
|
|
end
|
|
if index then
|
|
-- If it's the only tag for the window, stop.
|
|
if #tags == 1 then return end
|
|
tags[index] = nil
|
|
else
|
|
tags[#tags + 1] = target
|
|
end
|
|
sel:tags(tags)
|
|
end
|
|
end
|
|
|
|
--- Move a client to a screen. Default is next screen, cycling.
|
|
-- @param c The client to move.
|
|
-- @param s The screen number, default to current + 1.
|
|
function movetoscreen(c, s)
|
|
local sel = c or capi.client.focus
|
|
if sel then
|
|
local sc = capi.screen.count()
|
|
if not s then
|
|
s = sel.screen + 1
|
|
end
|
|
if s > sc then s = 1 elseif s < 1 then s = sc end
|
|
sel.screen = s
|
|
capi.mouse.coords(capi.screen[s].geometry)
|
|
end
|
|
end
|
|
|
|
--- Mark a client, and then call 'marked' hook.
|
|
-- @param c The client to mark, the focused one if not specified.
|
|
-- @return True if the client has been marked. False if the client was already marked.
|
|
function mark(c)
|
|
local cl = c or capi.client.focus
|
|
if cl then
|
|
for k, v in pairs(data.marked) do
|
|
if cl == v then
|
|
return false
|
|
end
|
|
end
|
|
|
|
table.insert(data.marked, cl)
|
|
|
|
-- Call callback
|
|
cl:emit_signal("marked")
|
|
return true
|
|
end
|
|
end
|
|
|
|
--- Unmark a client and then call 'unmarked' hook.
|
|
-- @param c The client to unmark, or the focused one if not specified.
|
|
-- @return True if the client has been unmarked. False if the client was not marked.
|
|
function unmark(c)
|
|
local cl = c or capi.client.focus
|
|
|
|
for k, v in pairs(data.marked) do
|
|
if cl == v then
|
|
table.remove(data.marked, k)
|
|
cl:emit_signal("unmarked")
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
--- Check if a client is marked.
|
|
-- @param c The client to check, or the focused one otherwise.
|
|
function ismarked(c)
|
|
local cl = c or capi.client.focus
|
|
if cl then
|
|
for k, v in pairs(data.marked) do
|
|
if cl == v then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
--- Toggle a client as marked.
|
|
-- @param c The client to toggle mark.
|
|
function togglemarked(c)
|
|
local cl = c or capi.client.focus
|
|
|
|
if not mark(c) then
|
|
unmark(c)
|
|
end
|
|
end
|
|
|
|
--- Return the marked clients and empty the marked table.
|
|
-- @return A table with all marked clients.
|
|
function getmarked()
|
|
for k, v in pairs(data.marked) do
|
|
v:emit_signal("unmarked")
|
|
end
|
|
|
|
t = data.marked
|
|
data.marked = {}
|
|
return t
|
|
end
|
|
|
|
--- Set a client floating state, overriding auto-detection.
|
|
-- Floating client are not handled by tiling layouts.
|
|
-- @param c A client.
|
|
-- @param s True or false.
|
|
function floating.set(c, s)
|
|
local c = c or capi.client.focus
|
|
if c and property.get(c, "floating") ~= s then
|
|
property.set(c, "floating", s)
|
|
local screen = c.screen
|
|
if s == true then
|
|
c:geometry(property.get(c, "floating_geometry"))
|
|
end
|
|
c.screen = screen
|
|
end
|
|
end
|
|
|
|
local function store_floating_geometry(c)
|
|
if floating.get(c) then
|
|
property.set(c, "floating_geometry", c:geometry())
|
|
end
|
|
end
|
|
|
|
-- Store the initial client geometry.
|
|
capi.client.connect_signal("new", function(c)
|
|
local function store_init_geometry(c)
|
|
property.set(c, "floating_geometry", c:geometry())
|
|
c:disconnect_signal("property::border_width", store_init_geometry)
|
|
end
|
|
c:connect_signal("property::border_width", store_init_geometry)
|
|
end)
|
|
|
|
capi.client.connect_signal("manage", function(c)
|
|
c:connect_signal("property::geometry", store_floating_geometry)
|
|
end)
|
|
|
|
--- Return if a client has a fixe size or not.
|
|
-- @param c The client.
|
|
function isfixed(c)
|
|
local c = c or capi.client.focus
|
|
if not c then return end
|
|
local h = c.size_hints
|
|
if h.min_width and h.max_width
|
|
and h.max_height and h.min_height
|
|
and h.min_width > 0 and h.max_width > 0
|
|
and h.max_height > 0 and h.min_height > 0
|
|
and h.min_width == h.max_width
|
|
and h.min_height == h.max_height then
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
--- Get a client floating state.
|
|
-- @param c A client.
|
|
-- @return True or false. Note that some windows might be floating even if you
|
|
-- did not set them manually. For example, windows with a type different than
|
|
-- normal.
|
|
function floating.get(c)
|
|
local c = c or capi.client.focus
|
|
if c then
|
|
local value = property.get(c, "floating")
|
|
if value ~= nil then
|
|
return value
|
|
end
|
|
if c.type ~= "normal"
|
|
or c.fullscreen
|
|
or c.maximized_vertical
|
|
or c.maximized_horizontal
|
|
or isfixed(c) then
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
end
|
|
|
|
--- Toggle the floating state of a client between 'auto' and 'true'.
|
|
-- @param c A client.
|
|
function floating.toggle(c)
|
|
local c = c or capi.client.focus
|
|
-- If it has been set to floating
|
|
if floating.get(c) then
|
|
floating.set(c, false)
|
|
else
|
|
floating.set(c, true)
|
|
end
|
|
end
|
|
|
|
--- Remove the floating information on a client.
|
|
-- @param c The client.
|
|
function floating.delete(c)
|
|
floating.set(c, nil)
|
|
end
|
|
|
|
--- Restore (=unminimize) a random client.
|
|
-- @param s The screen to use.
|
|
-- @return True if some client was restored.
|
|
function restore(s)
|
|
local s = s or (capi.client.focus and capi.client.focus.screen) or capi.mouse.screen
|
|
local cls = capi.client.get(s)
|
|
local tags = tag.selectedlist(s)
|
|
local mcls = {}
|
|
for k, c in pairs(cls) do
|
|
local ctags = c:tags()
|
|
if c.minimized then
|
|
for k, t in ipairs(tags) do
|
|
if util.table.hasitem(ctags, t) then
|
|
c.minimized = false
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- Normalize a set of numbers to 1
|
|
-- @param set the set of numbers to normalize
|
|
-- @param num the number of numbers to normalize
|
|
local function normalize(set, num)
|
|
local num = num or #set
|
|
local total = 0
|
|
if num then
|
|
for i = 1,num do
|
|
total = total + set[i]
|
|
end
|
|
for i = 1,num do
|
|
set[i] = set[i] / total
|
|
end
|
|
else
|
|
for i,v in ipairs(set) do
|
|
total = total + v
|
|
end
|
|
|
|
for i,v in ipairs(set) do
|
|
set[i] = v / total
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Calculate a client's column number, index in that column, and
|
|
-- number of visible clients in this column.
|
|
-- @param c the client
|
|
-- @return col the column number
|
|
-- @return idx index of the client in the column
|
|
-- @return num the number of visible clients in the column
|
|
function idx(c)
|
|
local c = c or capi.client.focus
|
|
if not c then return end
|
|
|
|
local clients = tiled(c.screen)
|
|
local idx = nil
|
|
for k, cl in ipairs(clients) do
|
|
if cl == c then
|
|
idx = k
|
|
break
|
|
end
|
|
end
|
|
|
|
local t = tag.selected(c.screen)
|
|
local nmaster = tag.getnmaster(t)
|
|
if idx <= nmaster then
|
|
return {idx = idx, col=0, num=nmaster}
|
|
end
|
|
local nother = #clients - nmaster
|
|
idx = idx - nmaster
|
|
|
|
-- rather than regenerate the column number we can calculate it
|
|
-- based on the how the tiling algorithm places clients we calculate
|
|
-- the column, we could easily use the for loop in the program but we can
|
|
-- calculate it.
|
|
local ncol = tag.getncol(t)
|
|
-- minimum number of clients per column
|
|
local percol = math.floor(nother / ncol)
|
|
-- number of columns with an extra client
|
|
local overcol = math.mod(nother, ncol)
|
|
-- number of columns filled with [percol] clients
|
|
local regcol = ncol - overcol
|
|
|
|
local col = math.floor( (idx - 1) / percol) + 1
|
|
if col > regcol then
|
|
-- col = math.floor( (idx - (percol*regcol) - 1) / (percol + 1) ) + regcol + 1
|
|
-- simplified
|
|
col = math.floor( (idx + regcol + percol) / (percol+1) )
|
|
-- calculate the index in the column
|
|
idx = idx - percol*regcol - (col - regcol - 1) * (percol+1)
|
|
percol = percol+1
|
|
else
|
|
idx = idx - percol*(col-1)
|
|
end
|
|
|
|
return {idx = idx, col=col, num=percol}
|
|
end
|
|
|
|
|
|
--- Set the window factor of a client
|
|
-- @param wfact the window factor value
|
|
-- @param c the client
|
|
function setwfact(wfact, c)
|
|
-- get the currently selected window
|
|
local c = c or capi.client.focus
|
|
if not c or not c:isvisible() then return end
|
|
|
|
local t = tag.selected(c.screen)
|
|
local w = idx(c)
|
|
|
|
local cls = tiled(t.screen)
|
|
local nmaster = tag.getnmaster(t)
|
|
|
|
-- n is the number of windows currently visible for which we have to be concerned with the properties
|
|
local data = tag.getproperty(t, "windowfact") or {}
|
|
local colfact = data[w.col]
|
|
|
|
colfact[w.idx] = wfact
|
|
rest = 1-wfact
|
|
|
|
-- calculate the current denominator
|
|
local total = 0
|
|
for i = 1,w.num do
|
|
if i ~= w.idx then
|
|
total = total + colfact[i]
|
|
end
|
|
end
|
|
|
|
-- normalize the windows
|
|
for i = 1,w.num do
|
|
if i ~= w.idx then
|
|
colfact[i] = (colfact[i] * rest) / total
|
|
end
|
|
end
|
|
|
|
t:emit_signal("property::windowfact")
|
|
end
|
|
|
|
--- Increment a client's window factor
|
|
-- @param add amount to increase the client's window
|
|
-- @param c the client
|
|
function incwfact(add, c)
|
|
local c = c or capi.client.focus
|
|
if not c then return end
|
|
|
|
local t = tag.selected(c.screen)
|
|
|
|
local w = idx(c)
|
|
|
|
local nmaster = tag.getnmaster(t)
|
|
local data = tag.getproperty(t, "windowfact") or {}
|
|
local colfact = data[w.col]
|
|
curr = colfact[w.idx] or 1
|
|
colfact[w.idx] = curr + add
|
|
|
|
-- keep our ratios normalized
|
|
normalize(colfact, w.num)
|
|
|
|
t:emit_signal("property::windowfact")
|
|
end
|
|
|
|
--- Get a client dockable state.
|
|
-- @param c A client.
|
|
-- @return True or false. Note that some windows might be dockable even if you
|
|
-- did not set them manually. For example, windows with a type "utility", "toolbar"
|
|
-- or "dock"
|
|
function dockable.get(c)
|
|
local value = property.get(c, "dockable")
|
|
|
|
-- Some sane defaults
|
|
if value == nil then
|
|
if (c.type == "utility" or c.type == "toolbar" or c.type == "dock") then
|
|
value = true
|
|
else
|
|
value = false
|
|
end
|
|
end
|
|
|
|
return value
|
|
end
|
|
|
|
--- Set a client dockable state, overriding auto-detection.
|
|
-- With this enabled you can dock windows by moving them from the center
|
|
-- to the edge of the workarea.
|
|
-- @param c A client.
|
|
-- @param value True or false.
|
|
function dockable.set(c, value)
|
|
property.set(c, "dockable", value)
|
|
end
|
|
|
|
--- Get a client property.
|
|
-- @param c The client.
|
|
-- @param prop The property name.
|
|
-- @return The property.
|
|
function property.get(c, prop)
|
|
if data.properties[c] then
|
|
return data.properties[c][prop]
|
|
end
|
|
end
|
|
|
|
--- Set a client property.
|
|
-- This properties are internal to awful. Some are used to move clients, etc.
|
|
-- @param c The client.
|
|
-- @param prop The property name.
|
|
-- @param value The value.
|
|
function property.set(c, prop, value)
|
|
if not data.properties[c] then
|
|
data.properties[c] = {}
|
|
end
|
|
data.properties[c][prop] = value
|
|
c:emit_signal("property::" .. prop)
|
|
end
|
|
|
|
---
|
|
-- Returns an iterator to cycle through, starting from the client in focus or
|
|
-- the given index, all clients that match a given criteria.
|
|
--
|
|
-- @param filter a function that returns true to indicate a positive match
|
|
-- @param start what index to start iterating from. Defaults to using the
|
|
-- index of the currently focused client.
|
|
-- @param s which screen to use. nil means all screens.
|
|
--
|
|
-- @usage e.g.: un-minimize all urxvt instances
|
|
-- <p><code>
|
|
-- local urxvt = function (c) <br/>
|
|
-- return awful.rules.match(c, {class = "URxvt"}) <br/>
|
|
-- end <br/>
|
|
-- </br>
|
|
-- for c in awful.client.iterate(urxvt) do <br/>
|
|
-- c.minimized = false <br/>
|
|
-- end <br/>
|
|
-- </code></p>
|
|
function iterate(filter, start, s)
|
|
local clients = capi.client.get(s)
|
|
local focused = capi.client.focus
|
|
local start = start or util.table.hasitem(clients, focused)
|
|
return util.table.iterate(clients, filter, start)
|
|
end
|
|
|
|
---
|
|
-- <p>Switch to a client matching the given condition if running, else spawn it.
|
|
-- If multiple clients match the given condition then the next one is
|
|
-- focussed.</p>
|
|
--
|
|
-- @param cmd the command to execute
|
|
-- @param matcher a function that returns true to indicate a matching client
|
|
-- @param merge if true then merge tags when clients are not visible
|
|
--
|
|
-- @usage run or raise urxvt (perhaps, with tabs) on modkey + semicolon
|
|
-- <p><code>
|
|
-- awful.key({ modkey, }, 'semicolon', function () <br/>
|
|
-- local matcher = function (c) <br/>
|
|
-- return awful.rules.match(c, {class = 'URxvt'}) <br/>
|
|
-- end <br/>
|
|
-- awful.client.run_or_raise('urxvt', matcher)
|
|
-- end);
|
|
-- </code></p>
|
|
function run_or_raise(cmd, matcher, merge)
|
|
local clients = capi.client.get()
|
|
local findex = util.table.hasitem(clients, capi.client.focus) or 1
|
|
local start = util.cycle(#clients, findex + 1)
|
|
|
|
for c in iterate(matcher, start) do
|
|
jumpto(c, merge)
|
|
return
|
|
end
|
|
|
|
-- client not found, spawn it
|
|
util.spawn(cmd)
|
|
end
|
|
|
|
-- Register standards signals
|
|
capi.client.add_signal("property::floating_geometry")
|
|
capi.client.add_signal("property::floating")
|
|
capi.client.add_signal("property::dockable")
|
|
|
|
capi.client.connect_signal("focus", focus.history.add)
|
|
capi.client.connect_signal("unmanage", focus.history.delete)
|
|
|
|
capi.client.connect_signal("manage", function(c) c:connect_signal("property::urgent", urgent.add) end)
|
|
capi.client.connect_signal("focus", urgent.delete)
|
|
capi.client.connect_signal("unmanage", urgent.delete)
|
|
|
|
capi.client.connect_signal("unmanage", floating.delete)
|
|
|
|
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
|