--------------------------------------------------------------------------- -- @author Julien Danjou <julien@danjou.info> -- @copyright 2008 Julien Danjou -- @release @AWESOME_VERSION@ --------------------------------------------------------------------------- -- Grab environment we need local type = type local string = string local assert = assert local loadstring = loadstring local ipairs = ipairs local pairs = pairs local os = os local io = io local math = math local setmetatable = setmetatable local table = table local otable = otable local capi = { awesome = awesome, screen = screen, client = client, mouse = mouse, button = button, titlebar = titlebar, widget = widget, hooks = hooks, keygrabber = keygrabber } --- awful: AWesome Functions very UsefuL module("awful") -- Local variable handling theme local theme = {} -- mapping of command/completion function local bashcomp_funcs = {} local bashcomp_src = "/etc/bash_completion" -- Various public structures beautiful = {} hooks = {} hooks.user = {} prompt = {} prompt.history = {} prompt.history.data = {} completion = {} screen = {} layout = {} client = {} client.data = {} client.data.maximize = otable() 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 = {} client.urgent = {} client.urgent.stack = {} client.urgent.stack.data = {} --- Strip alpha part of color. -- @param color The color. -- @return The color without alpha channel. local function color_strip_alpha(color) if color:len() == 9 then color = color:sub(1, 7) end return color 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 --- Create a directory -- @param dir The directory. -- @return mkdir return code function mkdir(dir) return os.execute("mkdir -p " .. dir) end --- Get the first client that got the urgent hint. -- @return The first urgent client. function client.urgent.get() if #client.urgent.stack.data > 0 then return client.urgent.stack.data[1] else -- fallback behaviour: iterate through clients and get the first urgent local clients = capi.client.get() for k, cl in pairs(clients) do if cl.urgent then return cl end end end end --- Jump to the client that received the urgent hint first. function client.urgent.jumpto() local c = client.urgent.get() if c then local s = capi.client.focus and capi.client.focus.screen or capi.mouse.screen -- focus the screen if s ~= c.screen then capi.mouse.screen = c.screen end -- focus the tag tag.viewonly(c:tags()[1]) -- focus the client capi.client.focus = c c:raise() end end --- Adds client to urgent stack. -- @param The client object. function client.urgent.stack.add(c) table.insert(client.urgent.stack.data, c) end --- Remove client from urgent stack. -- @param The client object. function client.urgent.stack.delete(c) for k, cl in ipairs(client.urgent.stack.data) do if c == cl then table.remove(client.urgent.stack.data, k) break end end 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 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 capi.client.focus = c 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 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 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 capi.client.focus = target 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 capi.client.focus = target 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 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 -- Set the client as slave: put it at the end of other windows. -- @param c The window to set as slave. -- @return function client.setslave(c) local cls = capi.client.visible_get(c.screen) for k, v in pairs(cls) do c:swap(v) end end --- Move/resize a client relative to current coordinates. -- @param x The relative x coordinate. -- @param y The relative y coordinate. -- @param w The relative width. -- @param h The relative height. -- @param c The optional client, otherwise focused one is used. function client.moveresize(x, y, w, h, c) local sel = c or capi.client.focus 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 workarea. -- @param c A client, or the focused one if nil. function client.maximize(c) local sel = c or capi.client.focus if sel then local ws = capi.screen[sel.screen].workarea ws.width = ws.width - 2 * sel.border_width ws.height = ws.height - 2 * sel.border_width if sel.floating and client.data.maximize[sel] then sel.floating = client.data.maximize[sel].floating if sel.floating then sel:coords(client.data.maximize[sel].coords) end client.data.maximize[sel] = nil else client.data.maximize[sel] = { coords = sel:coords(), floating = sel.floating } sel.floating = true sel:coords(ws) end end end --- Erase eventual client data in maximize. -- @param c The client. local function client_maximize_clean(c) client.data.maximize[c] = nil end --- Give the focus to a screen, and move pointer. -- @param Screen number. function screen.focus(i) local s = cycle(capi.screen.count(), capi.mouse.screen + i) local c = client.focus.history.get(s, 0) if c then capi.client.focus = c end -- Move the mouse on the screen capi.mouse.screen = 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.screen[screen]:tags() 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.screen[s]:tags() 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.screen[screen]:tags() 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.screen[screen or capi.mouse.screen]:tags() 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.screen[screen or capi.mouse.screen]:tags() 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.screen) 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. -- @param c Optional client to move, otherwise the focused one is used. function client.movetotag(target, c) local sel = c or capi.client.focus if sel then -- Check that tag and client screen are identical if sel.screen ~= target.screen then return end sel:tags({ target }) end end --- Toggle a tag on a client. -- @param target The tag to toggle. -- @param c Optional client to toggle, otherwise the focused one is used. function client.toggletag(target, c) local sel = c or capi.client.focus -- Check that tag and client screen are identical if sel and sel.screen == target.screen then local tags = sel:tags() if tags[target] then -- If it's the only tag for the window, stop. if #tags == 1 then return end tags[tags[target]] = nil else tags[target] = target end sel:tags(tags) end end --- 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 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 if sel then local sc = capi.screen.count() if not s then s = sel.screen + 1 end if s > sc then s = 1 elseif s < 1 then s = sc end sel.screen = s capi.mouse.coords(capi.screen[s].coords) capi.client.focus = sel 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(...) 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 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 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 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 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' or time <= 0 then return end local new_timer if hooks[name].timer then -- Take the smallest between current and new new_timer = math.min(time, hooks[name].timer) else new_timer = time end if not hooks[name].callbacks then hooks[name].callbacks = {} end if hooks[name].timer ~= new_timer then hooks[name].timer = new_timer hook(hooks[name].timer, function (...) for i, callback in ipairs(hooks[name].callbacks) do callback['counter'] = callback['counter'] + hooks[name].timer if callback['counter'] >= callback['timer'] then callback['callback'](...) callback['counter'] = 0 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. -- @param screen The screen where to spawn window. -- @return The awesome.spawn return value. function spawn(cmd, screen) if cmd and cmd ~= "" then return capi.awesome.spawn(cmd .. "&", screen or capi.mouse.screen) end end -- Read a program output and returns its output as a string. -- @param cmd The command to run. -- @return A string with the program output. function pread(cmd) if cmd and cmd ~= "" then local f = io.popen(cmd, 'r') local s = f:read("*all") f:close() return s end end --- Eval Lua code. -- @return The return value of Lua code. function eval(s) return assert(loadstring("return " .. s))() end --- Load history file in history table -- @param id The prompt history identifier which is the path to the filename -- @param max Optional parameter, the maximum number of entries in file local function prompt_history_check_load(id, max) if id and id ~= "" and not prompt.history[id] then prompt.history[id] = { max = 50, table = {} } if max then prompt.history[id].max = max end local f = io.open(id, "r") -- Read history file if f then for line in f:lines() do table.insert(prompt.history[id].table, line) if #prompt.history[id].table >= prompt.history[id].max then break end end end end end --- Save history table in history file -- @param id The prompt history identifier local function prompt_history_save(id) if prompt.history[id] then local f = io.open(id, "w") if not f then local i = 0 for d in id:gmatch(".-/") do i = i + #d end mkdir(id:sub(1, i - 1)) f = assert(io.open(id, "w")) end for i = 1, math.min(#prompt.history[id].table, prompt.history[id].max) do f:write(prompt.history[id].table[i] .. "\n") end f:close() end end --- Return the number of items in history table regarding the id -- @param id The prompt history identifier -- @return the number of items in history table, -1 if history is disabled local function prompt_history_items(id) if prompt.history[id] then return #prompt.history[id].table else return -1 end end --- Add an entry to the history file -- @param id The prompt history identifier -- @param command The command to add local function prompt_history_add(id, command) if prompt.history[id] then if command ~= "" and command ~= prompt.history[id].table[#prompt.history[id].table] then table.insert(prompt.history[id].table, command) -- Do not exceed our max_cmd if #prompt.history[id].table > prompt.history[id].max then table.remove(prompt.history[id].table, 1) end prompt_history_save(id) end end end --- Enable programmable bash completion in awful.completion.bash at the price of -- a slight overhead -- @param src The bash completion source file, /etc/bash_completion by default. function completion.bashcomp_load(src) if src then bashcomp_src = src end local c = io.popen("/usr/bin/env bash -c 'source " .. bashcomp_src .. "; complete -p'") while true do local line = c:read("*line") if not line then break end -- if a bash function is used for completion, register it if line:match(".* -F .*") then bashcomp_funcs[line:gsub(".* (%S+)$","%1")] = line:gsub(".*-F +(%S+) .*$", "%1") end end c:close() end --- Use bash completion system to complete command and filename. -- @param command The command line. -- @param cur_pos The cursor position. -- @param ncomp The element number to complete. -- @return The new command 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 bash_cmd if bashcomp_funcs[words[1]] then -- fairly complex command with inline bash script to get the possible completions bash_cmd = "/usr/bin/env bash -c 'source " .. bashcomp_src .. "; " .. "__print_completions() { for ((i=0;i<${#COMPREPLY[*]};i++)); do echo ${COMPREPLY[i]}; done }; " .. "COMP_WORDS=(" .. command .."); COMP_LINE=\"" .. command .. "\"; " .. "COMP_COUNT=" .. cur_pos .. "; COMP_CWORD=" .. cword_index-1 .. "; " .. bashcomp_funcs[words[1]] .. "; __print_completions | sort -u'" else bash_cmd = "/usr/bin/env bash -c 'compgen -A " .. comptype .. " " .. words[cword_index] .. "'" end local c = io.popen(bash_cmd) 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: 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. -- @param history_path Optional parameter: file path where the history should be saved, set nil to disable history -- @param history_max Optional parameter: set the maximum entries in history file, 50 by default -- @param done_callback Optional parameter: the callback function to always call without arguments, regardless of whether the prompt was cancelled. function prompt.run(args, textbox, exe_callback, completion_callback, history_path, history_max, done_callback) if not args then args = {} end local command = "" local command_before_comp local cur_pos_before_comp local prettyprompt = 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" prompt_history_check_load(history_path, history_max) local history_index = prompt_history_items(history_path) + 1 -- 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 = prettyprompt .. 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 = "" if done_callback then done_callback() end return false elseif key == "j" or key == "m" then textbox.text = "" exec_callback(command) if done_callback then done_callback() end return false end else if key == "Return" then textbox.text = "" prompt_history_add(history_path, command) exe_callback(command) if done_callback then done_callback() end return false elseif key == "Escape" then textbox.text = "" if done_callback then done_callback() end 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 elseif key == "Up" then if history_index > 1 then history_index = history_index - 1 command = prompt.history[history_path].table[history_index] cur_pos = #command + 2 end elseif key == "Down" then if history_index < prompt_history_items(history_path) then history_index = history_index + 1 command = prompt.history[history_path].table[history_index] cur_pos = #command + 2 elseif history_index == prompt_history_items(history_path) then history_index = history_index + 1 command = "" cur_pos = 1 end 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 = prettyprompt .. 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. -- taglist_squares Optional: set "true" or nil to display the taglist squares. -- taglist_squares_sel Optional: an user provided image for selected squares. -- taglist_squares_unsel Optional: an user provided image for unselected squares. -- @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.taglist_fg_focus or theme.fg_focus local bg_focus = args.bg_focus or theme.taglist_bg_focus or theme.bg_focus local fg_urgent = args.fg_urgent or theme.taglist_fg_urgent or theme.fg_urgent local bg_urgent = args.bg_urgent or theme.taglist_bg_urgent or theme.bg_urgent local taglist_squares = args.taglist_squares or theme.taglist_squares local taglist_squares_sel = args.squares_sel or theme.squares_sel local taglist_squares_unsel = args.squares_unsel or theme.squares_unsel local text local background = "" local sel = capi.client.focus 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 if not taglist_squares or taglist_squares == "true" then if taglist_squares_sel then background = "resize=\"true\" image=\"" .. taglist_squares_sel .. "\"" else background = "resize=\"true\" image=\"@AWESOME_ICON_PATH@/taglist/squarefw.png\"" end end elseif bg_urgent or fg_urgent then for k, c in pairs(t:clients()) do if not taglist_squares or taglist_squares == "true" then if taglist_squares_unsel then background = "resize=\"true\" image=\"" .. taglist_squares_unsel .. "\"" else background = "resize=\"true\" image=\"@AWESOME_ICON_PATH@/taglist/squarew.png\"" end end if c.urgent then bg_color = bg_urgent fg_color = fg_urgent break end end end if bg_color and fg_color then text = " "..escape(t.name).." " else text = " "..escape(t.name).." " end return text end --- Return labels for a taglist widget with all *non empty* tags 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.noempty(t, args) if #t:clients() > 0 or t.selected then if not args then args = {} end local fg_focus = args.fg_focus or theme.taglist_fg_focus or theme.fg_focus local bg_focus = args.bg_focus or theme.taglist_bg_focus or theme.bg_focus local fg_urgent = args.fg_urgent or theme.taglist_fg_urgent or theme.fg_urgent local bg_urgent = args.bg_urgent or theme.taglist_bg_urgent or theme.bg_urgent local bg_color = nil local fg_color = nil local text if t.selected then bg_color = bg_focus fg_color = fg_focus end if bg_urgent and fg_urgent then for k, c in pairs(t:clients()) do if c.urgent then bg_color = bg_urgent fg_color = fg_urgent break end end end if fg_color and bg_color then text = " " .. escape(t.name) .. " " else text = " " .. escape(t.name) .. " " end return text end end local function widget_tasklist_label_common(c, args) if not args then args = {} end local fg_focus = args.fg_focus or theme.tasklist_fg_focus or theme.fg_focus local bg_focus = args.bg_focus or theme.tasklist_bg_focus or theme.bg_focus local fg_urgent = args.fg_urgent or theme.tasklist_fg_urgent or theme.fg_urgent local bg_urgent = args.bg_urgent or theme.tasklist_bg_urgent or theme.bg_urgent local text = "" local name if c.floating then text = "" end if c.hidden then name = escape(c.icon_name) or "" else name = escape(c.name) or "" end if capi.client.focus == c then if bg_focus and fg_focus then text = text .. " "..name.." " else text = text .. " "..name.." " end elseif c.urgent and bg_urgent and fg_urgent then text = text .. " "..name.." " else text = text .. " "..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 ipairs(capi.screen[screen]:tags()) 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.titlebar_fg_normal or theme.fg_normal titlebar.data[c].bg = args.bg or theme.titlebar_bg_normal or theme.bg_normal titlebar.data[c].fg_focus = args.fg_focus or theme.titlebar_fg_focus or theme.fg_focus titlebar.data[c].bg_focus = args.bg_focus or theme.titlebar_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) local title = capi.widget({ type = "textbox", name = "title", align = "flex" }) local bts = { capi.button({ }, 1, function (t) t.client:mouse_move() end), capi.button({ args.modkey }, 3, function (t) t.client:mouse_resize() end) } title:buttons(bts) if theme.titlebar_close_button == "true" then local closef = widget.button({ name = "closef", align = "right", image = theme.titlebar_close_button_focus or theme.titlebar_close_button_img_focus or "@AWESOME_ICON_PATH@/titlebar/closer.png" }) local close = widget.button({ name = "close", align = "right", image = theme.titlebar_close_button_normal or theme.titlebar_close_button_img_normal or "@AWESOME_ICON_PATH@/titlebar/close.png" }) -- Bind kill button local b = capi.button({ }, 1, nil, function (t) t.client:kill() end) local bts = closef:buttons() bts[#bts + 1] = b closef:buttons(bts) bts = close:buttons() bts[#bts + 1] = b close:buttons(bts) tb:widgets({ capi.widget({ type = "appicon", name = "appicon", align = "left" }), title, closef, close }) else tb:widgets({ capi.widget({ type = "appicon", name = "appicon", align = "left" }), title }) end c.titlebar = tb titlebar.update(c) 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:widgets() local title, close, closef for k, v in ipairs(widgets) do if v.name == "title" then title = v end if v.name == "close" then close = v end if v.name == "closef" then closef = v end if title and close and closef then break end end if title then title.text = " " .. escape(c.name) .. " " end if capi.client.focus == c then c.titlebar.fg = titlebar.data[c].fg_focus c.titlebar.bg = titlebar.data[c].bg_focus if closef then closef.visible = true end if close then close.visible = false end else c.titlebar.fg = titlebar.data[c].fg c.titlebar.bg = titlebar.data[c].bg if closef then closef.visible = false end if close then close.visible = 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 --- Create a button widget. When clicked, the image is deplaced to make it like -- a real button. -- @param args Standard widget table arguments, plus image for the image path. -- @return A textbox widget configured as a button. function widget.button(args) if not args then return end args.type = "textbox" local w = capi.widget(args) local img_release = "" local img_press = "" w.text = img_release w:buttons({ capi.button({}, 1, function () w.text = img_press end, function () w.text = img_release end) }) return w 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.unmanage.register(client_maximize_clean) hooks.focus.register(titlebar.update) hooks.unfocus.register(titlebar.update) hooks.titleupdate.register(titlebar.update) hooks.unmanage.register(titlebar.remove) hooks.urgent.register(client.urgent.stack.add) hooks.focus.register(client.urgent.stack.delete) hooks.unmanage.register(client.urgent.stack.delete) -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80