summaryrefslogtreecommitdiff
path: root/lib/awful/client.lua
diff options
context:
space:
mode:
Diffstat (limited to 'lib/awful/client.lua')
-rw-r--r--lib/awful/client.lua1238
1 files changed, 1238 insertions, 0 deletions
diff --git a/lib/awful/client.lua b/lib/awful/client.lua
new file mode 100644
index 0000000..809c055
--- /dev/null
+++ b/lib/awful/client.lua
@@ -0,0 +1,1238 @@
+---------------------------------------------------------------------------
+--- Useful client manipulation functions.
+--
+-- @author Julien Danjou <julien@danjou.info>
+-- @copyright 2008 Julien Danjou
+-- @module client
+---------------------------------------------------------------------------
+
+-- Grab environment we need
+local util = require("awful.util")
+local spawn = require("awful.spawn")
+local object = require("gears.object")
+local grect = require("gears.geometry").rectangle
+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,
+ awesome = awesome,
+}
+
+local function get_screen(s)
+ return s and capi.screen[s]
+end
+
+-- We use a metatable to prevent circular dependency loops.
+local screen
+do
+ screen = setmetatable({}, {
+ __index = function(_, k)
+ screen = require("awful.screen")
+ return screen[k]
+ end,
+ __newindex = error -- Just to be sure in case anything ever does this
+ })
+end
+local client = {object={}}
+
+-- Private data
+client.data = {}
+client.data.marked = {}
+client.data.persistent_properties_registered = {} -- keys are names of persistent properties, value always true
+
+-- Functions
+client.urgent = require("awful.client.urgent")
+client.swap = {}
+client.floating = {}
+client.dockable = {}
+client.property = {}
+client.shape = require("awful.client.shape")
+client.focus = require("awful.client.focus")
+
+--- Jump to the given client.
+-- Takes care of focussing the screen, the right tag, etc.
+--
+-- @deprecated awful.client.jumpto
+-- @see client.jump_to
+-- @client c the client to jump to
+-- @tparam bool|function merge If true then merge tags (select the client's
+-- first tag additionally) when the client is not visible.
+-- If it is a function, it will be called with the client and its first
+-- tag as arguments.
+function client.jumpto(c, merge)
+ util.deprecate("Use c:jump_to(merge) instead of awful.client.jumpto")
+ client.object.jump_to(c, merge)
+end
+
+--- Jump to the given client.
+-- Takes care of focussing the screen, the right tag, etc.
+--
+-- @function client.jump_to
+-- @tparam bool|function merge If true then merge tags (select the client's
+-- first tag additionally) when the client is not visible.
+-- If it is a function, it will be called with the client and its first
+-- tag as arguments.
+function client.object.jump_to(self, merge)
+ local s = get_screen(screen.focused())
+ -- focus the screen
+ if s ~= get_screen(self.screen) then
+ screen.focus(self.screen)
+ end
+
+ self.minimized = false
+
+ -- Try to make client visible, this also covers e.g. sticky.
+ if not self:isvisible() then
+ local t = self.first_tag
+ if merge then
+ if type(merge) == "function" then
+ merge(self, t)
+ elseif t then
+ t.selected = true
+ end
+ elseif t then
+ t:view_only()
+ end
+ end
+
+ self:emit_signal("request::activate", "client.jumpto", {raise=true})
+end
+
+--- Get visible clients from a screen.
+--
+-- @deprecated awful.client.visible
+-- @see screen.clients
+-- @tparam[opt] integer|screen s The screen, or nil for all screens.
+-- @tparam[opt=false] boolean stacked Use stacking order? (top to bottom)
+-- @treturn table A table with all visible clients.
+function client.visible(s, stacked)
+ local cls = capi.client.get(s, stacked)
+ local vcls = {}
+ for _, c in pairs(cls) do
+ if c:isvisible() then
+ table.insert(vcls, c)
+ end
+ end
+ return vcls
+end
+
+--- Get visible and tiled clients
+--
+-- @deprecated awful.client.tiled
+-- @see screen.tiled_clients
+-- @tparam integer|screen s The screen, or nil for all screens.
+-- @tparam[opt=false] boolean stacked Use stacking order? (top to bottom)
+-- @treturn table A table with all visible and tiled clients.
+function client.tiled(s, stacked)
+ local clients = client.visible(s, stacked)
+ local tclients = {}
+ -- Remove floating clients
+ for _, c in pairs(clients) do
+ if not client.object.get_floating(c)
+ and not c.fullscreen
+ and not c.maximized_vertical
+ and not c.maximized_horizontal then
+ table.insert(tclients, c)
+ end
+ end
+ return tclients
+end
+
+--- Get a client by its relative index to another client.
+-- If no client is passed, the focused client will be used.
+--
+-- @function awful.client.next
+-- @tparam int i The index. Use 1 to get the next, -1 to get the previous.
+-- @client[opt] sel The client.
+-- @tparam[opt=false] boolean stacked Use stacking order? (top to bottom)
+-- @return A client, or nil if no client is available.
+--
+-- @usage -- focus the next window in the index
+-- awful.client.next(1)
+-- -- focus the previous
+-- awful.client.next(-1)
+function client.next(i, sel, stacked)
+ -- Get currently focused client
+ sel = sel or capi.client.focus
+ if sel then
+ -- Get all visible clients
+ local cls = client.visible(sel.screen, stacked)
+ local fcls = {}
+ -- Remove all non-normal clients
+ for _, c in ipairs(cls) do
+ if client.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
+
+--- Swap a client with another client in the given direction.
+-- @function awful.client.swap.bydirection
+-- @tparam string dir The direction, can be either "up", "down", "left" or "right".
+-- @client[opt=focused] c The client.
+-- @tparam[opt=false] boolean stacked Use stacking order? (top to bottom)
+function client.swap.bydirection(dir, c, stacked)
+ local sel = c or capi.client.focus
+ if sel then
+ local cltbl = client.visible(sel.screen, stacked)
+ local geomtbl = {}
+ for i,cl in ipairs(cltbl) do
+ geomtbl[i] = cl:geometry()
+ end
+ local target = grect.get_in_direction(dir, geomtbl, sel:geometry())
+
+ -- If we found a client to swap with, then go for it
+ if target then
+ cltbl[target]:swap(sel)
+ end
+ end
+end
+
+--- Swap a client with another client in the given direction. Swaps across screens.
+-- @function awful.client.swap.global_bydirection
+-- @param dir The direction, can be either "up", "down", "left" or "right".
+-- @client[opt] sel The client.
+function client.swap.global_bydirection(dir, sel)
+ sel = sel or capi.client.focus
+ local scr = get_screen(sel and sel.screen or screen.focused())
+
+ if sel then
+ -- move focus
+ client.focus.global_bydirection(dir, sel)
+ local c = capi.client.focus
+
+ -- swapping inside a screen
+ if get_screen(sel.screen) == get_screen(c.screen) and sel ~= c then
+ c:swap(sel)
+
+ -- swapping to an empty screen
+ elseif get_screen(sel.screen) ~= get_screen(c.screen) and sel == c then
+ sel:move_to_screen(screen.focused())
+
+ -- swapping to a nonempty screen
+ elseif get_screen(sel.screen) ~= get_screen(c.screen) and sel ~= c then
+ sel:move_to_screen(c.screen)
+ c:move_to_screen(scr)
+ end
+
+ screen.focus(sel.screen)
+ sel:emit_signal("request::activate", "client.swap.global_bydirection",
+ {raise=false})
+ end
+end
+
+--- Swap a client by its relative index.
+-- @function awful.client.swap.byidx
+-- @param i The index.
+-- @client[opt] c The client, otherwise focused one is used.
+function client.swap.byidx(i, c)
+ local sel = c or capi.client.focus
+ local target = client.next(i, sel)
+ if target then
+ target:swap(sel)
+ end
+end
+
+--- Cycle clients.
+--
+-- @function awful.client.cycle
+-- @param clockwise True to cycle clients clockwise.
+-- @param[opt] s The screen where to cycle clients.
+-- @tparam[opt=false] boolean stacked Use stacking order? (top to bottom)
+function client.cycle(clockwise, s, stacked)
+ s = s or screen.focused()
+ local cls = client.visible(s, stacked)
+ -- 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.
+--
+-- @legacylayout awful.client.getmaster
+-- @screen_or_idx[opt=awful.screen.focused()] s The screen.
+-- @return The master window.
+function client.getmaster(s)
+ s = s or screen.focused()
+ return client.visible(s)[1]
+end
+
+--- Set the client as master: put it at the beginning of other windows.
+--
+-- @legacylayout awful.client.setmaster
+-- @client c The window to set as master.
+function client.setmaster(c)
+ local cls = util.table.reverse(capi.client.get(c.screen))
+ for _, v in pairs(cls) do
+ c:swap(v)
+ end
+end
+
+--- Set the client as slave: put it at the end of other windows.
+-- @legacylayout awful.client.setslave
+-- @client c The window to set as slave.
+function client.setslave(c)
+ local cls = capi.client.get(c.screen)
+ for _, v in pairs(cls) do
+ c:swap(v)
+ end
+end
+
+--- Move/resize a client relative to current coordinates.
+-- @deprecated awful.client.moveresize
+-- @param x The relative x coordinate.
+-- @param y The relative y coordinate.
+-- @param w The relative width.
+-- @param h The relative height.
+-- @client[opt] c The client, otherwise focused one is used.
+-- @see client.relative_move
+function client.moveresize(x, y, w, h, c)
+ util.deprecate("Use c:relative_move(x, y, w, h) instead of awful.client.moveresize")
+ client.object.relative_move(c or capi.client.focus, x, y, w, h)
+end
+
+--- Move/resize a client relative to current coordinates.
+-- @function client.relative_move
+-- @see geometry
+-- @tparam[opt=c.x] number x The relative x coordinate.
+-- @tparam[opt=c.y] number y The relative y coordinate.
+-- @tparam[opt=c.width] number w The relative width.
+-- @tparam[opt=c.height] number h The relative height.
+function client.object.relative_move(self, x, y, w, h)
+ local geometry = self:geometry()
+ geometry['x'] = geometry['x'] + x
+ geometry['y'] = geometry['y'] + y
+ geometry['width'] = geometry['width'] + w
+ geometry['height'] = geometry['height'] + h
+ self:geometry(geometry)
+end
+
+--- Move a client to a tag.
+-- @deprecated awful.client.movetotag
+-- @param target The tag to move the client to.
+-- @client[opt] c The client to move, otherwise the focused one is used.
+-- @see client.move_to_tag
+function client.movetotag(target, c)
+ util.deprecate("Use c:move_to_tag(target) instead of awful.client.movetotag")
+ client.object.move_to_tag(c or capi.client.focus, target)
+end
+
+--- Move a client to a tag.
+-- @function client.move_to_tag
+-- @tparam tag target The tag to move the client to.
+function client.object.move_to_tag(self, target)
+ local s = target.screen
+ if self and s then
+ if self == capi.client.focus then
+ self:emit_signal("request::activate", "client.movetotag", {raise=true})
+ end
+ -- Set client on the same screen as the tag.
+ self.screen = s
+ self:tags({ target })
+ end
+end
+
+--- Toggle a tag on a client.
+-- @deprecated awful.client.toggletag
+-- @param target The tag to toggle.
+-- @client[opt] c The client to toggle, otherwise the focused one is used.
+-- @see client.toggle_tag
+function client.toggletag(target, c)
+ util.deprecate("Use c:toggle_tag(target) instead of awful.client.toggletag")
+ client.object.toggle_tag(c or capi.client.focus, target)
+end
+
+--- Toggle a tag on a client.
+-- @function client.toggle_tag
+-- @tparam tag target The tag to move the client to.
+function client.object.toggle_tag(self, target)
+ -- Check that tag and client screen are identical
+ if self and get_screen(self.screen) == get_screen(target.screen) then
+ local tags = self: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
+ self:tags(tags)
+ end
+end
+
+--- Move a client to a screen. Default is next screen, cycling.
+-- @deprecated awful.client.movetoscreen
+-- @client c The client to move.
+-- @param s The screen, default to current + 1.
+-- @see screen
+-- @see client.move_to_screen
+function client.movetoscreen(c, s)
+ util.deprecate("Use c:move_to_screen(s) instead of awful.client.movetoscreen")
+ client.object.move_to_screen(c or capi.client.focus, s)
+end
+
+--- Move a client to a screen. Default is next screen, cycling.
+-- @function client.move_to_screen
+-- @tparam[opt=c.screen.index+1] screen s The screen, default to current + 1.
+-- @see screen
+-- @see request::activate
+function client.object.move_to_screen(self, s)
+ if self then
+ local sc = capi.screen.count()
+ if not s then
+ s = self.screen.index + 1
+ end
+ if type(s) == "number" then
+ if s > sc then s = 1 elseif s < 1 then s = sc end
+ end
+ s = get_screen(s)
+ if get_screen(self.screen) ~= s then
+ local sel_is_focused = self == capi.client.focus
+ self.screen = s
+ screen.focus(s)
+
+ if sel_is_focused then
+ self:emit_signal("request::activate", "client.movetoscreen",
+ {raise=true})
+ end
+ end
+ end
+end
+
+--- Tag a client with the set of current tags.
+-- @function client.to_selected_tags
+-- @see screen.selected_tags
+function client.object.to_selected_tags(self)
+ local tags = {}
+
+ for _, t in ipairs(self:tags()) do
+ if get_screen(t.screen) == get_screen(self.screen) then
+ table.insert(tags, t)
+ end
+ end
+
+ if self.screen then
+ if #tags == 0 then
+ tags = self.screen.selected_tags
+ end
+
+ if #tags == 0 then
+ tags = self.screen.tags
+ end
+ end
+
+ if #tags ~= 0 then
+ self:tags(tags)
+ end
+end
+
+--- If a client is marked or not.
+--
+-- **Signal:**
+--
+-- * *marked* (for legacy reasons, use `property::marked`)
+-- * *unmarked* (for legacy reasons, use `property::marked`)
+-- * *property::marked*
+--
+-- @property marked
+-- @param boolean
+
+--- The border color when the client is focused.
+--
+-- @beautiful beautiful.border_marked
+-- @param string
+--
+
+function client.object.set_marked(self, value)
+ local is_marked = self.marked
+
+ if value == false and is_marked then
+ for k, v in pairs(client.data.marked) do
+ if self == v then
+ table.remove(client.data.marked, k)
+ end
+ end
+ self:emit_signal("unmarked")
+ elseif not is_marked and value then
+ self:emit_signal("marked")
+ table.insert(client.data.marked, self)
+ end
+
+ client.property.set(self, "marked", value)
+end
+
+function client.object.get_marked(self)
+ return client.property.get(self, "marked")
+end
+
+--- Mark a client, and then call 'marked' hook.
+-- @deprecated awful.client.mark
+-- @client c The client to mark, the focused one if not specified.
+function client.mark(c)
+ util.deprecate("Use c.marked = true instead of awful.client.mark")
+ client.object.set_marked(c or capi.client.focus, true)
+end
+
+--- Unmark a client and then call 'unmarked' hook.
+-- @deprecated awful.client.unmark
+-- @client c The client to unmark, or the focused one if not specified.
+function client.unmark(c)
+ util.deprecate("Use c.marked = false instead of awful.client.unmark")
+ client.object.set_marked(c or capi.client.focus, false)
+end
+
+--- Check if a client is marked.
+-- @deprecated awful.client.ismarked
+-- @client c The client to check, or the focused one otherwise.
+function client.ismarked(c)
+ util.deprecate("Use c.marked instead of awful.client.ismarked")
+ return client.object.get_marked(c or capi.client.focus)
+end
+
+--- Toggle a client as marked.
+-- @deprecated awful.client.togglemarked
+-- @client c The client to toggle mark.
+function client.togglemarked(c)
+ util.deprecate("Use c.marked = not c.marked instead of awful.client.togglemarked")
+ c = c or capi.client.focus
+ if c then
+ c.marked = not c.marked
+ end
+end
+
+--- Return the marked clients and empty the marked table.
+-- @function awful.client.getmarked
+-- @return A table with all marked clients.
+function client.getmarked()
+ local copy = util.table.clone(client.data.marked, false)
+
+ for _, v in pairs(copy) do
+ client.property.set(v, "marked", false)
+ v:emit_signal("unmarked")
+ end
+
+ client.data.marked = {}
+
+ return copy
+end
+
+--- Set a client floating state, overriding auto-detection.
+-- Floating client are not handled by tiling layouts.
+-- @deprecated awful.client.floating.set
+-- @client c A client.
+-- @param s True or false.
+function client.floating.set(c, s)
+ util.deprecate("Use c.floating = true instead of awful.client.floating.set")
+ client.object.set_floating(c, s)
+end
+
+-- Set a client floating state, overriding auto-detection.
+-- Floating client are not handled by tiling layouts.
+-- @client c A client.
+-- @param s True or false.
+function client.object.set_floating(c, s)
+ c = c or capi.client.focus
+ if c and client.property.get(c, "floating") ~= s then
+ client.property.set(c, "floating", s)
+ local scr = c.screen
+ if s == true then
+ c:geometry(client.property.get(c, "floating_geometry"))
+ end
+ c.screen = scr
+ end
+end
+
+local function store_floating_geometry(c)
+ if client.object.get_floating(c) then
+ client.property.set(c, "floating_geometry", c:geometry())
+ end
+end
+
+-- Store the initial client geometry.
+capi.client.connect_signal("new", function(cl)
+ local function store_init_geometry(c)
+ client.property.set(c, "floating_geometry", c:geometry())
+ c:disconnect_signal("property::border_width", store_init_geometry)
+ end
+ cl:connect_signal("property::border_width", store_init_geometry)
+end)
+
+capi.client.connect_signal("property::geometry", store_floating_geometry)
+
+--- Return if a client has a fixed size or not.
+-- This function is deprecated, use `c.is_fixed`
+-- @client c The client.
+-- @deprecated awful.client.isfixed
+-- @see is_fixed
+-- @see size_hints_honor
+function client.isfixed(c)
+ util.deprecate("Use c.is_fixed instead of awful.client.isfixed")
+ c = c or capi.client.focus
+ return client.object.is_fixed(c)
+end
+
+--- Return if a client has a fixed size or not.
+--
+-- **Signal:**
+--
+-- * *property::is_fixed*
+--
+-- This property is read only.
+-- @property is_fixed
+-- @param boolean The floating state
+-- @see size_hints
+-- @see size_hints_honor
+
+function client.object.is_fixed(c)
+ 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.
+-- @client c A client.
+-- @see floating
+-- @deprecated awful.client.floating.get
+-- @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 client.floating.get(c)
+ util.deprecate("Use c.floating instead of awful.client.floating.get")
+ return client.object.get_floating(c)
+end
+
+--- The client floating state.
+-- If the client is part of the tiled layout or free floating.
+--
+-- Note that some windows might be floating even if you
+-- did not set them manually. For example, windows with a type different than
+-- normal.
+--
+-- **Signal:**
+--
+-- * *property::floating*
+--
+-- @property floating
+-- @param boolean The floating state
+
+function client.object.get_floating(c)
+ c = c or capi.client.focus
+ if c then
+ local value = client.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 client.object.is_fixed(c) then
+ return true
+ end
+ return false
+ end
+end
+
+--- Toggle the floating state of a client between 'auto' and 'true'.
+-- Use `c.floating = not c.floating`
+-- @deprecated awful.client.floating.toggle
+-- @client c A client.
+-- @see floating
+function client.floating.toggle(c)
+ c = c or capi.client.focus
+ -- If it has been set to floating
+ client.object.set_floating(c, not client.object.get_floating(c))
+end
+
+-- Remove the floating information on a client.
+-- @client c The client.
+function client.floating.delete(c)
+ client.object.set_floating(c, nil)
+end
+
+--- The x coordinates.
+--
+-- **Signal:**
+--
+-- * *property::x*
+--
+-- @property x
+-- @param integer
+
+--- The y coordinates.
+--
+-- **Signal:**
+--
+-- * *property::y*
+--
+-- @property y
+-- @param integer
+
+--- The width of the wibox.
+--
+-- **Signal:**
+--
+-- * *property::width*
+--
+-- @property width
+-- @param width
+
+--- The height of the wibox.
+--
+-- **Signal:**
+--
+-- * *property::height*
+--
+-- @property height
+-- @param height
+
+-- Add the geometry helpers to match the wibox API
+for _, v in ipairs {"x", "y", "width", "height"} do
+ client.object["get_"..v] = function(c)
+ return c:geometry()[v]
+ end
+
+ client.object["set_"..v] = function(c, value)
+ return c:geometry({[v] = value})
+ end
+end
+
+
+--- Restore (=unminimize) a random client.
+-- @function awful.client.restore
+-- @param s The screen to use.
+-- @return The restored client if some client was restored, otherwise nil.
+function client.restore(s)
+ s = s or screen.focused()
+ local cls = capi.client.get(s)
+ local tags = s.selected_tags
+ for _, c in pairs(cls) do
+ local ctags = c:tags()
+ if c.minimized then
+ for _, t in ipairs(tags) do
+ if util.table.hasitem(ctags, t) then
+ c.minimized = false
+ return c
+ end
+ end
+ end
+ end
+ return nil
+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)
+ 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 _,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.
+--
+-- @legacylayout awful.client.idx
+-- @client 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 client.idx(c)
+ c = c or capi.client.focus
+ if not c then return end
+
+ -- Only check the tiled clients, the others un irrelevant
+ local clients = client.tiled(c.screen)
+ local idx = nil
+ for k, cl in ipairs(clients) do
+ if cl == c then
+ idx = k
+ break
+ end
+ end
+
+ local t = c.screen.selected_tag
+ local nmaster = t.master_count
+
+ -- This will happen for floating or maximized clients
+ if not idx then return nil end
+
+ 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 = t.column_count
+ -- minimum number of clients per column
+ local percol = math.floor(nother / ncol)
+ -- number of columns with an extra client
+ local overcol = math.fmod(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
+--
+-- @legacylayout awful.client.setwfact
+-- @param wfact the window factor value
+-- @client c the client
+function client.setwfact(wfact, c)
+ -- get the currently selected window
+ c = c or capi.client.focus
+ if not c or not c:isvisible() then return end
+
+ local w = client.idx(c)
+
+ if not w then return end
+
+ local t = c.screen.selected_tag
+
+ -- n is the number of windows currently visible for which we have to be concerned with the properties
+ local data = t.windowfact or {}
+ local colfact = data[w.col]
+
+ local need_normalize = colfact ~= nil
+
+ if not need_normalize then
+ colfact = {}
+ end
+
+ colfact[w.idx] = wfact
+
+ if not need_normalize then
+ t:emit_signal("property::windowfact")
+ return
+ end
+
+ local 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
+
+--- Change window factor of a client.
+--
+-- @legacylayout awful.client.incwfact
+-- @tparam number add Amount to increase/decrease the client's window factor.
+-- Should be between `-current_window_factor` and something close to
+-- infinite. The normalisation then ensures that the sum of all factors is 1.
+-- @client c the client
+function client.incwfact(add, c)
+ c = c or capi.client.focus
+ if not c then return end
+
+ local t = c.screen.selected_tag
+ local w = client.idx(c)
+ local data = t.windowfact or {}
+ local colfact = data[w.col] or {}
+ local 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.
+--
+-- @client 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"
+-- @deprecated awful.client.dockable.get
+function client.dockable.get(c)
+ util.deprecate("Use c.dockable instead of awful.client.dockable.get")
+ return client.object.get_dockable(c)
+end
+
+--- If the client is dockable.
+-- A dockable client is an application confined to the edge of the screen. The
+-- space it occupy is substracted from the `screen.workarea`.
+--
+-- **Signal:**
+--
+-- * *property::dockable*
+--
+-- @property dockable
+-- @param boolean The dockable state
+
+function client.object.get_dockable(c)
+ local value = client.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.
+--
+-- @client c A client.
+-- @param value True or false.
+-- @deprecated awful.client.dockable.set
+function client.dockable.set(c, value)
+ util.deprecate("Use c.dockable = value instead of awful.client.dockable.set")
+ client.property.set(c, "dockable", value)
+end
+
+--- Get a client property.
+--
+-- This method is deprecated. It is now possible to use `c.value` directly.
+--
+-- @client c The client.
+-- @param prop The property name.
+-- @return The property.
+-- @deprecated awful.client.property.get
+function client.property.get(c, prop)
+ if not c.data._persistent_properties_loaded then
+ c.data._persistent_properties_loaded = true
+ for p in pairs(client.data.persistent_properties_registered) do
+ local value = c:get_xproperty("awful.client.property." .. p)
+ if value ~= nil then
+ client.property.set(c, p, value)
+ end
+ end
+ end
+ if c.data.awful_client_properties then
+ return c.data.awful_client_properties[prop]
+ end
+end
+
+--- Set a client property.
+--
+-- This method is deprecated. It is now possible to use `c.value = value`
+-- directly.
+--
+-- @client c The client.
+-- @param prop The property name.
+-- @param value The value.
+-- @deprecated awful.client.property.set
+function client.property.set(c, prop, value)
+ if not c.data.awful_client_properties then
+ c.data.awful_client_properties = {}
+ end
+ if c.data.awful_client_properties[prop] ~= value then
+ if client.data.persistent_properties_registered[prop] then
+ c:set_xproperty("awful.client.property." .. prop, value)
+ end
+ c.data.awful_client_properties[prop] = value
+ c:emit_signal("property::" .. prop)
+ end
+end
+
+--- Set a client property to be persistent across restarts (via X properties).
+--
+-- @function awful.client.property.persist
+-- @param prop The property name.
+-- @param kind The type (used for register_xproperty).
+-- One of "string", "number" or "boolean".
+function client.property.persist(prop, kind)
+ local xprop = "awful.client.property." .. prop
+ capi.awesome.register_xproperty(xprop, kind)
+ client.data.persistent_properties_registered[prop] = true
+
+ -- Make already-set properties persistent
+ for c in pairs(capi.client.get()) do
+ if c.data.awful_client_properties and c.data.awful_client_properties[prop] ~= nil then
+ c:set_xproperty(xprop, c.data.awful_client_properties[prop])
+ end
+ end
+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.
+--
+-- @function awful.client.iterate
+-- @usage -- un-minimize all urxvt instances
+-- local urxvt = function (c)
+-- return awful.rules.match(c, {class = "URxvt"})
+-- end
+--
+-- for c in awful.client.iterate(urxvt) do
+-- c.minimized = false
+-- end
+function client.iterate(filter, start, s)
+ local clients = capi.client.get(s)
+ local focused = capi.client.focus
+ start = start or util.table.hasitem(clients, focused)
+ return util.table.iterate(clients, filter, start)
+end
+
+--- 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.
+--
+-- @param cmd the command to execute
+-- @param matcher a function that returns true to indicate a matching client
+-- @tparam bool|function merge If true then merge tags (select the client's
+-- first tag additionally) when the client is not visible.
+-- If it is a function, it will be called with the client as argument.
+--
+-- @function awful.client.run_or_raise
+-- @usage -- run or raise urxvt (perhaps, with tabs) on modkey + semicolon
+-- awful.key({ modkey, }, 'semicolon', function ()
+-- local matcher = function (c)
+-- return awful.rules.match(c, {class = 'URxvt'})
+-- end
+-- awful.client.run_or_raise('urxvt', matcher)
+-- end);
+function client.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)
+
+ local c = client.iterate(matcher, start)()
+ if c then
+ c:jump_to(merge)
+ else
+ -- client not found, spawn it
+ spawn(cmd)
+ end
+end
+
+--- Get a matching transient_for client (if any).
+-- @deprecated awful.client.get_transient_for_matching
+-- @see client.get_transient_for_matching
+-- @client c The client.
+-- @tparam function matcher A function that should return true, if
+-- a matching parent client is found.
+-- @treturn client.client|nil The matching parent client or nil.
+function client.get_transient_for_matching(c, matcher)
+ util.deprecate("Use c:get_transient_for_matching(matcher) instead of"..
+ "awful.client.get_transient_for_matching")
+
+ return client.object.get_transient_for_matching(c, matcher)
+end
+
+--- Get a matching transient_for client (if any).
+-- @function client.get_transient_for_matching
+-- @tparam function matcher A function that should return true, if
+-- a matching parent client is found.
+-- @treturn client.client|nil The matching parent client or nil.
+function client.object.get_transient_for_matching(self, matcher)
+ local tc = self.transient_for
+ while tc do
+ if matcher(tc) then
+ return tc
+ end
+ tc = tc.transient_for
+ end
+ return nil
+end
+
+--- Is a client transient for another one?
+-- @deprecated awful.client.is_transient_for
+-- @see client.is_transient_for
+-- @client c The child client (having transient_for).
+-- @client c2 The parent client to check.
+-- @treturn client.client|nil The parent client or nil.
+function client.is_transient_for(c, c2)
+ util.deprecate("Use c:is_transient_for(c2) instead of"..
+ "awful.client.is_transient_for")
+ return client.object.is_transient_for(c, c2)
+end
+
+--- Is a client transient for another one?
+-- @function client.is_transient_for
+-- @client c2 The parent client to check.
+-- @treturn client.client|nil The parent client or nil.
+function client.object.is_transient_for(self, c2)
+ local tc = self
+ while tc.transient_for do
+ if tc.transient_for == c2 then
+ return tc
+ end
+ tc = tc.transient_for
+ end
+ return nil
+end
+
+-- Register standards signals
+
+--- The last geometry when client was floating.
+-- @signal property::floating_geometry
+
+--- Emited when a client need to get a titlebar.
+-- @signal request::titlebars
+-- @tparam[opt=nil] string content The context (like "rules")
+-- @tparam[opt=nil] table hints Some hints.
+
+--- The client marked signal (deprecated).
+-- @signal .marked
+
+--- The client unmarked signal (deprecated).
+-- @signal unmarked
+
+-- Add clients during startup to focus history.
+-- This used to happen through ewmh.activate, but that only handles visible
+-- clients now.
+capi.client.connect_signal("manage", function (c)
+ if awesome.startup then
+ client.focus.history.add(c)
+ end
+end)
+capi.client.connect_signal("unmanage", client.focus.history.delete)
+
+capi.client.connect_signal("unmanage", client.floating.delete)
+
+-- Connect to "focus" signal, and allow to disable tracking.
+do
+ local disabled_count = 1
+ --- Disable history tracking.
+ --
+ -- See `awful.client.focus.history.enable_tracking` to enable it again.
+ -- @treturn int The internal value of `disabled_count` (calls to this
+ -- function without calling `awful.client.focus.history.enable_tracking`).
+ -- @function awful.client.focus.history.disable_tracking
+ function client.focus.history.disable_tracking()
+ disabled_count = disabled_count + 1
+ if disabled_count == 1 then
+ capi.client.disconnect_signal("focus", client.focus.history.add)
+ end
+ return disabled_count
+ end
+
+ --- Enable history tracking.
+ --
+ -- This is the default, but can be disabled
+ -- through `awful.client.focus.history.disable_tracking`.
+ -- @treturn boolean True if history tracking has been enabled.
+ -- @function awful.client.focus.history.enable_tracking
+ function client.focus.history.enable_tracking()
+ assert(disabled_count > 0)
+ disabled_count = disabled_count - 1
+ if disabled_count == 0 then
+ capi.client.connect_signal("focus", client.focus.history.add)
+ end
+ return disabled_count == 0
+ end
+
+ --- Is history tracking enabled?
+ -- @treturn bool True if history tracking is enabled.
+ -- @treturn int The number of times that tracking has been disabled.
+ -- @function awful.client.focus.history.is_enabled
+ function client.focus.history.is_enabled()
+ return disabled_count == 0, disabled_count
+ end
+end
+client.focus.history.enable_tracking()
+
+-- Register persistent properties
+client.property.persist("floating", "boolean")
+
+-- Extend the luaobject
+object.properties(capi.client, {
+ getter_class = client.object,
+ setter_class = client.object,
+ getter_fallback = client.property.get,
+ setter_fallback = client.property.set,
+})
+
+return client
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80