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 awesome.spawn return value.
76 function util
.spawn(cmd
, sn
)
77 if cmd
and cmd
~= "" then
78 if sn
== nil then sn
= true end
79 return capi
.awesome
.spawn(cmd
, sn
)
83 --- Spawn a program using the shell.
84 -- @param cmd The command.
85 function util
.spawn_with_shell(cmd
)
86 if cmd
and cmd
~= "" then
87 cmd
= { util
.shell
, "-c", cmd
}
88 return capi
.awesome
.spawn(cmd
, false)
92 --- Read a program output and returns its output as a string.
93 -- @param cmd The command to run.
94 -- @return A string with the program output, or the error if one occured.
95 function util
.pread(cmd
)
96 if cmd
and cmd
~= "" then
97 local f
, err
= io
.popen(cmd
, 'r')
99 local s
= f
:read("*all")
109 -- @return The return value of Lua code.
110 function util
.eval(s
)
111 return assert(load(s
))()
114 local xml_entity_names
= { ["'"] = "'", ["\""] = """, ["<"] = "<", [">"] = ">", ["&"] = "&" };
115 --- Escape a string from XML char.
116 -- Useful to set raw text in textbox.
117 -- @param text Text to escape.
118 -- @return Escape text.
119 function util
.escape(text
)
120 return text
and text
:gsub("['&<>\"]", xml_entity_names
) or nil
123 local xml_entity_chars
= { lt
= "<", gt
= ">", nbsp
= " ", quot
= "\"", apos
= "'", ndash
= "-", mdash
= "-", amp
= "&" };
124 --- Unescape a string from entities.
125 -- @param text Text to unescape.
126 -- @return Unescaped text.
127 function util
.unescape(text
)
128 return text
and text
:gsub("&(%a+);", xml_entity_chars
) or nil
131 --- Check if a file is a Lua valid file.
132 -- This is done by loading the content and compiling it with loadfile().
133 -- @param path The file path.
134 -- @return A function if everything is alright, a string with the error
136 function util
.checkfile(path
)
137 local f
, e
= loadfile(path
)
138 -- Return function if function, otherwise return error.
139 if f
then return f
end
143 --- Try to restart awesome.
144 -- It checks if the configuration file is valid, and then restart if it's ok.
145 -- If it's not ok, the error will be returned.
146 -- @return Never return if awesome restart, or return a string error.
147 function util
.restart()
148 local c
= util
.checkfile(capi
.awesome
.conffile
)
150 if type(c
) ~= "function" then
154 capi
.awesome
.restart()
157 --- Get the user's config or cache dir.
158 -- It first checks XDG_CONFIG_HOME / XDG_CACHE_HOME, but then goes with the
160 -- @param d The directory to get (either "config" or "cache").
161 -- @return A string containing the requested path.
162 function util
.getdir(d
)
163 if d
== "config" then
164 local dir
= os
.getenv("XDG_CONFIG_HOME")
166 return dir
.. "/awesome"
168 return os
.getenv("HOME") .. "/.config/awesome"
169 elseif d
== "cache" then
170 local dir
= os
.getenv("XDG_CACHE_HOME")
172 return dir
.. "/awesome"
174 return os
.getenv("HOME").."/.cache/awesome"
178 --- Search for an icon and return the full path.
179 -- It searches for the icon path under the directories given w/the right ext
180 -- @param iconname The name of the icon to search for.
181 -- @param exts Table of image extensions allowed, otherwise { 'png', gif' }
182 -- @param dirs Table of dirs to search, otherwise { '/usr/share/pixmaps/' }
183 -- @param size Optional size. If this is specified, subdirectories <size>x<size>
184 -- of the dirs are searched first
185 function util
.geticonpath(iconname
, exts
, dirs
, size
)
186 exts
= exts
or { 'png', 'gif' }
187 dirs
= dirs
or { '/usr/share/pixmaps/' }
188 for _
, d
in pairs(dirs
) do
189 for _
, e
in pairs(exts
) do
192 icon
= string.format("%s%ux%u/%s.%s",
193 d
, size
, size
, iconname
, e
)
194 if util
.file_readable(icon
) then
198 icon
= d
.. iconname
.. '.' .. e
199 if util
.file_readable(icon
) then
206 --- Check if file exists and is readable.
207 -- @param filename The file path
208 -- @return True if file exists and readable.
209 function util
.file_readable(filename
)
210 local file
= io
.open(filename
)
218 local function subset_mask_apply(mask
, set
)
222 rtable
.insert(ret
, set
[i
])
228 local function subset_next(mask
)
230 while i
<= #mask
and mask
[i
] do
242 --- Return all subsets of a specific set.
243 -- This function, giving a set, will return all subset it.
244 -- For example, if we consider a set with value { 10, 15, 34 },
245 -- it will return a table containing 2^n set:
246 -- { }, { 10 }, { 15 }, { 34 }, { 10, 15 }, { 10, 34 }, etc.
248 -- @return A table with all subset.
249 function util
.subsets(set
)
252 for i
= 1, #set
do mask
[i
] = false end
254 -- Insert the empty one
255 rtable
.insert(ret
, {})
257 while subset_next(mask
) do
258 rtable
.insert(ret
, subset_mask_apply(mask
, set
))
263 -- Return true whether rectangle B is in the right direction
264 -- compared to rectangle A.
265 -- @param dir The direction.
266 -- @param gA The geometric specification for rectangle A.
267 -- @param gB The geometric specification for rectangle B.
268 -- @return True if B is in the direction of A.
269 local function is_in_direction(dir
, gA
, gB
)
272 elseif dir
== "down" then
274 elseif dir
== "left" then
276 elseif dir
== "right" then
282 -- Calculate distance between two points.
283 -- i.e: if we want to move to the right, we will take the right border
284 -- of the currently focused screen and the left side of the checked screen.
285 -- @param dir The direction.
286 -- @param gA The first rectangle.
287 -- @param gB The second rectangle.
288 -- @return The distance between the screens.
289 local function calculate_distance(dir
, _gA
, _gB
)
296 gBy
= _gB
.y
+ _gB
.height
297 elseif dir
== "down" then
298 gAy
= _gA
.y
+ _gA
.height
299 elseif dir
== "left" then
300 gBx
= _gB
.x
+ _gB
.width
301 elseif dir
== "right" then
302 gAx
= _gA
.x
+ _gA
.width
305 return math
.sqrt(math
.pow(gBx
- gAx
, 2) + math
.pow(gBy
- gAy
, 2))
308 -- Get the nearest rectangle in the given direction. Every rectangle is specified as a table
309 -- with 'x', 'y', 'width', 'height' keys, the same as client or screen geometries.
310 -- @param dir The direction, can be either "up", "down", "left" or "right".
311 -- @param recttbl A table of rectangle specifications.
312 -- @param cur The current rectangle.
313 -- @return The index for the rectangle in recttbl closer to cur in the given direction. nil if none found.
314 function util
.get_rectangle_in_direction(dir
, recttbl
, cur
)
318 -- We check each object
319 for i
, rect
in ipairs(recttbl
) do
320 -- Check geometry to see if object is located in the right direction.
321 if is_in_direction(dir
, cur
, rect
) then
322 -- Calculate distance between current and checked object.
323 dist
= calculate_distance(dir
, cur
, rect
)
325 -- If distance is shorter then keep the object.
326 if not target
or dist
< dist_min
then
335 --- Join all tables given as parameters.
336 -- This will iterate all tables and insert all their keys into a new table.
337 -- @param args A list of tables to join
338 -- @return A new table containing all keys from the arguments.
339 function util
.table.join(...)
341 for i
, t
in pairs({...}) do
343 for k
, v
in pairs(t
) do
344 if type(k
) == "number" then
345 rtable
.insert(ret
, v
)
355 --- Check if a table has an item and return its key.
356 -- @param t The table.
357 -- @param item The item to look for in values of the table.
358 -- @return The key were the item is found, or nil if not found.
359 function util
.table.hasitem(t
, item
)
360 for k
, v
in pairs(t
) do
367 --- Split a string into multiple lines
368 -- @param text String to wrap.
369 -- @param width Maximum length of each line. Default: 72.
370 -- @param indent Number of spaces added before each wrapped line. Default: 0.
371 -- @return The string with lines wrapped to width.
372 function util
.linewrap(text
, width
, indent
)
373 local text
= text
or ""
374 local width
= width
or 72
375 local indent
= indent
or 0
378 return text
:gsub("(%s+)()(%S+)()",
379 function(sp
, st
, word
, fi
)
380 if fi
- pos
> width
then
382 return "\n" .. string.rep(" ", indent
) .. word
387 --- Get a sorted table with all integer keys from a table
388 -- @param t the table for which the keys to get
389 -- @return A table with keys
390 function util
.table.keys(t
)
392 for k
, _
in pairs(t
) do
393 rtable
.insert(keys
, k
)
395 rtable
.sort(keys
, function (a
, b
)
396 return type(a
) == type(b
) and a
< b
or false
401 --- Filter a tables keys for certain content types
402 -- @param t The table to retrieve the keys for
403 -- @param ... the types to look for
404 -- @return A filtered table with keys
405 function util
.table.keys_filter(t
, ...)
406 local keys
= util
.table.keys(t
)
407 local keys_filtered
= { }
408 for _
, k
in pairs(keys
) do
409 for _
, et
in pairs({...}) do
410 if type(t
[k
]) == et
then
411 rtable
.insert(keys_filtered
, k
)
420 -- @param t the table to reverse
421 -- @return the reversed table
422 function util
.table.reverse(t
)
424 -- reverse all elements with integer keys
425 for _
, v
in ipairs(t
) do
426 rtable
.insert(tr
, 1, v
)
428 -- add the remaining elements
429 for k
, v
in pairs(t
) do
430 if type(k
) ~= "number" then
438 -- @param t the table to clone
439 -- @param deep Create a deep clone? (default: true)
440 -- @return a clone of t
441 function util
.table.clone(t
, deep
)
442 local deep
= deep
== nil and true or deep
444 for k
, v
in pairs(t
) do
445 if deep
and type(v
) == "table" then
446 c
[k
] = util
.table.clone(v
)
455 -- Returns an iterator to cycle through, starting from the first element or the
456 -- given index, all elments of a table that match a given criteria.
458 -- @param t the table to iterate
459 -- @param filter a function that returns true to indicate a positive match
460 -- @param start what index to start iterating from. Default is 1 (=> start of
462 function util
.table.iterate(t
, filter
, start
)
464 local index
= start
or 1
468 while count
< length
do
469 local item
= t
[index
]
470 index
= util
.cycle(#t
, index
+ 1)
472 if filter(item
) then return item
end
479 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80