----------------------------------------------- -- awful: AWesome Function very UsefuL -- -- Common useful awesome functions -- -- -- -- © 2008 Julien Danjou -- ----------------------------------------------- -- We usually are required as 'awful' -- But that can be changed. local P = {} -- package if _REQUIREDNAME == nil then awful = P else _G[_REQUIREDNAME] = P end -- Grab environment we need local string = string local assert = assert local loadstring = loadstring local ipairs = ipairs local pairs = pairs local unpack = unpack local awesome = awesome local screen = screen local client = client local tag = tag local mouse = mouse local os = os local table = table local hooks = hooks local keygrabber = keygrabber local io = io -- Reset env setfenv(1, P) P.hooks = {} P.myhooks = {} P.prompt = {} P.completion = {} P.screen = {} P.layout = {} P.client = {} P.tag = {} P.widget = {} P.widget.taglist = {} P.widget.tasklist = {} P.widget.tasklist.label = {} --- Create a new userhook (for external libs). -- @param name Hook name. local function userhook_create(name) P.myhooks[name] = {} P.hooks[name] = function (f) table.insert(P.myhooks[name], { callback = f }) end end --- Call a created userhook (for external libs). -- @param name Hook name. local function userhook_call(name, args) for i, o in pairs(P.myhooks[name]) do P.myhooks[name][i]['callback'](unpack(args)) end end --- 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 --- 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 P.client.next(i, c) -- Get currently focused client local sel = c or client.focus_get() if sel then -- Get all visible clients local cls = 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 --- Focus a client by its relative index. -- @param i The index. -- @param c Optional client. function P.client.focus(i, c) local target = P.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 P.client.swap(i, c) local sel = c or client.focus_get() local target = P.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 P.client.master(screen) local s = screen or mouse.screen return 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 P.client.moveresize(x, y, w, h, c) local sel = c or 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 --- Give the focus to a screen, and move pointer. -- @param Screen number. function P.screen.focus(i) local sel = client.focus_get() local s if sel then s = sel.screen else s = mouse.screen end s = cycle(screen.count(), s + i) screen.focus(s) -- Move the mouse on the screen mouse.coords = screen.coords_get(s) end --- Return a table with all visible tags -- @param s Screen number. -- @return A table with all selected tags. function P.tag.selectedlist(s) local screen = s or mouse.screen local tags = 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 P.tag.selected(s) return P.tag.selectedlist(s)[1] end --- Set master width factor. -- @param mwfact Master width factor. function P.tag.setmwfact(mwfact) local t = P.tag.selected() if t then t.mwfact = mwfact end end --- Increase master width factor. -- @param add Value to add to master width factor. function P.tag.incmwfact(add) local t = P.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 P.tag.setnmaster(nmaster) local t = P.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 P.tag.incnmaster(add) local t = P.tag.selected() if t then t.nmaster = t.nmaster + add end end --- Set number of column windows. -- @param ncol The number of column. function P.tag.setncol(ncol) local t = P.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 P.tag.incncol(add) local t = P.tag.selected() if t then t.ncol = t.ncol + add end end --- View no tag. -- @param Optional screen number. function P.tag.viewnone(screen) local tags = tag.get(screen or 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 P.tag.viewidx(i, screen) local tags = tag.geti(screen or mouse.screen) local sel = P.tag.selected() P.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 P.tag.viewnext() return P.tag.viewidx(1) end --- View previous tag. This is the same a tag.viewidx(-1). function P.tag.viewprev() return P.tag.viewidx(-1) end --- View only a tag. -- @param t The tag object. function P.tag.viewonly(t) P.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 P.tag.viewmore(tags, screen) P.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 P.client.movetotag(target, c) local sel = c or client.focus_get(); -- Check that tag and client screen are identical if sel.screen ~= target.screen then return end local tags = tag.get(sel.screen) for k, t in pairs(tags) do sel:tag(t, false) end sel:tag(target, true) 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 P.client.toggletag(target, c) local sel = c or client.focus_get(); -- Check that tag and client screen are identical if sel.screen ~= target.screen then return end local toggle = false if sel then -- Count how many tags has the client -- an only toggle tag if the client has at least one tag other than target for k, v in pairs(tag.get(sel.screen)) do if target ~= v and sel:istagged(v) then toggle = true break end end if toggle then sel:tag(target, not sel:istagged(target)) end end end --- Toggle the floating status of a client. -- @param c Optional client, the focused on if not set. function P.client.togglefloating(c) local sel = c or 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 P.client.movetoscreen(c, s) local sel = c or client.focus_get(); if sel then local sc = 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 mouse.coords = screen.coords_get(s) sel:focus_set() end end --- Get the current layout name. -- @param screen The screen number. function P.layout.get(screen) local t = P.tag.selected(screen) if t then return t.layout end end -- Just set an awful mark to a client to move it later. local awfulmarked = {} userhook_create('marked') userhook_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 P.client.mark (c) local cl = c or 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 userhook_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 P.client.unmark(c) local cl = c or client.focus_get() for k, v in pairs(awfulmarked) do if cl == v then table.remove(awfulmarked, k) userhook_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 P.client.ismarked(c) local cl = c or 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 P.client.togglemarked(c) local cl = c or client.focus_get() if not P.client.mark(c) then P.client.unmark(c) end end --- Return the marked clients and empty the marked table. -- @return A table with all marked clients. function P.client.getmarked() for k, v in pairs(awfulmarked) do userhook_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 P.layout.inc(layouts, i) local t = P.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 P.layout.set(layout) local t = P.tag.selected() if t then t.layout = layout end end P.hooks['userhook_create'] = userhook_create P.hooks['userhook_call'] = userhook_call for name, hook in pairs(hooks) do if name ~= 'timer' then P.hooks[name] = function (f) if P.myhooks[name] == nil then P.myhooks[name] = {} hooks[name](function (...) for i, o in pairs(P.myhooks[name]) do P.myhooks[name][i]['callback'](...) end end) end table.insert(P.myhooks[name], {callback = f}) end else P.hooks[name] = function (time, f, runnow) if P.myhooks[name] == nil then P.myhooks[name] = {} hooks[name](1, function (...) for i,o in pairs(P.myhooks[name]) do if P.myhooks[name][i]['counter'] >= P.myhooks[name][i]['timer'] then P.myhooks[name][i]['counter'] = 1 P.myhooks[name][i]['callback'](...) else P.myhooks[name][i]['counter'] = P.myhooks[name][i]['counter']+1 end end end) end if runnow then table.insert(P.myhooks[name], {callback = f, timer = time, counter = time}) else table.insert(P.myhooks[name], {callback = f, timer = time, counter = 0}) end end end end --- Spawn a program. -- @param cmd The command. -- @return The os.execute() return value. function P.spawn(cmd) return os.execute(cmd .. "&") end --- Eval Lua code. -- @return The return value of Lua code. function P.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 P.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 .. "" .. char .. "" .. text_end end --- Run a prompt in a box. -- @param args A table with optional arguments: cursor_fg, cursor_bg, 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 P.prompt(args, textbox, exe_callback, completion_callback) if not args then return end local command = "" local command_before_comp local cur_pos_before_comp local prompt = args.prompt or "" local inv_col = args.cursor_fg or "black" local cur_col = args.cursor_bg 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) 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 == "g" then textbox.text = "" 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 == "e" then cur_pos = #command + 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 P.escape(text) text = text:gsub("&", "&") text = text:gsub("<", "<") text = text:gsub(">", ">") text = text:gsub("'", "'") text = text:gsub("\"", """) return text end --- Unescape a string from entities. -- @param text Text to unescape. -- @return Unescaped text. function P.unescape(text) text = text:gsub("&", "&") text = text:gsub("<", "<") text = text:gsub(">", ">") text = text:gsub("'", "'") text = text:gsub(""", "\"") return text end --- Return labels for a taglist widget. -- It returns the tag name and set a special -- foreground and background color for selected tags. -- @param t The tag. -- @param bg_focus The background color for selected tag. -- @param fg_focus The foreground color for selected tag. -- @return A string to print. function P.widget.taglist.label(t, bg_focus, fg_focus) local text = "" if t.selected then text = " "..t.name.." " else text = " "..t.name.." " end return text end local function widget_tasklist_label_common(c, bg_focus, fg_focus) local text = "" if c.floating then text = "" end if client.focus_get() == c then text = text .. " "..P.escape(c.name).." " else text = text .. " "..P.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 bg_focus The background color for focused client. -- @param fg_focus The foreground color for focused client. -- @return A string to pring. function P.widget.tasklist.label.allscreen(c, screen, bg_focus, fg_focus) return widget_tasklist_label_common(c, bg_focus, fg_focus) 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 bg_focus The background color for focused client. -- @param fg_focus The foreground color for focused client. -- @return A string to pring. function P.widget.tasklist.label.alltags(c, screen, bg_focus, fg_focus) -- Only print client on the same screen as this widget if c.screen ~= screen then return end return widget_tasklist_label_common(c, bg_focus, fg_focus) 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 bg_focus The background color for focused client. -- @param fg_focus The foreground color for focused client. -- @return A string to pring. function P.widget.tasklist.label.currenttags(c, screen, bg_focus, fg_focus) -- Only print client on the same screen as this widget if c.screen ~= screen then return end for k, t in pairs(tag.get(screen)) do if t.selected and c:istagged(t) then return widget_tasklist_label_common(c, bg_focus, fg_focus) end end end return P