1 ---------------------------------------------------------------------------
2 -- @author Julien Danjou <julien@danjou.info>
3 -- @copyright 2008 Julien Danjou
4 -- @release @AWESOME_VERSION@
5 ---------------------------------------------------------------------------
7 -- Grab environment we need
11 local load
= loadstring
or load
-- v5.1 - loadstring, v5.2 - load
12 local loadfile
= loadfile
26 --- Utility module for awful
31 util
.shell
= os
.getenv("SHELL") or "/bin/sh"
33 function util
.deprecate(see
)
34 io
.stderr
:write("W: awful: function is deprecated")
36 io
.stderr
:write(", see " .. see
)
39 io
.stderr
:write(debug
.traceback())
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)
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
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
)
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
)
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)
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')
100 local s
= f
:read("*all")
110 -- @return The return value of Lua code.
111 function util
.eval(s
)
112 return assert(load(s
))()
115 local xml_entity_names
= { ["'"] = "'", ["\""] = """, ["<"] = "<", [">"] = ">", ["&"] = "&" };
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
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
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
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
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")
167 return dir
.. "/awesome"
169 return os
.getenv("HOME") .. "/.config/awesome"
170 elseif d
== "cache" then
171 local dir
= os
.getenv("XDG_CACHE_HOME")
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
193 for _
, e
in pairs(exts
) do
194 icon
= d
.. iconname
.. '.' .. e
195 if util
.file_readable(icon
) 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
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
)
222 local function subset_mask_apply(mask
, set
)
226 rtable
.insert(ret
, set
[i
])
232 local function subset_next(mask
)
234 while i
<= #mask
and mask
[i
] do
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.
252 -- @return A table with all subset.
253 function util
.subsets(set
)
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
))
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
)
276 elseif dir
== "down" then
278 elseif dir
== "left" then
280 elseif dir
== "right" then
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
)
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
)
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
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(...)
345 for i
, t
in pairs({...}) do
347 for k
, v
in pairs(t
) do
348 if type(k
) == "number" then
349 rtable
.insert(ret
, v
)
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
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
382 return text
:gsub("(%s+)()(%S+)()",
383 function(sp
, st
, word
, fi
)
384 if fi
- pos
> width
then
386 return "\n" .. string.rep(" ", indent
) .. word
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
)
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
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
)
424 -- @param t the table to reverse
425 -- @return the reversed table
426 function util
.table.reverse(t
)
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
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
448 for k
, v
in pairs(t
) do
449 if deep
and type(v
) == "table" then
450 c
[k
] = util
.table.clone(v
)
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
466 function util
.table.iterate(t
, filter
, start
)
468 local index
= start
or 1
472 while count
< length
do
473 local item
= t
[index
]
474 index
= util
.cycle(#t
, index
+ 1)
476 if filter(item
) then return item
end
483 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80