ewmh.activate: focus and especially raise clients during startup
[awesome.git] / lib / awful / util.lua.in
blob76c88e204f0edfc8cac1689f81e2a74a59d7cec3
1 ---------------------------------------------------------------------------
2 -- @author Julien Danjou <julien@danjou.info>
3 -- @copyright 2008 Julien Danjou
4 -- @release @AWESOME_VERSION@
5 ---------------------------------------------------------------------------
7 -- Grab environment we need
8 local os = os
9 local io = io
10 local assert = assert
11 local load = loadstring or load -- v5.1 - loadstring, v5.2 - load
12 local loadfile = loadfile
13 local debug = debug
14 local pairs = pairs
15 local ipairs = ipairs
16 local type = type
17 local rtable = table
18 local pairs = pairs
19 local string = string
20 local capi =
22 awesome = awesome,
23 mouse = mouse
26 --- Utility module for awful
27 -- awful.util
28 local util = {}
29 util.table = {}
31 util.shell = os.getenv("SHELL") or "/bin/sh"
33 function util.deprecate(see)
34 io.stderr:write("W: awful: function is deprecated")
35 if see then
36 io.stderr:write(", see " .. see)
37 end
38 io.stderr:write("\n")
39 io.stderr:write(debug.traceback())
40 end
42 --- Strip alpha part of color.
43 -- @param color The color.
44 -- @return The color without alpha channel.
45 function util.color_strip_alpha(color)
46 if color:len() == 9 then
47 color = color:sub(1, 7)
48 end
49 return color
50 end
52 --- Make i cycle.
53 -- @param t A length. Must be greater than zero.
54 -- @param i An absolute index to fit into #t.
55 -- @return An integer in (1, t) or nil if t is less than or equal to zero.
56 function util.cycle(t, i)
57 if t < 1 then return end
58 i = i % t
59 if i == 0 then
60 i = t
61 end
62 return i
63 end
65 --- Create a directory
66 -- @param dir The directory.
67 -- @return mkdir return code
68 function util.mkdir(dir)
69 return os.execute("mkdir -p " .. dir)
70 end
72 --- Spawn a program.
73 -- @param cmd The command.
74 -- @param sn Enable startup-notification.
75 -- @return The forked PID or an error message
76 -- @return The startup notification UID, if the spawn was successful
77 function util.spawn(cmd, sn)
78 if cmd and cmd ~= "" then
79 if sn == nil then sn = true end
80 return capi.awesome.spawn(cmd, sn)
81 end
82 end
84 --- Spawn a program using the shell.
85 -- @param cmd The command.
86 function util.spawn_with_shell(cmd)
87 if cmd and cmd ~= "" then
88 cmd = { util.shell, "-c", cmd }
89 return capi.awesome.spawn(cmd, false)
90 end
91 end
93 --- Read a program output and returns its output as a string.
94 -- @param cmd The command to run.
95 -- @return A string with the program output, or the error if one occured.
96 function util.pread(cmd)
97 if cmd and cmd ~= "" then
98 local f, err = io.popen(cmd, 'r')
99 if f then
100 local s = f:read("*all")
101 f:close()
102 return s
103 else
104 return err
109 --- Eval Lua code.
110 -- @return The return value of Lua code.
111 function util.eval(s)
112 return assert(load(s))()
115 local xml_entity_names = { ["'"] = "&apos;", ["\""] = "&quot;", ["<"] = "&lt;", [">"] = "&gt;", ["&"] = "&amp;" };
116 --- Escape a string from XML char.
117 -- Useful to set raw text in textbox.
118 -- @param text Text to escape.
119 -- @return Escape text.
120 function util.escape(text)
121 return text and text:gsub("['&<>\"]", xml_entity_names) or nil
124 local xml_entity_chars = { lt = "<", gt = ">", nbsp = " ", quot = "\"", apos = "'", ndash = "-", mdash = "-", amp = "&" };
125 --- Unescape a string from entities.
126 -- @param text Text to unescape.
127 -- @return Unescaped text.
128 function util.unescape(text)
129 return text and text:gsub("&(%a+);", xml_entity_chars) or nil
132 --- Check if a file is a Lua valid file.
133 -- This is done by loading the content and compiling it with loadfile().
134 -- @param path The file path.
135 -- @return A function if everything is alright, a string with the error
136 -- otherwise.
137 function util.checkfile(path)
138 local f, e = loadfile(path)
139 -- Return function if function, otherwise return error.
140 if f then return f end
141 return e
144 --- Try to restart awesome.
145 -- It checks if the configuration file is valid, and then restart if it's ok.
146 -- If it's not ok, the error will be returned.
147 -- @return Never return if awesome restart, or return a string error.
148 function util.restart()
149 local c = util.checkfile(capi.awesome.conffile)
151 if type(c) ~= "function" then
152 return c
155 capi.awesome.restart()
158 --- Get the user's config or cache dir.
159 -- It first checks XDG_CONFIG_HOME / XDG_CACHE_HOME, but then goes with the
160 -- default paths.
161 -- @param d The directory to get (either "config" or "cache").
162 -- @return A string containing the requested path.
163 function util.getdir(d)
164 if d == "config" then
165 local dir = os.getenv("XDG_CONFIG_HOME")
166 if dir then
167 return dir .. "/awesome"
169 return os.getenv("HOME") .. "/.config/awesome"
170 elseif d == "cache" then
171 local dir = os.getenv("XDG_CACHE_HOME")
172 if dir then
173 return dir .. "/awesome"
175 return os.getenv("HOME").."/.cache/awesome"
179 --- Search for an icon and return the full path.
180 -- It searches for the icon path under the directories given w/the right ext
181 -- @param iconname The name of the icon to search for.
182 -- @param exts Table of image extensions allowed, otherwise { 'png', gif' }
183 -- @param dirs Table of dirs to search, otherwise { '/usr/share/pixmaps/' }
184 -- @param size Optional size. If this is specified, subdirectories <size>x<size>
185 -- of the dirs are searched first
186 function util.geticonpath(iconname, exts, dirs, size)
187 local exts = exts or { 'png', 'gif' }
188 local dirs = dirs or { '/usr/share/pixmaps/', '/usr/share/icons/hicolor/' }
189 local icontypes = { 'apps', 'actions', 'categories', 'emblems',
190 'mimetypes', 'status', 'devices', 'extras', 'places', 'stock' }
191 for _, d in pairs(dirs) do
192 local icon
193 for _, e in pairs(exts) do
194 icon = d .. iconname .. '.' .. e
195 if util.file_readable(icon) then
196 return icon
198 if size then
199 for _, t in pairs(icontypes) do
200 icon = string.format("%s%ux%u/%s/%s.%s", d, size, size, t, iconname, e)
201 if util.file_readable(icon) then
202 return icon
210 --- Check if file exists and is readable.
211 -- @param filename The file path
212 -- @return True if file exists and readable.
213 function util.file_readable(filename)
214 local file = io.open(filename)
215 if file then
216 io.close(file)
217 return true
219 return false
222 local function subset_mask_apply(mask, set)
223 local ret = {}
224 for i = 1, #set do
225 if mask[i] then
226 rtable.insert(ret, set[i])
229 return ret
232 local function subset_next(mask)
233 local i = 1
234 while i <= #mask and mask[i] do
235 mask[i] = false
236 i = i + 1
239 if i <= #mask then
240 mask[i] = 1
241 return true
243 return false
246 --- Return all subsets of a specific set.
247 -- This function, giving a set, will return all subset it.
248 -- For example, if we consider a set with value { 10, 15, 34 },
249 -- it will return a table containing 2^n set:
250 -- { }, { 10 }, { 15 }, { 34 }, { 10, 15 }, { 10, 34 }, etc.
251 -- @param set A set.
252 -- @return A table with all subset.
253 function util.subsets(set)
254 local mask = {}
255 local ret = {}
256 for i = 1, #set do mask[i] = false end
258 -- Insert the empty one
259 rtable.insert(ret, {})
261 while subset_next(mask) do
262 rtable.insert(ret, subset_mask_apply(mask, set))
264 return ret
267 -- Return true whether rectangle B is in the right direction
268 -- compared to rectangle A.
269 -- @param dir The direction.
270 -- @param gA The geometric specification for rectangle A.
271 -- @param gB The geometric specification for rectangle B.
272 -- @return True if B is in the direction of A.
273 local function is_in_direction(dir, gA, gB)
274 if dir == "up" then
275 return gA.y > gB.y
276 elseif dir == "down" then
277 return gA.y < gB.y
278 elseif dir == "left" then
279 return gA.x > gB.x
280 elseif dir == "right" then
281 return gA.x < gB.x
283 return false
286 -- Calculate distance between two points.
287 -- i.e: if we want to move to the right, we will take the right border
288 -- of the currently focused screen and the left side of the checked screen.
289 -- @param dir The direction.
290 -- @param gA The first rectangle.
291 -- @param gB The second rectangle.
292 -- @return The distance between the screens.
293 local function calculate_distance(dir, _gA, _gB)
294 local gAx = _gA.x
295 local gAy = _gA.y
296 local gBx = _gB.x
297 local gBy = _gB.y
299 if dir == "up" then
300 gBy = _gB.y + _gB.height
301 elseif dir == "down" then
302 gAy = _gA.y + _gA.height
303 elseif dir == "left" then
304 gBx = _gB.x + _gB.width
305 elseif dir == "right" then
306 gAx = _gA.x + _gA.width
309 return math.sqrt(math.pow(gBx - gAx, 2) + math.pow(gBy - gAy, 2))
312 -- Get the nearest rectangle in the given direction. Every rectangle is specified as a table
313 -- with 'x', 'y', 'width', 'height' keys, the same as client or screen geometries.
314 -- @param dir The direction, can be either "up", "down", "left" or "right".
315 -- @param recttbl A table of rectangle specifications.
316 -- @param cur The current rectangle.
317 -- @return The index for the rectangle in recttbl closer to cur in the given direction. nil if none found.
318 function util.get_rectangle_in_direction(dir, recttbl, cur)
319 local dist, dist_min
320 local target = nil
322 -- We check each object
323 for i, rect in ipairs(recttbl) do
324 -- Check geometry to see if object is located in the right direction.
325 if is_in_direction(dir, cur, rect) then
326 -- Calculate distance between current and checked object.
327 dist = calculate_distance(dir, cur, rect)
329 -- If distance is shorter then keep the object.
330 if not target or dist < dist_min then
331 target = i
332 dist_min = dist
336 return target
339 --- Join all tables given as parameters.
340 -- This will iterate all tables and insert all their keys into a new table.
341 -- @param args A list of tables to join
342 -- @return A new table containing all keys from the arguments.
343 function util.table.join(...)
344 local ret = {}
345 for i, t in pairs({...}) do
346 if t then
347 for k, v in pairs(t) do
348 if type(k) == "number" then
349 rtable.insert(ret, v)
350 else
351 ret[k] = v
356 return ret
359 --- Check if a table has an item and return its key.
360 -- @param t The table.
361 -- @param item The item to look for in values of the table.
362 -- @return The key were the item is found, or nil if not found.
363 function util.table.hasitem(t, item)
364 for k, v in pairs(t) do
365 if v == item then
366 return k
371 --- Split a string into multiple lines
372 -- @param text String to wrap.
373 -- @param width Maximum length of each line. Default: 72.
374 -- @param indent Number of spaces added before each wrapped line. Default: 0.
375 -- @return The string with lines wrapped to width.
376 function util.linewrap(text, width, indent)
377 local text = text or ""
378 local width = width or 72
379 local indent = indent or 0
381 local pos = 1
382 return text:gsub("(%s+)()(%S+)()",
383 function(sp, st, word, fi)
384 if fi - pos > width then
385 pos = st
386 return "\n" .. string.rep(" ", indent) .. word
388 end)
391 --- Get a sorted table with all integer keys from a table
392 -- @param t the table for which the keys to get
393 -- @return A table with keys
394 function util.table.keys(t)
395 local keys = { }
396 for k, _ in pairs(t) do
397 rtable.insert(keys, k)
399 rtable.sort(keys, function (a, b)
400 return type(a) == type(b) and a < b or false
401 end)
402 return keys
405 --- Filter a tables keys for certain content types
406 -- @param t The table to retrieve the keys for
407 -- @param ... the types to look for
408 -- @return A filtered table with keys
409 function util.table.keys_filter(t, ...)
410 local keys = util.table.keys(t)
411 local keys_filtered = { }
412 for _, k in pairs(keys) do
413 for _, et in pairs({...}) do
414 if type(t[k]) == et then
415 rtable.insert(keys_filtered, k)
416 break
420 return keys_filtered
423 --- Reverse a table
424 -- @param t the table to reverse
425 -- @return the reversed table
426 function util.table.reverse(t)
427 local tr = { }
428 -- reverse all elements with integer keys
429 for _, v in ipairs(t) do
430 rtable.insert(tr, 1, v)
432 -- add the remaining elements
433 for k, v in pairs(t) do
434 if type(k) ~= "number" then
435 tr[k] = v
438 return tr
441 --- Clone a table
442 -- @param t the table to clone
443 -- @param deep Create a deep clone? (default: true)
444 -- @return a clone of t
445 function util.table.clone(t, deep)
446 local deep = deep == nil and true or deep
447 local c = { }
448 for k, v in pairs(t) do
449 if deep and type(v) == "table" then
450 c[k] = util.table.clone(v)
451 else
452 c[k] = v
455 return c
459 -- Returns an iterator to cycle through, starting from the first element or the
460 -- given index, all elements of a table that match a given criteria.
462 -- @param t the table to iterate
463 -- @param filter a function that returns true to indicate a positive match
464 -- @param start what index to start iterating from. Default is 1 (=> start of
465 -- the table)
466 function util.table.iterate(t, filter, start)
467 local count = 0
468 local index = start or 1
469 local length = #t
471 return function ()
472 while count < length do
473 local item = t[index]
474 index = util.cycle(#t, index + 1)
475 count = count + 1
476 if filter(item) then return item end
481 return util
483 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80