awesome/lib/awful/client.lua.in

576 lines
16 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 ipairs = ipairs
local table = table
local math = math
local otable = otable
local setmetatable = setmetatable
local capi =
{
client = client,
mouse = mouse,
screen = screen,
hooks = hooks
}
local hooks = require("awful.hooks")
--- Client module for awful
module("awful.client")
-- Private data
local data = {}
data.focus = {}
data.urgent = {}
data.marked = {}
data.floating = otable()
-- Urgent functions
urgent = {}
focus = {}
focus.history = {}
swap = {}
floating = {}
-- User hooks
hooks.user.create('marked')
hooks.user.create('unmarked')
--- 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.
function urgent.jumpto()
local c = urgent.get()
if c then
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
-- focus the tag
tag.viewonly(c:tags()[1])
-- focus the client
capi.client.focus = c
c:raise()
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 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" then
return nil
end
return c
end
--- Update client focus history.
-- @param c The client that has been focused.
function focus.history.add(c)
if focus.filter(c) then
-- Remove the client if its in stack
focus.history.delete(c)
-- Record the client has latest focused
table.insert(data.focus, 1, c)
end
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
if counter == 0 then
return vc[1]
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) 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)
if dir == "up" then
return cA['y'] > cB['y']
elseif dir == "down" then
return cA['y'] < cB['y']
elseif dir == "left" then
return cA['x'] > cB['x']
elseif dir == "right" then
return cA['x'] < cB['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 xA = cA['x']
local xB = cB['x']
local yA = cA['y']
local yB = cB['y']
if dir == "up" then
yB = yB + cB['height']
elseif dir == "down" then
yA = yA + cA['height']
elseif dir == "left" then
xB = xB + cB['width']
elseif dir == "right" then
xA = xA + cA['width']
end
return math.sqrt(math.pow(xB - xA, 2) + math.pow(yB - yA, 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, geometry, c:geometry()) then
-- Calculate distance between focused client and checked client.
dist = calculate_distance(dir, geometry, c:geometry())
-- 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
--- 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
function maximize()
util.deprecate("maximized_horizontal and vertical client attributes")
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 then
-- Check that tag and client screen are identical
if sel.screen ~= target.screen then return end
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()
if tags[target] then
-- If it's the only tag for the window, stop.
if #tags == 1 then return end
tags[tags[target]] = nil
else
tags[target] = 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)
capi.client.focus = sel
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
hooks.user.call('marked', cl)
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)
hooks.user.call('unmarked', cl)
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
hooks.user.call('unmarked', v)
end
t = data.marked
data.marked = {}
return t
end
--- Set a client floating state.
-- Floating client are not handled by tiling layouts.
-- @param c A client.
-- @param state True or false.
function floating.set(c, s)
local c = c or capi.client.focus
if c and data.floating[c] ~= s then
data.floating[c] = s
capi.hooks.arrange()(c.screen)
capi.hooks.property()(c, "floating")
end
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
if data.floating[c] ~= nil then
return data.floating[c]
end
return (c.type ~= "normal"
or c.fullscreen
or c.maximized_vertical
or c.maximized_horizontal)
end
end
--- Toggle the floating state of a client.
-- @param c A client.
function floating.toggle(c)
floating.set(c, not floating.get(c))
end
--- Remove the floating information on a client.
-- @param c The client.
function floating.delete(c)
data.floating[c] = nil
end
-- Register standards hooks
hooks.focus.register(focus.history.add)
hooks.unmanage.register(focus.history.delete)
hooks.property.register(urgent.add)
hooks.focus.register(urgent.delete)
hooks.unmanage.register(urgent.delete)
hooks.unmanage.register(floating.delete)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80