mirror of
https://github.com/awesomeWM/awesome
synced 2024-11-17 07:47:41 +01:00
9cb22b96eb
Signed-off-by: Julien Danjou <julien@danjou.info>
1238 lines
37 KiB
Lua
1238 lines
37 KiB
Lua
---------------------------------------------------------------------------
|
|
-- @author Julien Danjou <julien@danjou.info>
|
|
-- @copyright 2008 Julien Danjou
|
|
---------------------------------------------------------------------------
|
|
|
|
-- Grab environment we need
|
|
local type = type
|
|
local string = string
|
|
local assert = assert
|
|
local loadstring = loadstring
|
|
local ipairs = ipairs
|
|
local pairs = pairs
|
|
local unpack = unpack
|
|
local os = os
|
|
local io = io
|
|
local math = math
|
|
local setmetatable = setmetatable
|
|
local table = table
|
|
local otable = otable
|
|
local capi =
|
|
{
|
|
screen = screen,
|
|
client = client,
|
|
tag = tag,
|
|
mouse = mouse,
|
|
titlebar = titlebar,
|
|
widget = widget,
|
|
hooks = hooks,
|
|
keygrabber = keygrabber
|
|
}
|
|
|
|
--- awful: AWesome Functions very UsefuL
|
|
module("awful")
|
|
|
|
-- Local variable handling theme
|
|
local theme = {}
|
|
|
|
-- Various public structures
|
|
beautiful = {}
|
|
hooks = {}
|
|
hooks.user = {}
|
|
prompt = {}
|
|
completion = {}
|
|
screen = {}
|
|
layout = {}
|
|
client = {}
|
|
client.focus = {}
|
|
client.focus.history = {}
|
|
client.focus.history.data = {}
|
|
tag = {}
|
|
tag.history = {}
|
|
tag.history.data = {}
|
|
tag.history.data.past = {}
|
|
tag.history.data.current = {}
|
|
titlebar = {}
|
|
titlebar.data = otable()
|
|
widget = {}
|
|
widget.taglist = {}
|
|
widget.taglist.label = {}
|
|
widget.tasklist = {}
|
|
widget.tasklist.label = {}
|
|
|
|
--- Make i cycle.
|
|
-- @param t A length.
|
|
-- @param i An absolute index to fit into #t.
|
|
-- @return The object at new index.
|
|
local function cycle(t, i)
|
|
while i > t do i = i - t end
|
|
while i < 1 do i = i + t end
|
|
return i
|
|
end
|
|
|
|
--- Remove a client from the focus history
|
|
-- @param c The client that must be removed.
|
|
function client.focus.history.delete(c)
|
|
for k, v in ipairs(client.focus.history.data) do
|
|
if v == c then
|
|
table.remove(client.focus.history.data, k)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Update client focus history.
|
|
-- @param c The client that has been focused.
|
|
function client.focus.history.add(c)
|
|
-- Remove the client if its in stack
|
|
client.focus.history.delete(c)
|
|
-- Record the client has latest focused
|
|
table.insert(client.focus.history.data, 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 client.focus.history.get(screen, idx)
|
|
-- When this counter is equal to idx, we return the client
|
|
local counter = 0
|
|
local vc = capi.client.visible_get(screen)
|
|
for k, c in ipairs(client.focus.history.data) 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 client.focus.history.previous()
|
|
local sel = capi.client.focus_get()
|
|
local s
|
|
if sel then
|
|
s = sel.screen
|
|
else
|
|
s = capi.mouse.screen
|
|
end
|
|
local c = client.focus.history.get(s, 1)
|
|
if c then c:focus_set() end
|
|
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 client.next(i, c)
|
|
-- Get currently focused client
|
|
local sel = c or capi.client.focus_get()
|
|
if sel then
|
|
-- Get all visible clients
|
|
local cls = capi.client.visible_get(sel.screen)
|
|
-- Loop upon each client
|
|
for idx, c in ipairs(cls) do
|
|
if c == sel then
|
|
-- Cycle
|
|
return cls[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
|
|
|
|
--- Focus a client by the given direction.
|
|
-- @param dir The direction, can be either "up", "down", "left" or "right".
|
|
-- @param c Optional client.
|
|
function client.focusbydirection(dir, c)
|
|
local sel = c or capi.client.focus_get()
|
|
if sel then
|
|
local coords = sel.coords
|
|
local dist, dist_min
|
|
local target = nil
|
|
local cls = capi.client.visible_get(sel.screen)
|
|
|
|
-- We check each client.
|
|
for i, c in ipairs(cls) do
|
|
-- Check coords to see if client is located in the right direction.
|
|
if is_in_direction(dir, coords, c.coords) then
|
|
|
|
-- Calculate distance between focused client and checked client.
|
|
dist = calculate_distance(dir, coords, c.coords)
|
|
|
|
-- If distance is shorter then keep the client.
|
|
if not target or dist < dist_min then
|
|
target = c
|
|
dist_min = dist
|
|
end
|
|
end
|
|
end
|
|
|
|
-- If we found a client to focus, then do it.
|
|
if target then
|
|
target:focus_set()
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Focus a client by its relative index.
|
|
-- @param i The index.
|
|
-- @param c Optional client.
|
|
function client.focusbyidx(i, c)
|
|
local target = client.next(i, c)
|
|
if target then
|
|
target:focus_set()
|
|
end
|
|
end
|
|
|
|
--- Swap a client by its relative index.
|
|
-- @param i The index.
|
|
-- @param c Optional client, otherwise focused one is used.
|
|
function client.swap(i, c)
|
|
local sel = c or capi.client.focus_get()
|
|
local target = client.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 client.master(screen)
|
|
local s = screen or capi.mouse.screen
|
|
return capi.client.visible_get(s)[1]
|
|
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 client.moveresize(x, y, w, h, c)
|
|
local sel = c or capi.client.focus_get()
|
|
local coords = sel.coords
|
|
coords['x'] = coords['x'] + x
|
|
coords['y'] = coords['y'] + y
|
|
coords['width'] = coords['width'] + w
|
|
coords['height'] = coords['height'] + h
|
|
sel.coords = coords
|
|
end
|
|
|
|
--- Maximize a client to use the full workspace area.
|
|
-- @param c A client, or the focused one if nil.
|
|
function client.maximize(c)
|
|
local sel = c or capi.client.focus_get()
|
|
if sel then
|
|
sel.floating = true
|
|
local ws = capi.screen.workspace_get(sel.screen)
|
|
ws.width = ws.width - 2 * sel.border_width
|
|
ws.height = ws.height - 2 * sel.border_width
|
|
sel.coords = ws
|
|
end
|
|
end
|
|
|
|
--- Give the focus to a screen, and move pointer.
|
|
-- @param Screen number.
|
|
function screen.focus(i)
|
|
local sel = capi.client.focus_get()
|
|
local s
|
|
if sel then
|
|
s = sel.screen
|
|
else
|
|
s = capi.mouse.screen
|
|
end
|
|
s = cycle(capi.screen.count(), s + i)
|
|
local c = client.focus.history.get(s, 0)
|
|
if c then c:focus_set() end
|
|
-- Move the mouse on the screen
|
|
capi.mouse.coords = capi.screen.coords_get(s)
|
|
end
|
|
|
|
--- Compare 2 tables of tags.
|
|
-- @param a The first table.
|
|
-- @param b The second table of tags.
|
|
-- @return True if the tables are identical, false otherwise.
|
|
local function tag_compare_select(a, b)
|
|
if not a or not b then
|
|
return false
|
|
end
|
|
-- Quick size comparison
|
|
if #a ~= #b then
|
|
return false
|
|
end
|
|
for ka, va in pairs(a) do
|
|
if b[ka] ~= va.selected then
|
|
return false
|
|
end
|
|
end
|
|
for kb, vb in pairs(b) do
|
|
if a[kb].selected ~= vb then
|
|
return false
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
--- Update the tag history.
|
|
-- @param screen The screen number.
|
|
function tag.history.update(screen)
|
|
local curtags = capi.tag.geti(screen)
|
|
if not tag_compare_select(curtags, tag.history.data.current[screen]) then
|
|
tag.history.data.past[screen] = tag.history.data.current[screen]
|
|
tag.history.data.current[screen] = {}
|
|
for k, v in ipairs(curtags) do
|
|
tag.history.data.current[screen][k] = v.selected
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Revert tag history.
|
|
-- @param screen The screen number.
|
|
function tag.history.restore(screen)
|
|
local s = screen or capi.mouse.screen
|
|
local tags = capi.tag.geti(s)
|
|
for k, t in pairs(tags) do
|
|
t.selected = tag.history.data.past[s][k]
|
|
end
|
|
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 = capi.tag.geti(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.
|
|
function tag.setmwfact(mwfact)
|
|
local t = tag.selected()
|
|
if t then
|
|
t.mwfact = mwfact
|
|
end
|
|
end
|
|
|
|
--- Increase master width factor.
|
|
-- @param add Value to add to master width factor.
|
|
function tag.incmwfact(add)
|
|
local t = tag.selected()
|
|
if t then
|
|
t.mwfact = t.mwfact + add
|
|
end
|
|
end
|
|
|
|
--- Set the number of master windows.
|
|
-- @param nmaster The number of master windows.
|
|
function tag.setnmaster(nmaster)
|
|
local t = tag.selected()
|
|
if t then
|
|
t.nmaster = nmaster
|
|
end
|
|
end
|
|
|
|
--- Increase the number of master windows.
|
|
-- @param add Value to add to number of master windows.
|
|
function tag.incnmaster(add)
|
|
local t = tag.selected()
|
|
if t then
|
|
t.nmaster = t.nmaster + add
|
|
end
|
|
end
|
|
|
|
--- Set number of column windows.
|
|
-- @param ncol The number of column.
|
|
function tag.setncol(ncol)
|
|
local t = tag.selected()
|
|
if t then
|
|
t.ncol = ncol
|
|
end
|
|
end
|
|
|
|
--- Increase number of column windows.
|
|
-- @param add Value to add to number of column windows.
|
|
function tag.incncol(add)
|
|
local t = tag.selected()
|
|
if t then
|
|
t.ncol = t.ncol + add
|
|
end
|
|
end
|
|
|
|
--- View no tag.
|
|
-- @param Optional screen number.
|
|
function tag.viewnone(screen)
|
|
local tags = capi.tag.get(screen or capi.mouse.screen)
|
|
for i, t in pairs(tags) do
|
|
t.selected = false
|
|
end
|
|
end
|
|
|
|
--- View a tag by its index.
|
|
-- @param i The relative index to see.
|
|
-- @param screen Optional screen number.
|
|
function tag.viewidx(i, screen)
|
|
local tags = capi.tag.geti(screen or capi.mouse.screen)
|
|
local sel = tag.selected()
|
|
tag.viewnone()
|
|
for k, t in ipairs(tags) do
|
|
if t == sel then
|
|
tags[cycle(#tags, k + i)].selected = true
|
|
end
|
|
end
|
|
end
|
|
|
|
--- View next tag. This is the same as tag.viewidx(1).
|
|
function tag.viewnext()
|
|
return tag.viewidx(1)
|
|
end
|
|
|
|
--- View previous tag. This is the same a tag.viewidx(-1).
|
|
function tag.viewprev()
|
|
return tag.viewidx(-1)
|
|
end
|
|
|
|
--- View only a tag.
|
|
-- @param t The tag object.
|
|
function tag.viewonly(t)
|
|
tag.viewnone()
|
|
t.selected = true
|
|
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)
|
|
tag.viewnone(screen)
|
|
for i, t in pairs(tags) do
|
|
t.selected = true
|
|
end
|
|
end
|
|
|
|
--- Move a client to a tag.
|
|
-- @param target The tag to move the client to.
|
|
-- @para c Optional client to move, otherwise the focused one is used.
|
|
function client.movetotag(target, c)
|
|
local sel = c or capi.client.focus_get();
|
|
-- Check that tag and client screen are identical
|
|
if sel.screen ~= target.screen then return end
|
|
local tags = { target }
|
|
sel.tags = tags
|
|
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 client.toggletag(target, c)
|
|
local sel = c or capi.client.focus_get();
|
|
-- Check that tag and client screen are identical
|
|
if sel and sel.screen == target.screen then
|
|
local tags = client.tags
|
|
if tags[target] then
|
|
tags[target] = nil
|
|
else
|
|
tags[target] = target
|
|
end
|
|
sel.tags = tags
|
|
end
|
|
end
|
|
|
|
--- Toggle the floating status of a client.
|
|
-- @param c Optional client, the focused on if not set.
|
|
function client.togglefloating(c)
|
|
local sel = c or capi.client.focus_get();
|
|
if sel then
|
|
sel.floating = not sel.floating
|
|
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 client.movetoscreen(c, s)
|
|
local sel = c or capi.client.focus_get();
|
|
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.coords_get(s)
|
|
sel:focus_set()
|
|
end
|
|
end
|
|
|
|
--- Get the current layout name.
|
|
-- @param screen The screen number.
|
|
function layout.get(screen)
|
|
local t = tag.selected(screen)
|
|
if t then
|
|
return t.layout
|
|
end
|
|
end
|
|
|
|
--- Create a new userhook (for external libs).
|
|
-- @param name Hook name.
|
|
function hooks.user.create(name)
|
|
hooks[name] = {}
|
|
hooks[name].callbacks = {}
|
|
hooks[name].register = function (f)
|
|
table.insert(hooks[name].callbacks, f)
|
|
end
|
|
hooks[name].unregister = function (f)
|
|
for k, h in ipairs(hooks[name].callbacks) do
|
|
if h == f then
|
|
table.remove(hooks[name].callbacks, k)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Call a created userhook (for external libs).
|
|
-- @param name Hook name.
|
|
function hooks.user.call(name, ...)
|
|
for name, callback in pairs(hooks[name].callbacks) do
|
|
callback(unpack(args))
|
|
end
|
|
end
|
|
|
|
-- Just set an awful mark to a client to move it later.
|
|
local awfulmarked = {}
|
|
hooks.user.create('marked')
|
|
hooks.user.create('unmarked')
|
|
|
|
--- 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 client.mark (c)
|
|
local cl = c or capi.client.focus_get()
|
|
if cl then
|
|
for k, v in pairs(awfulmarked) do
|
|
if cl == v then
|
|
return false
|
|
end
|
|
end
|
|
|
|
table.insert(awfulmarked, 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 client.unmark(c)
|
|
local cl = c or capi.client.focus_get()
|
|
|
|
for k, v in pairs(awfulmarked) do
|
|
if cl == v then
|
|
table.remove(awfulmarked, 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 client.ismarked(c)
|
|
local cl = c or capi.client.focus_get()
|
|
if cl then
|
|
for k, v in pairs(awfulmarked) 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 client.togglemarked(c)
|
|
local cl = c or capi.client.focus_get()
|
|
|
|
if not client.mark(c) then
|
|
client.unmark(c)
|
|
end
|
|
end
|
|
|
|
--- Return the marked clients and empty the marked table.
|
|
-- @return A table with all marked clients.
|
|
function client.getmarked()
|
|
for k, v in pairs(awfulmarked) do
|
|
hooks.user.call('unmarked', v)
|
|
end
|
|
|
|
t = awfulmarked
|
|
awfulmarked = {}
|
|
return t
|
|
end
|
|
|
|
--- Change the layout of the current tag.
|
|
-- @param layouts A table of layouts.
|
|
-- @param i Relative index.
|
|
function layout.inc(layouts, i)
|
|
local t = tag.selected()
|
|
local number_of_layouts = 0
|
|
local rev_layouts = {}
|
|
for i, v in ipairs(layouts) do
|
|
rev_layouts[v] = i
|
|
number_of_layouts = number_of_layouts + 1
|
|
end
|
|
if t then
|
|
local cur_layout = layout.get()
|
|
local new_layout_index = (rev_layouts[cur_layout] + i) % number_of_layouts
|
|
if new_layout_index == 0 then
|
|
new_layout_index = number_of_layouts
|
|
end
|
|
t.layout = layouts[new_layout_index]
|
|
end
|
|
end
|
|
|
|
--- Set the layout of the current tag by name.
|
|
-- @param layout Layout name.
|
|
function layout.set(layout)
|
|
local t = tag.selected()
|
|
if t then
|
|
t.layout = layout
|
|
end
|
|
end
|
|
|
|
-- Autodeclare awful.hooks.* functions
|
|
-- mapped to awesome hooks.* functions
|
|
for name, hook in pairs(capi.hooks) do
|
|
if name ~= 'timer' then
|
|
hooks[name] = {}
|
|
hooks[name].register = function (f)
|
|
if not hooks[name].callbacks then
|
|
hooks[name].callbacks = {}
|
|
hook(function (...)
|
|
for i, callback in ipairs(hooks[name].callbacks) do
|
|
callback(...)
|
|
end
|
|
end)
|
|
end
|
|
|
|
table.insert(hooks[name].callbacks, f)
|
|
end
|
|
hooks[name].unregister = function (f)
|
|
if hooks[name].callbacks then
|
|
for k, h in ipairs(hooks[name].callbacks) do
|
|
if h == f then
|
|
table.remove(hooks[name].callbacks, k)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
else
|
|
hooks[name] = {}
|
|
hooks[name].register = function (time, f, runnow)
|
|
if type(time) ~= 'number' or type(f) ~= 'function' then
|
|
return
|
|
end
|
|
if not hooks[name].callbacks then
|
|
hooks[name].callbacks = {}
|
|
hook(1, function (...)
|
|
for i, callback in ipairs(hooks[name].callbacks) do
|
|
if callback['counter'] >= callback['timer'] then
|
|
callback['counter'] = 1
|
|
callback['callback'](...)
|
|
else
|
|
callback['counter'] = callback['counter'] + 1
|
|
end
|
|
end
|
|
end)
|
|
end
|
|
|
|
if runnow then
|
|
table.insert(hooks[name].callbacks, { callback = f, timer = time, counter = time })
|
|
else
|
|
table.insert(hooks[name].callbacks, { callback = f, timer = time, counter = 0 })
|
|
end
|
|
end
|
|
hooks[name].unregister = function (f)
|
|
if hooks[name].callbacks then
|
|
for k, h in ipairs(hooks[name].callbacks) do
|
|
if h.callback == f then
|
|
table.remove(hooks[name].callbacks, k)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Spawn a program.
|
|
-- @param cmd The command.
|
|
-- @return The os.execute() return value.
|
|
function spawn(cmd)
|
|
return os.execute(cmd .. "&")
|
|
end
|
|
|
|
--- Eval Lua code.
|
|
-- @return The return value of Lua code.
|
|
function eval(s)
|
|
return assert(loadstring("return " .. s))()
|
|
end
|
|
|
|
--- Use bash completion system to complete command and filename.
|
|
-- @param command The command line.
|
|
-- @param cur_pos The cursor position.
|
|
-- @paran ncomp The element number to complete.
|
|
-- @return The new commande and the new cursor position.
|
|
function completion.bash(command, cur_pos, ncomp)
|
|
local wstart = 1
|
|
local wend = 1
|
|
local words = {}
|
|
local cword_index = 0
|
|
local cword_start = 0
|
|
local cword_end = 0
|
|
local i = 1
|
|
local comptype = "file"
|
|
|
|
-- do nothing if we are on a letter, i.e. not at len + 1 or on a space
|
|
if cur_pos ~= #command + 1 and command:sub(cur_pos, cur_pos) ~= " " then
|
|
return command, cur_pos
|
|
elseif #command == 0 then
|
|
return command, cur_pos
|
|
end
|
|
|
|
while wend <= #command do
|
|
wend = command:find(" ", wstart)
|
|
if not wend then wend = #command + 1 end
|
|
table.insert(words, command:sub(wstart, wend - 1))
|
|
if cur_pos >= wstart and cur_pos <= wend + 1 then
|
|
cword_start = wstart
|
|
cword_end = wend
|
|
cword_index = i
|
|
end
|
|
wstart = wend + 1
|
|
i = i + 1
|
|
end
|
|
|
|
if cword_index == 1 then
|
|
comptype = "command"
|
|
end
|
|
|
|
local c = io.popen("/usr/bin/env bash -c 'compgen -A " .. comptype .. " " .. words[cword_index] .. "'")
|
|
local output = {}
|
|
i = 0
|
|
while true do
|
|
local line = c:read("*line")
|
|
if not line then break end
|
|
table.insert(output, line)
|
|
end
|
|
|
|
c:close()
|
|
|
|
-- no completion, return
|
|
if #output == 0 then
|
|
return command, cur_pos
|
|
end
|
|
|
|
-- cycle
|
|
while ncomp > #output do
|
|
ncomp = ncomp - #output
|
|
end
|
|
|
|
local str = command:sub(1, cword_start - 1) .. output[ncomp] .. command:sub(cword_end)
|
|
cur_pos = cword_end + #output[ncomp] + 1
|
|
|
|
return str, cur_pos
|
|
end
|
|
|
|
--- Draw the prompt text with a cursor.
|
|
-- @param text The text.
|
|
-- @param text_color The text color.
|
|
-- @param cursor_color The cursor color.
|
|
-- @param cursor_pos The cursor position.
|
|
local function prompt_text_with_cursor(text, text_color, cursor_color, cursor_pos)
|
|
local char
|
|
if not text then text = "" end
|
|
if #text < cursor_pos then
|
|
char = " "
|
|
else
|
|
char = escape(text:sub(cursor_pos, cursor_pos))
|
|
end
|
|
local text_start = escape(text:sub(1, cursor_pos - 1))
|
|
local text_end = escape(text:sub(cursor_pos + 1))
|
|
return text_start .. "<span background=\"" .. cursor_color .. "\" foreground=\"" .. text_color .. "\">" .. char .. "</span>" .. text_end
|
|
end
|
|
|
|
--- Run a prompt in a box.
|
|
-- @param args A table with optional arguments: fg_cursor, bg_cursor, prompt.
|
|
-- @param textbox The textbox to use for the prompt.
|
|
-- @param exe_callback The callback function to call with command as argument when finished.
|
|
-- @param completion_callback The callback function to call to get completion.
|
|
function prompt(args, textbox, exe_callback, completion_callback)
|
|
if not args then args = {} end
|
|
local command = ""
|
|
local command_before_comp
|
|
local cur_pos_before_comp
|
|
local prompt = args.prompt or ""
|
|
local inv_col = args.fg_cursor or theme.fg_focus or "black"
|
|
local cur_col = args.bg_cursor or theme.bg_focus or "white"
|
|
-- The cursor position
|
|
local cur_pos = 1
|
|
-- The completion element to use on completion request.
|
|
local ncomp = 1
|
|
if not textbox or not exe_callback then
|
|
return
|
|
end
|
|
textbox.text = prompt .. prompt_text_with_cursor(text, inv_col, cur_col, cur_pos)
|
|
capi.keygrabber.run(
|
|
function (mod, key)
|
|
local has_ctrl = false
|
|
|
|
-- Compute modifiers keys
|
|
for k, v in ipairs(mod) do
|
|
if v == "Control" then has_ctrl = true end
|
|
end
|
|
|
|
-- Get out cases
|
|
if has_ctrl then
|
|
if key == "c" or key == "g" then
|
|
textbox.text = ""
|
|
return false
|
|
elseif key == "j" or key == "m" then
|
|
textbox.text = ""
|
|
exec_callback(command)
|
|
return false
|
|
end
|
|
else
|
|
if key == "Return" then
|
|
textbox.text = ""
|
|
exe_callback(command)
|
|
return false
|
|
elseif key == "Escape" then
|
|
textbox.text = ""
|
|
return false
|
|
end
|
|
end
|
|
|
|
-- Control cases
|
|
if has_ctrl then
|
|
if key == "a" then
|
|
cur_pos = 1
|
|
elseif key == "b" then
|
|
if cur_pos > 1 then
|
|
cur_pos = cur_pos - 1
|
|
end
|
|
elseif key == "d" then
|
|
if cur_pos <= #command then
|
|
command = command:sub(1, cur_pos - 1) .. command:sub(cur_pos + 1)
|
|
end
|
|
elseif key == "e" then
|
|
cur_pos = #command + 1
|
|
elseif key == "f" then
|
|
if cur_pos <= #command then
|
|
cur_pos = cur_pos + 1
|
|
end
|
|
elseif key == "h" then
|
|
if cur_pos > 1 then
|
|
command = command:sub(1, cur_pos - 2) .. command:sub(cur_pos)
|
|
cur_pos = cur_pos - 1
|
|
end
|
|
elseif key == "k" then
|
|
command = command:sub(1, cur_pos - 1)
|
|
elseif key == "u" then
|
|
command = command:sub(cur_pos, #command)
|
|
cur_pos = 1
|
|
elseif key == "w" then
|
|
local wstart = 1
|
|
local wend = 1
|
|
local cword_start = 1
|
|
local cword_end = 1
|
|
while wend < cur_pos do
|
|
wend = command:find(" ", wstart)
|
|
if not wend then wend = #command + 1 end
|
|
if cur_pos >= wstart and cur_pos <= wend + 1 then
|
|
cword_start = wstart
|
|
cword_end = cur_pos - 1
|
|
break
|
|
end
|
|
wstart = wend + 1
|
|
end
|
|
command = command:sub(1, cword_start - 1) .. command:sub(cword_end + 1)
|
|
cur_pos = cword_start
|
|
end
|
|
else
|
|
if completion_callback then
|
|
-- That's tab
|
|
if key:byte() == 9 then
|
|
if ncomp == 1 then
|
|
command_before_comp = command
|
|
cur_pos_before_comp = cur_pos
|
|
end
|
|
command, cur_pos = completion_callback(command_before_comp, cur_pos_before_comp, ncomp)
|
|
ncomp = ncomp + 1
|
|
key = ""
|
|
else
|
|
ncomp = 1
|
|
end
|
|
end
|
|
|
|
-- Typin cases
|
|
if key == "Home" then
|
|
cur_pos = 1
|
|
elseif key == "End" then
|
|
cur_pos = #command + 1
|
|
elseif key == "BackSpace" then
|
|
if cur_pos > 1 then
|
|
command = command:sub(1, cur_pos - 2) .. command:sub(cur_pos)
|
|
cur_pos = cur_pos - 1
|
|
end
|
|
-- That's DEL
|
|
elseif key:byte() == 127 then
|
|
command = command:sub(1, cur_pos - 1) .. command:sub(cur_pos + 1)
|
|
elseif key == "Left" then
|
|
cur_pos = cur_pos - 1
|
|
elseif key == "Right" then
|
|
cur_pos = cur_pos + 1
|
|
else
|
|
-- len() is UTF-8 aware but #key is not,
|
|
-- so check that we have one UTF-8 char but advance the cursor of # position
|
|
if key:len() == 1 then
|
|
command = command:sub(1, cur_pos - 1) .. key .. command:sub(cur_pos)
|
|
cur_pos = cur_pos + #key
|
|
end
|
|
end
|
|
if cur_pos < 1 then
|
|
cur_pos = 1
|
|
elseif cur_pos > #command + 1 then
|
|
cur_pos = #command + 1
|
|
end
|
|
end
|
|
|
|
-- Update textbox
|
|
textbox.text = prompt .. prompt_text_with_cursor(command, inv_col, cur_col, cur_pos)
|
|
|
|
return true
|
|
end)
|
|
end
|
|
|
|
--- Escape a string from XML char.
|
|
-- Useful to set raw text in textbox.
|
|
-- @param text Text to escape.
|
|
-- @return Escape text.
|
|
function escape(text)
|
|
if text then
|
|
text = text:gsub("&", "&")
|
|
text = text:gsub("<", "<")
|
|
text = text:gsub(">", ">")
|
|
text = text:gsub("'", "'")
|
|
text = text:gsub("\"", """)
|
|
end
|
|
return text
|
|
end
|
|
|
|
--- Unescape a string from entities.
|
|
-- @param text Text to unescape.
|
|
-- @return Unescaped text.
|
|
function unescape(text)
|
|
if text then
|
|
text = text:gsub("&", "&")
|
|
text = text:gsub("<", "<")
|
|
text = text:gsub(">", ">")
|
|
text = text:gsub("'", "'")
|
|
text = text:gsub(""", "\"")
|
|
end
|
|
return text
|
|
end
|
|
|
|
--- Return labels for a taglist widget with all tag from screen.
|
|
-- It returns the tag name and set a special
|
|
-- foreground and background color for selected tags.
|
|
-- @param t The tag.
|
|
-- @param args The arguments table.
|
|
-- bg_focus The background color for selected tag.
|
|
-- fg_focus The foreground color for selected tag.
|
|
-- bg_urgent The background color for urgent tags.
|
|
-- fg_urgent The foreground color for urgent tags.
|
|
-- @return A string to print.
|
|
function widget.taglist.label.all(t, args)
|
|
if not args then args = {} end
|
|
local fg_focus = args.fg_focus or theme.fg_focus
|
|
local bg_focus = args.bg_focus or theme.bg_focus
|
|
local fg_urgent = args.fg_urgent or theme.fg_urgent
|
|
local bg_urgent = args.bg_urgent or theme.bg_urgent
|
|
local text
|
|
local background = ""
|
|
local sel = capi.client.focus_get()
|
|
local bg_color = nil
|
|
local fg_color = nil
|
|
if t.selected then
|
|
bg_color = bg_focus
|
|
fg_color = fg_focus
|
|
end
|
|
if sel and sel.tags[t] then
|
|
background = "resize=\"true\" image=\"@AWESOME_ICON_PATH@/taglist/squarefw.png\""
|
|
elseif bg_urgent and fg_urgent then
|
|
for k, c in pairs(capi.client.get()) do
|
|
if c.tags[t] then
|
|
background = "resize=\"true\" image=\"@AWESOME_ICON_PATH@/taglist/squarew.png\""
|
|
if c.urgent then
|
|
bg_color = bg_urgent
|
|
fg_color = fg_urgent
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if bg_color and fg_color then
|
|
text = "<bg "..background.." color='"..bg_color.."'/> <span color='"..fg_color.."'>"..escape(t.name).."</span> "
|
|
else
|
|
text = " <bg "..background.." />"..escape(t.name).." "
|
|
end
|
|
return text
|
|
end
|
|
|
|
local function widget_tasklist_label_common(c, args)
|
|
if not args then args = {} end
|
|
local fg_focus = args.fg_focus or theme.fg_focus
|
|
local bg_focus = args.bg_focus or theme.bg_focus
|
|
local fg_urgent = args.fg_urgent or theme.fg_urgent
|
|
local bg_urgent = args.bg_urgent or theme.bg_urgent
|
|
local text = ""
|
|
if c.floating then
|
|
text = "<bg image=\"@AWESOME_ICON_PATH@/tasklist/floatingw.png\" align=\"right\"/>"
|
|
end
|
|
if capi.client.focus_get() == c then
|
|
text = text .. " <bg color='"..bg_focus.."'/><span color='"..fg_focus.."'>"..escape(c.name).."</span> "
|
|
elseif c.urgent and bg_urgent and fg_urgent then
|
|
text = text .. " <bg color='"..bg_urgent.."'/><span color='"..fg_urgent.."'>"..escape(c.name).."</span> "
|
|
else
|
|
text = text .. " "..escape(c.name).." "
|
|
end
|
|
return text
|
|
end
|
|
|
|
--- Return labels for a tasklist widget with clients from all tags and screen.
|
|
-- It returns the client name and set a special
|
|
-- foreground and background color for focused client.
|
|
-- It also puts a special icon for floating windows.
|
|
-- @param c The client.
|
|
-- @param screen The screen we are drawing on.
|
|
-- @param args The arguments table.
|
|
-- bg_focus The background color for focused client.
|
|
-- fg_focus The foreground color for focused client.
|
|
-- bg_urgent The background color for urgent clients.
|
|
-- fg_urgent The foreground color for urgent clients.
|
|
-- @return A string to print.
|
|
function widget.tasklist.label.allscreen(c, screen, args)
|
|
return widget_tasklist_label_common(c, args)
|
|
end
|
|
|
|
--- Return labels for a tasklist widget with clients from all tags.
|
|
-- It returns the client name and set a special
|
|
-- foreground and background color for focused client.
|
|
-- It also puts a special icon for floating windows.
|
|
-- @param c The client.
|
|
-- @param screen The screen we are drawing on.
|
|
-- @param args The arguments table.
|
|
-- bg_focus The background color for focused client.
|
|
-- fg_focus The foreground color for focused client.
|
|
-- bg_urgent The background color for urgent clients.
|
|
-- fg_urgent The foreground color for urgent clients.
|
|
-- @return A string to print.
|
|
function widget.tasklist.label.alltags(c, screen, args)
|
|
-- Only print client on the same screen as this widget
|
|
if c.screen ~= screen then return end
|
|
return widget_tasklist_label_common(c, args)
|
|
end
|
|
|
|
--- Return labels for a tasklist widget with clients from currently selected tags.
|
|
-- It returns the client name and set a special
|
|
-- foreground and background color for focused client.
|
|
-- It also puts a special icon for floating windows.
|
|
-- @param c The client.
|
|
-- @param screen The screen we are drawing on.
|
|
-- @param args The arguments table.
|
|
-- bg_focus The background color for focused client.
|
|
-- fg_focus The foreground color for focused client.
|
|
-- bg_urgent The background color for urgent clients.
|
|
-- fg_urgent The foreground color for urgent clients.
|
|
-- @return A string to print.
|
|
function widget.tasklist.label.currenttags(c, screen, args)
|
|
-- Only print client on the same screen as this widget
|
|
if c.screen ~= screen then return end
|
|
for k, t in pairs(capi.tag.get(screen)) do
|
|
if t.selected and c.tags[t] then
|
|
return widget_tasklist_label_common(c, args)
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Create a standard titlebar.
|
|
-- @param c The client.
|
|
-- @param args Arguments.
|
|
-- fg: the foreground color.
|
|
-- bg: the background color.
|
|
-- fg_focus: the foreground color for focused window.
|
|
-- fg_focus: the background color for focused window.
|
|
function titlebar.add(c, args)
|
|
if not args then args = {} end
|
|
-- Store colors
|
|
titlebar.data[c] = {}
|
|
titlebar.data[c].fg = args.fg or theme.fg_normal
|
|
titlebar.data[c].bg = args.bg or theme.bg_normal
|
|
titlebar.data[c].fg_focus = args.fg_focus or theme.fg_focus
|
|
titlebar.data[c].bg_focus = args.bg_focus or theme.bg_focus
|
|
|
|
-- Built args
|
|
local targs = {}
|
|
if args.fg then targs.fg = args.fg end
|
|
if args.bg then targs.bg = args.bg end
|
|
local tb = capi.titlebar(targs)
|
|
|
|
tb:widget_add(capi.widget({ type = "appicon", name = "appicon", align = "left" }))
|
|
|
|
local title = capi.widget({ type = "textbox", name = "title", align = "flex" })
|
|
title:mouse_add(capi.mouse({ }, 1, function (t) t:client_get():mouse_move() end))
|
|
title:mouse_add(capi.mouse({ args.modkey }, 3, function (t) t:client_get():mouse_resize() end))
|
|
tb:widget_add(title)
|
|
|
|
local close_button= capi.widget({ type = "textbox", name = "close", align = "right" })
|
|
close_button:mouse_add(capi.mouse({ }, 1, function (t) t:client_get():kill() end))
|
|
tb:widget_add(close_button)
|
|
|
|
titlebar.update(c)
|
|
|
|
c.titlebar = tb
|
|
end
|
|
|
|
--- Update a titlebar. This should be called in some hooks.
|
|
-- @param c The client to update.
|
|
function titlebar.update(c)
|
|
if c.titlebar and titlebar.data[c] then
|
|
local widgets = c.titlebar:widget_get()
|
|
if widgets.title then
|
|
widgets.title.text = " " .. escape(c.name)
|
|
end
|
|
if capi.client.focus_get() == c then
|
|
c.titlebar.fg = titlebar.data[c].fg_focus
|
|
c.titlebar.bg = titlebar.data[c].bg_focus
|
|
if widgets.close then
|
|
widgets.close.text = "<bg image=\"@AWESOME_ICON_PATH@/titlebar/closer.png\" resize=\"true\"/>"
|
|
end
|
|
else
|
|
c.titlebar.fg = titlebar.data[c].fg
|
|
c.titlebar.bg = titlebar.data[c].bg
|
|
if widgets.close then
|
|
widgets.close.text = "<bg image=\"@AWESOME_ICON_PATH@/titlebar/close.png\" resize=\"true\"/>"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Remove a titlebar from a client.
|
|
-- @param c The client.
|
|
function titlebar.remove(c)
|
|
c.titlebar = nil
|
|
titlebar.data[c] = nil
|
|
end
|
|
|
|
--- Set the beautiful theme if any.
|
|
-- @param The beautiful theme.
|
|
function beautiful.register(btheme)
|
|
if btheme then
|
|
theme = btheme
|
|
else
|
|
theme = {}
|
|
end
|
|
end
|
|
|
|
-- Register standards hooks
|
|
hooks.arrange.register(tag.history.update)
|
|
|
|
hooks.focus.register(client.focus.history.add)
|
|
hooks.unmanage.register(client.focus.history.delete)
|
|
|
|
hooks.focus.register(titlebar.update)
|
|
hooks.unfocus.register(titlebar.update)
|
|
hooks.titleupdate.register(titlebar.update)
|
|
hooks.unmanage.register(titlebar.remove)
|
|
|
|
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80
|