summaryrefslogtreecommitdiff
path: root/lib/gears
diff options
context:
space:
mode:
authorache <ache@ache.one>2017-03-13 23:17:19 +0100
committerache <ache@ache.one>2017-03-13 23:17:19 +0100
commit22d656903563f75678f3634964731ccf93355dfd (patch)
treee3cb6279d95c9764093072d5e946566ea6533799 /lib/gears
Init commit
Diffstat (limited to 'lib/gears')
-rw-r--r--lib/gears/cache.lua51
-rw-r--r--lib/gears/color.lua346
-rw-r--r--lib/gears/debug.lua78
-rw-r--r--lib/gears/geometry.lua240
-rw-r--r--lib/gears/init.lua23
-rw-r--r--lib/gears/matrix.lua219
-rw-r--r--lib/gears/object.lua285
-rw-r--r--lib/gears/object/properties.lua88
-rw-r--r--lib/gears/protected_call.lua57
-rw-r--r--lib/gears/shape.lua785
-rw-r--r--lib/gears/surface.lua252
-rw-r--r--lib/gears/timer.lua187
-rw-r--r--lib/gears/wallpaper.lua221
13 files changed, 2832 insertions, 0 deletions
diff --git a/lib/gears/cache.lua b/lib/gears/cache.lua
new file mode 100644
index 0000000..dc5add5
--- /dev/null
+++ b/lib/gears/cache.lua
@@ -0,0 +1,51 @@
+---------------------------------------------------------------------------
+-- @author Uli Schlachter
+-- @copyright 2015 Uli Schlachter
+-- @classmod gears.cache
+---------------------------------------------------------------------------
+
+local select = select
+local setmetatable = setmetatable
+local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
+
+local cache = {}
+
+--- Get an entry from the cache, creating it if it's missing.
+-- @param ... Arguments for the creation callback. These are checked against the
+-- cache contents for equality.
+-- @return The entry from the cache
+function cache:get(...)
+ local result = self._cache
+ for i = 1, select("#", ...) do
+ local arg = select(i, ...)
+ local next = result[arg]
+ if not next then
+ next = {}
+ result[arg] = next
+ end
+ result = next
+ end
+ local ret = result._entry
+ if not ret then
+ ret = { self._creation_cb(...) }
+ result._entry = ret
+ end
+ return unpack(ret)
+end
+
+--- Create a new cache object. A cache keeps some data that can be
+-- garbage-collected at any time, but might be useful to keep.
+-- @param creation_cb Callback that is used for creating missing cache entries.
+-- @return A new cache object.
+function cache.new(creation_cb)
+ return setmetatable({
+ _cache = setmetatable({}, { __mode = "v" }),
+ _creation_cb = creation_cb
+ }, {
+ __index = cache
+ })
+end
+
+return setmetatable(cache, { __call = function(_, ...) return cache.new(...) end })
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/gears/color.lua b/lib/gears/color.lua
new file mode 100644
index 0000000..f0197c1
--- /dev/null
+++ b/lib/gears/color.lua
@@ -0,0 +1,346 @@
+---------------------------------------------------------------------------
+-- @author Uli Schlachter
+-- @copyright 2010 Uli Schlachter
+-- @module gears.color
+---------------------------------------------------------------------------
+
+local setmetatable = setmetatable
+local string = string
+local table = table
+local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
+local tonumber = tonumber
+local ipairs = ipairs
+local pairs = pairs
+local type = type
+local lgi = require("lgi")
+local cairo = lgi.cairo
+local Pango = lgi.Pango
+local surface = require("gears.surface")
+
+local color = { mt = {} }
+local pattern_cache
+
+--- Create a pattern from a given string.
+-- This function can create solid, linear, radial and png patterns. In general,
+-- patterns are specified as strings formatted as "type:arguments". "arguments"
+-- is specific to the pattern being used. For example, one can use
+-- "radial:50,50,10:55,55,30:0,#ff0000:0.5,#00ff00:1,#0000ff".
+-- Alternatively, patterns can be specified via tables. In this case, the
+-- table's 'type' member specifies the type. For example:
+-- {
+-- type = "radial",
+-- from = { 50, 50, 10 },
+-- to = { 55, 55, 30 },
+-- stops = { { 0, "#ff0000" }, { 0.5, "#00ff00" }, { 1, "#0000ff" } }
+-- }
+-- Any argument that cannot be understood is passed to @{create_solid_pattern}.
+--
+-- Please note that you MUST NOT modify the returned pattern, for example by
+-- calling :set_matrix() on it, because this function uses a cache and your
+-- changes could thus have unintended side effects. Use @{create_pattern_uncached}
+-- if you need to modify the returned pattern.
+-- @see create_pattern_uncached, create_solid_pattern, create_png_pattern,
+-- create_linear_pattern, create_radial_pattern
+-- @tparam string col The string describing the pattern.
+-- @return a cairo pattern object
+-- @function gears.color
+
+--- Parse a HTML-color.
+-- This function can parse colors like `#rrggbb` and `#rrggbbaa` and also `red`.
+-- Max 4 chars per channel.
+--
+-- @param col The color to parse
+-- @treturn table 4 values representing color in RGBA format (each of them in
+-- [0, 1] range) or nil if input is incorrect.
+-- @usage -- This will return 0, 1, 0, 1
+-- gears.color.parse_color("#00ff00ff")
+function color.parse_color(col)
+ local rgb = {}
+ if string.match(col, "^#%x+$") then
+ local hex_str = col:sub(2, #col)
+ local channels
+ if #hex_str % 3 == 0 then
+ channels = 3
+ elseif #hex_str % 4 == 0 then
+ channels = 4
+ else
+ return nil
+ end
+ local chars_per_channel = #hex_str / channels
+ if chars_per_channel > 4 then
+ return nil
+ end
+ local dividor = (0x10 ^ chars_per_channel) - 1
+ for idx=1,#hex_str,chars_per_channel do
+ local channel_val = tonumber(hex_str:sub(idx,idx+chars_per_channel-1), 16)
+ table.insert(rgb, channel_val / dividor)
+ end
+ if channels == 3 then
+ table.insert(rgb, 1)
+ end
+ else
+ local c = Pango.Color()
+ if not c:parse(col) then
+ return nil
+ end
+ rgb = {
+ c.red / 0xffff,
+ c.green / 0xffff,
+ c.blue / 0xffff,
+ 1.0
+ }
+ end
+ assert(#rgb == 4, col)
+ return unpack(rgb)
+end
+
+--- Find all numbers in a string
+--
+-- @tparam string s The string to parse
+-- @return Each number found as a separate value
+local function parse_numbers(s)
+ local res = {}
+ for k in string.gmatch(s, "-?[0-9]+[.]?[0-9]*") do
+ table.insert(res, tonumber(k))
+ end
+ return unpack(res)
+end
+
+--- Create a solid pattern
+--
+-- @param col The color for the pattern
+-- @return A cairo pattern object
+function color.create_solid_pattern(col)
+ if col == nil then
+ col = "#000000"
+ elseif type(col) == "table" then
+ col = col.color
+ end
+ return cairo.Pattern.create_rgba(color.parse_color(col))
+end
+
+--- Create an image pattern from a png file
+--
+-- @param file The filename of the file
+-- @return a cairo pattern object
+function color.create_png_pattern(file)
+ if type(file) == "table" then
+ file = file.file
+ end
+ local image = surface.load(file)
+ local pattern = cairo.Pattern.create_for_surface(image)
+ pattern:set_extend(cairo.Extend.REPEAT)
+ return pattern
+end
+
+--- Add stops to the given pattern.
+-- @param p The cairo pattern to add stops to
+-- @param iterator An iterator that returns strings. Each of those strings
+-- should be in the form place,color where place is in [0, 1].
+local function add_iterator_stops(p, iterator)
+ for k in iterator do
+ local sub = string.gmatch(k, "[^,]+")
+ local point, clr = sub(), sub()
+ p:add_color_stop_rgba(point, color.parse_color(clr))
+ end
+end
+
+--- Add a list of stops to a given pattern
+local function add_stops_table(pat, arg)
+ for _, stop in ipairs(arg) do
+ pat:add_color_stop_rgba(stop[1], color.parse_color(stop[2]))
+ end
+end
+
+--- Create a pattern from a string
+local function string_pattern(creator, arg)
+ local iterator = string.gmatch(arg, "[^:]+")
+ -- Create a table where each entry is a number from the original string
+ local args = { parse_numbers(iterator()) }
+ local to = { parse_numbers(iterator()) }
+ -- Now merge those two tables
+ for _, v in pairs(to) do
+ table.insert(args, v)
+ end
+ -- And call our creator function with the values
+ local p = creator(unpack(args))
+
+ add_iterator_stops(p, iterator)
+ return p
+end
+
+--- Create a linear pattern object.
+-- The pattern is created from a string. This string should have the following
+-- form: `"x0, y0:x1, y1:<stops>"`
+-- Alternatively, the pattern can be specified as a table:
+-- { type = "linear", from = { x0, y0 }, to = { x1, y1 },
+-- stops = { <stops> } }
+-- `x0,y0` and `x1,y1` are the start and stop point of the pattern.
+-- For the explanation of `<stops>`, see `color.create_pattern`.
+-- @tparam string|table arg The argument describing the pattern.
+-- @return a cairo pattern object
+function color.create_linear_pattern(arg)
+ local pat
+
+ if type(arg) == "string" then
+ return string_pattern(cairo.Pattern.create_linear, arg)
+ elseif type(arg) ~= "table" then
+ error("Wrong argument type: " .. type(arg))
+ end
+
+ pat = cairo.Pattern.create_linear(arg.from[1], arg.from[2], arg.to[1], arg.to[2])
+ add_stops_table(pat, arg.stops)
+ return pat
+end
+
+--- Create a radial pattern object.
+-- The pattern is created from a string. This string should have the following
+-- form: `"x0, y0, r0:x1, y1, r1:<stops>"`
+-- Alternatively, the pattern can be specified as a table:
+-- { type = "radial", from = { x0, y0, r0 }, to = { x1, y1, r1 },
+-- stops = { <stops> } }
+-- `x0,y0` and `x1,y1` are the start and stop point of the pattern.
+-- `r0` and `r1` are the radii of the start / stop circle.
+-- For the explanation of `<stops>`, see `color.create_pattern`.
+-- @tparam string|table arg The argument describing the pattern
+-- @return a cairo pattern object
+function color.create_radial_pattern(arg)
+ local pat
+
+ if type(arg) == "string" then
+ return string_pattern(cairo.Pattern.create_radial, arg)
+ elseif type(arg) ~= "table" then
+ error("Wrong argument type: " .. type(arg))
+ end
+
+ pat = cairo.Pattern.create_radial(arg.from[1], arg.from[2], arg.from[3],
+ arg.to[1], arg.to[2], arg.to[3])
+ add_stops_table(pat, arg.stops)
+ return pat
+end
+
+--- Mapping of all supported color types. New entries can be added.
+color.types = {
+ solid = color.create_solid_pattern,
+ png = color.create_png_pattern,
+ linear = color.create_linear_pattern,
+ radial = color.create_radial_pattern
+}
+
+--- Create a pattern from a given string.
+-- For full documentation of this function, please refer to
+-- `color.create_pattern`. The difference between `color.create_pattern`
+-- and this function is that this function does not insert the generated
+-- objects into the pattern cache. Thus, you are allowed to modify the
+-- returned object.
+-- @see create_pattern
+-- @param col The string describing the pattern.
+-- @return a cairo pattern object
+function color.create_pattern_uncached(col)
+ -- If it already is a cairo pattern, just leave it as that
+ if cairo.Pattern:is_type_of(col) then
+ return col
+ end
+ col = col or "#000000"
+ if type(col) == "string" then
+ local t = string.match(col, "[^:]+")
+ if color.types[t] then
+ local pos = string.len(t)
+ local arg = string.sub(col, pos + 2)
+ return color.types[t](arg)
+ end
+ elseif type(col) == "table" then
+ local t = col.type
+ if color.types[t] then
+ return color.types[t](col)
+ end
+ end
+ return color.create_solid_pattern(col)
+end
+
+--- Create a pattern from a given string, same as `gears.color`.
+-- @see gears.color
+function color.create_pattern(col)
+ if cairo.Pattern:is_type_of(col) then
+ return col
+ end
+ return pattern_cache:get(col or "#000000")
+end
+
+--- Check if a pattern is opaque.
+-- A pattern is transparent if the background on which it gets drawn (with
+-- operator OVER) doesn't influence the visual result.
+-- @param col An argument that `create_pattern` accepts.
+-- @return The pattern if it is surely opaque, else nil
+function color.create_opaque_pattern(col)
+ local pattern = color.create_pattern(col)
+ local kind = pattern:get_type()
+
+ if kind == "SOLID" then
+ local _, _, _, _, alpha = pattern:get_rgba()
+ if alpha ~= 1 then
+ return
+ end
+ return pattern
+ elseif kind == "SURFACE" then
+ local status, surf = pattern:get_surface()
+ if status ~= "SUCCESS" or surf.content ~= "COLOR" then
+ -- The surface has an alpha channel which *might* be non-opaque
+ return
+ end
+
+ -- Only the "NONE" extend mode is forbidden, everything else doesn't
+ -- introduce transparent parts
+ if pattern:get_extend() == "NONE" then
+ return
+ end
+
+ return pattern
+ elseif kind == "LINEAR" then
+ local _, stops = pattern:get_color_stop_count()
+
+ -- No color stops or extend NONE -> pattern *might* contain transparency
+ if stops == 0 or pattern:get_extend() == "NONE" then
+ return
+ end
+
+ -- Now check if any of the color stops contain transparency
+ for i = 0, stops - 1 do
+ local _, _, _, _, _, alpha = pattern:get_color_stop_rgba(i)
+ if alpha ~= 1 then
+ return
+ end
+ end
+ return pattern
+ end
+
+ -- Unknown type, e.g. mesh or raster source or unsupported type (radial
+ -- gradients can do weird self-intersections)
+end
+
+--- Fill non-transparent area of an image with a given color.
+-- @param image Image or path to it.
+-- @param new_color New color.
+-- @return Recolored image.
+function color.recolor_image(image, new_color)
+ if type(image) == 'string' then
+ image = surface.duplicate_surface(image)
+ end
+ local cr = cairo.Context.create(image)
+ cr:set_source(color.create_pattern(new_color))
+ cr:mask(cairo.Pattern.create_for_surface(image), 0, 0)
+ return image
+end
+
+function color.mt.__call(_, ...)
+ return color.create_pattern(...)
+end
+
+pattern_cache = require("gears.cache").new(color.create_pattern_uncached)
+
+--- No color
+color.transparent = color.create_pattern("#00000000")
+
+return setmetatable(color, color.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/gears/debug.lua b/lib/gears/debug.lua
new file mode 100644
index 0000000..55f72f5
--- /dev/null
+++ b/lib/gears/debug.lua
@@ -0,0 +1,78 @@
+---------------------------------------------------------------------------
+-- @author Uli Schlachter
+-- @copyright 2010 Uli Schlachter
+-- @module gears.debug
+---------------------------------------------------------------------------
+
+local tostring = tostring
+local print = print
+local type = type
+local pairs = pairs
+
+local debug = {}
+
+--- Given a table (or any other data) return a string that contains its
+-- tag, value and type. If data is a table then recursively call `dump_raw`
+-- on each of its values.
+-- @param data Value to inspect.
+-- @param shift Spaces to indent lines with.
+-- @param tag The name of the value.
+-- @tparam[opt=10] int depth Depth of recursion.
+-- @return a string which contains tag, value, value type and table key/value
+-- pairs if data is a table.
+local function dump_raw(data, shift, tag, depth)
+ depth = depth == nil and 10 or depth or 0
+ local result = ""
+
+ if tag then
+ result = result .. tostring(tag) .. " : "
+ end
+
+ if type(data) == "table" and depth > 0 then
+ shift = (shift or "") .. " "
+ result = result .. tostring(data)
+ for k, v in pairs(data) do
+ result = result .. "\n" .. shift .. dump_raw(v, shift, k, depth - 1)
+ end
+ else
+ result = result .. tostring(data) .. " (" .. type(data) .. ")"
+ if depth == 0 and type(data) == "table" then
+ result = result .. " […]"
+ end
+ end
+
+ return result
+end
+
+--- Inspect the value in data.
+-- @param data Value to inspect.
+-- @param tag The name of the value.
+-- @tparam[opt] int depth Depth of recursion.
+-- @return string A string that contains the expanded value of data.
+function debug.dump_return(data, tag, depth)
+ return dump_raw(data, nil, tag, depth)
+end
+
+--- Print the table (or any other value) to the console.
+-- @param data Table to print.
+-- @param tag The name of the table.
+-- @tparam[opt] int depth Depth of recursion.
+function debug.dump(data, tag, depth)
+ print(debug.dump_return(data, tag, depth))
+end
+
+--- Print an warning message
+-- @tparam string message The warning message to print
+function debug.print_warning(message)
+ io.stderr:write(os.date("%Y-%m-%d %T W: ") .. tostring(message) .. "\n")
+end
+
+--- Print an error message
+-- @tparam string message The error message to print
+function debug.print_error(message)
+ io.stderr:write(os.date("%Y-%m-%d %T E: ") .. tostring(message) .. "\n")
+end
+
+return debug
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/gears/geometry.lua b/lib/gears/geometry.lua
new file mode 100644
index 0000000..a429abd
--- /dev/null
+++ b/lib/gears/geometry.lua
@@ -0,0 +1,240 @@
+---------------------------------------------------------------------------
+--
+-- Helper functions used to compute geometries.
+--
+-- When this module refer to a geometry table, this assume a table with at least
+-- an *x*, *y*, *width* and *height* keys and numeric values.
+--
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2008 Julien Danjou
+-- @module gears.geometry
+---------------------------------------------------------------------------
+local math = math
+
+local gears = {geometry = {rectangle = {} } }
+
+--- Get the square distance between a rectangle and a point.
+-- @tparam table geom A rectangle
+-- @tparam number geom.x The horizontal coordinate
+-- @tparam number geom.y The vertical coordinate
+-- @tparam number geom.width The rectangle width
+-- @tparam number geom.height The rectangle height
+-- @tparam number x X coordinate of point
+-- @tparam number y Y coordinate of point
+-- @treturn number The squared distance of the rectangle to the provided point
+function gears.geometry.rectangle.get_square_distance(geom, x, y)
+ local dist_x, dist_y = 0, 0
+ if x < geom.x then
+ dist_x = geom.x - x
+ elseif x >= geom.x + geom.width then
+ dist_x = x - geom.x - geom.width + 1
+ end
+ if y < geom.y then
+ dist_y = geom.y - y
+ elseif y >= geom.y + geom.height then
+ dist_y = y - geom.y - geom.height + 1
+ end
+ return dist_x * dist_x + dist_y * dist_y
+end
+
+--- Return the closest rectangle from `list` for a given point.
+-- @tparam table list A list of geometry tables.
+-- @tparam number x The x coordinate
+-- @tparam number y The y coordinate
+-- @return The key from the closest geometry.
+function gears.geometry.rectangle.get_closest_by_coord(list, x, y)
+ local dist = math.huge
+ local ret = nil
+
+ for k, v in pairs(list) do
+ local d = gears.geometry.rectangle.get_square_distance(v, x, y)
+ if d < dist then
+ ret, dist = k, d
+ end
+ end
+
+ return ret
+end
+
+--- Return the rectangle containing the [x, y] point.
+--
+-- Note that if multiple element from the geometry list contains the point, the
+-- returned result is nondeterministic.
+--
+-- @tparam table list A list of geometry tables.
+-- @tparam number x The x coordinate
+-- @tparam number y The y coordinate
+-- @return The key from the closest geometry. In case no result is found, *nil*
+-- is returned.
+function gears.geometry.rectangle.get_by_coord(list, x, y)
+ for k, geometry in pairs(list) do
+ if x >= geometry.x and x < geometry.x + geometry.width
+ and y >= geometry.y and y < geometry.y + geometry.height then
+ return k
+ end
+ end
+end
+
+--- Return true whether rectangle B is in the right direction
+-- compared to rectangle A.
+-- @param dir The direction.
+-- @param gA The geometric specification for rectangle A.
+-- @param gB The geometric specification for rectangle B.
+-- @return True if B is in the direction of A.
+local function is_in_direction(dir, gA, gB)
+ if dir == "up" then
+ return gA.y > gB.y
+ elseif dir == "down" then
+ return gA.y < gB.y
+ elseif dir == "left" then
+ return gA.x > gB.x
+ elseif dir == "right" then
+ return gA.x < gB.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 screen and the left side of the checked screen.
+-- @param dir The direction.
+-- @param _gA The first rectangle.
+-- @param _gB The second rectangle.
+-- @return The distance between the screens.
+local function calculate_distance(dir, _gA, _gB)
+ local gAx = _gA.x
+ local gAy = _gA.y
+ local gBx = _gB.x
+ local gBy = _gB.y
+
+ if dir == "up" then
+ gBy = _gB.y + _gB.height
+ elseif dir == "down" then
+ gAy = _gA.y + _gA.height
+ elseif dir == "left" then
+ gBx = _gB.x + _gB.width
+ elseif dir == "right" then
+ gAx = _gA.x + _gA.width
+ end
+
+ return math.sqrt(math.pow(gBx - gAx, 2) + math.pow(gBy - gAy, 2))
+end
+
+--- Get the nearest rectangle in the given direction. Every rectangle is specified as a table
+-- with *x*, *y*, *width*, *height* keys, the same as client or screen geometries.
+-- @tparam string dir The direction, can be either *up*, *down*, *left* or *right*.
+-- @tparam table recttbl A table of rectangle specifications.
+-- @tparam table cur The current rectangle.
+-- @return The index for the rectangle in recttbl closer to cur in the given direction. nil if none found.
+function gears.geometry.rectangle.get_in_direction(dir, recttbl, cur)
+ local dist, dist_min
+ local target = nil
+
+ -- We check each object
+ for i, rect in pairs(recttbl) do
+ -- Check geometry to see if object is located in the right direction.
+ if is_in_direction(dir, cur, rect) then
+ -- Calculate distance between current and checked object.
+ dist = calculate_distance(dir, cur, rect)
+
+ -- If distance is shorter then keep the object.
+ if not target or dist < dist_min then
+ target = i
+ dist_min = dist
+ end
+ end
+ end
+ return target
+end
+
+--- Check if an area intersect another area.
+-- @param a The area.
+-- @param b The other area.
+-- @return True if they intersect, false otherwise.
+local function area_intersect_area(a, b)
+ return (b.x < a.x + a.width
+ and b.x + b.width > a.x
+ and b.y < a.y + a.height
+ and b.y + b.height > a.y)
+end
+
+--- Get the intersect area between a and b.
+-- @tparam table a The area.
+-- @tparam number a.x The horizontal coordinate
+-- @tparam number a.y The vertical coordinate
+-- @tparam number a.width The rectangle width
+-- @tparam number a.height The rectangle height
+-- @tparam table b The other area.
+-- @tparam number b.x The horizontal coordinate
+-- @tparam number b.y The vertical coordinate
+-- @tparam number b.width The rectangle width
+-- @tparam number b.height The rectangle height
+-- @treturn table The intersect area.
+function gears.geometry.rectangle.get_intersection(a, b)
+ local g = {}
+ g.x = math.max(a.x, b.x)
+ g.y = math.max(a.y, b.y)
+ g.width = math.min(a.x + a.width, b.x + b.width) - g.x
+ g.height = math.min(a.y + a.height, b.y + b.height) - g.y
+ return g
+end
+
+--- Remove an area from a list, splitting the space between several area that
+-- can overlap.
+-- @tparam table areas Table of areas.
+-- @tparam table elem Area to remove.
+-- @tparam number elem.x The horizontal coordinate
+-- @tparam number elem.y The vertical coordinate
+-- @tparam number elem.width The rectangle width
+-- @tparam number elem.height The rectangle height
+-- @return The new area list.
+function gears.geometry.rectangle.area_remove(areas, elem)
+ for i = #areas, 1, -1 do
+ -- Check if the 'elem' intersect
+ if area_intersect_area(areas[i], elem) then
+ -- It does? remove it
+ local r = table.remove(areas, i)
+ local inter = gears.geometry.rectangle.get_intersection(r, elem)
+
+ if inter.x > r.x then
+ table.insert(areas, {
+ x = r.x,
+ y = r.y,
+ width = inter.x - r.x,
+ height = r.height
+ })
+ end
+
+ if inter.y > r.y then
+ table.insert(areas, {
+ x = r.x,
+ y = r.y,
+ width = r.width,
+ height = inter.y - r.y
+ })
+ end
+
+ if inter.x + inter.width < r.x + r.width then
+ table.insert(areas, {
+ x = inter.x + inter.width,
+ y = r.y,
+ width = (r.x + r.width) - (inter.x + inter.width),
+ height = r.height
+ })
+ end
+
+ if inter.y + inter.height < r.y + r.height then
+ table.insert(areas, {
+ x = r.x,
+ y = inter.y + inter.height,
+ width = r.width,
+ height = (r.y + r.height) - (inter.y + inter.height)
+ })
+ end
+ end
+ end
+
+ return areas
+end
+
+return gears.geometry
diff --git a/lib/gears/init.lua b/lib/gears/init.lua
new file mode 100644
index 0000000..eae92ee
--- /dev/null
+++ b/lib/gears/init.lua
@@ -0,0 +1,23 @@
+---------------------------------------------------------------------------
+-- @author Uli Schlachter
+-- @copyright 2010 Uli Schlachter
+-- @module gears
+---------------------------------------------------------------------------
+
+
+return
+{
+ color = require("gears.color");
+ debug = require("gears.debug");
+ object = require("gears.object");
+ surface = require("gears.surface");
+ wallpaper = require("gears.wallpaper");
+ timer = require("gears.timer");
+ cache = require("gears.cache");
+ matrix = require("gears.matrix");
+ shape = require("gears.shape");
+ protected_call = require("gears.protected_call");
+ geometry = require("gears.geometry");
+}
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/gears/matrix.lua b/lib/gears/matrix.lua
new file mode 100644
index 0000000..a6bc975
--- /dev/null
+++ b/lib/gears/matrix.lua
@@ -0,0 +1,219 @@
+---------------------------------------------------------------------------
+-- An implementation of matrices for describing and working with affine
+-- transformations.
+-- @author Uli Schlachter
+-- @copyright 2015 Uli Schlachter
+-- @classmod gears.matrix
+---------------------------------------------------------------------------
+
+local cairo = require("lgi").cairo
+local matrix = {}
+
+-- Metatable for matrix instances. This is set up near the end of the file.
+local matrix_mt = {}
+
+--- Create a new matrix instance
+-- @tparam number xx The xx transformation part.
+-- @tparam number yx The yx transformation part.
+-- @tparam number xy The xy transformation part.
+-- @tparam number yy The yy transformation part.
+-- @tparam number x0 The x0 transformation part.
+-- @tparam number y0 The y0 transformation part.
+-- @return A new matrix describing the given transformation.
+function matrix.create(xx, yx, xy, yy, x0, y0)
+ return setmetatable({
+ xx = xx, xy = xy, x0 = x0,
+ yx = yx, yy = yy, y0 = y0
+ }, matrix_mt)
+end
+
+--- Create a new translation matrix
+-- @tparam number x The translation in x direction.
+-- @tparam number y The translation in y direction.
+-- @return A new matrix describing the given transformation.
+function matrix.create_translate(x, y)
+ return matrix.create(1, 0, 0, 1, x, y)
+end
+
+--- Create a new scaling matrix
+-- @tparam number sx The scaling in x direction.
+-- @tparam number sy The scaling in y direction.
+-- @return A new matrix describing the given transformation.
+function matrix.create_scale(sx, sy)
+ return matrix.create(sx, 0, 0, sy, 0, 0)
+end
+
+--- Create a new rotation matrix
+-- @tparam number angle The angle of the rotation in radians.
+-- @return A new matrix describing the given transformation.
+function matrix.create_rotate(angle)
+ local c, s = math.cos(angle), math.sin(angle)
+ return matrix.create(c, s, -s, c, 0, 0)
+end
+
+--- Create a new rotation matrix rotating around a custom point
+-- @tparam number x The horizontal rotation point
+-- @tparam number y The vertical rotation point
+-- @tparam number angle The angle of the rotation in radians.
+-- @return A new matrix describing the given transformation.
+function matrix.create_rotate_at(x, y, angle)
+ return matrix.create_translate( -x, -y )
+ * matrix.create_rotate ( angle )
+ * matrix.create_translate( x, y )
+end
+
+--- Translate this matrix
+-- @tparam number x The translation in x direction.
+-- @tparam number y The translation in y direction.
+-- @return A new matrix describing the new transformation.
+function matrix:translate(x, y)
+ return matrix.create_translate(x, y):multiply(self)
+end
+
+--- Scale this matrix
+-- @tparam number sx The scaling in x direction.
+-- @tparam number sy The scaling in y direction.
+-- @return A new matrix describing the new transformation.
+function matrix:scale(sx, sy)
+ return matrix.create_scale(sx, sy):multiply(self)
+end
+
+--- Rotate this matrix
+-- @tparam number angle The angle of the rotation in radians.
+-- @return A new matrix describing the new transformation.
+function matrix:rotate(angle)
+ return matrix.create_rotate(angle):multiply(self)
+end
+
+--- Rotate a shape from a custom point
+-- @tparam number x The horizontal rotation point
+-- @tparam number y The vertical rotation point
+-- @tparam number angle The angle (in radiant: -2*math.pi to 2*math.pi)
+-- @return A transformation object
+function matrix:rotate_at(x, y, angle)
+ return self * matrix.create_rotate_at(x, y, angle)
+end
+
+--- Invert this matrix
+-- @return A new matrix describing the inverse transformation.
+function matrix:invert()
+ -- Beware of math! (I just copied the algorithm from cairo's source code)
+ local a, b, c, d, x0, y0 = self.xx, self.yx, self.xy, self.yy, self.x0, self.y0
+ local inv_det = 1/(a*d - b*c)
+ return matrix.create(inv_det * d, inv_det * -b,
+ inv_det * -c, inv_det * a,
+ inv_det * (c * y0 - d * x0), inv_det * (b * x0 - a * y0))
+end
+
+--- Multiply this matrix with another matrix.
+-- The resulting matrix describes a transformation that is equivalent to first
+-- applying this transformation and then the transformation from `other`.
+-- Note that this function can also be called by directly multiplicating two
+-- matrix instances: `a * b == a:multiply(b)`.
+-- @tparam gears.matrix|cairo.Matrix other The other matrix to multiply with.
+-- @return The multiplication result.
+function matrix:multiply(other)
+ local ret = matrix.create(self.xx * other.xx + self.yx * other.xy,
+ self.xx * other.yx + self.yx * other.yy,
+ self.xy * other.xx + self.yy * other.xy,
+ self.xy * other.yx + self.yy * other.yy,
+ self.x0 * other.xx + self.y0 * other.xy + other.x0,
+ self.x0 * other.yx + self.y0 * other.yy + other.y0)
+
+ return ret
+end
+
+--- Check if two matrices are equal.
+-- Note that this function cal also be called by directly comparing two matrix
+-- instances: `a == b`.
+-- @tparam gears.matrix|cairo.Matrix other The matrix to compare with.
+-- @return True if this and the other matrix are equal.
+function matrix:equals(other)
+ for _, k in pairs{ "xx", "xy", "yx", "yy", "x0", "y0" } do
+ if self[k] ~= other[k] then
+ return false
+ end
+ end
+ return true
+end
+
+--- Get a string representation of this matrix
+-- @return A string showing this matrix in column form.
+function matrix:tostring()
+ return string.format("[[%g, %g], [%g, %g], [%g, %g]]",
+ self.xx, self.yx, self.xy,
+ self.yy, self.x0, self.y0)
+end
+
+--- Transform a distance by this matrix.
+-- The difference to @{matrix:transform_point} is that the translation part of
+-- this matrix is ignored.
+-- @tparam number x The x coordinate of the point.
+-- @tparam number y The y coordinate of the point.
+-- @treturn number The x coordinate of the transformed point.
+-- @treturn number The x coordinate of the transformed point.
+function matrix:transform_distance(x, y)
+ return self.xx * x + self.xy * y, self.yx * x + self.yy * y
+end
+
+--- Transform a point by this matrix.
+-- @tparam number x The x coordinate of the point.
+-- @tparam number y The y coordinate of the point.
+-- @treturn number The x coordinate of the transformed point.
+-- @treturn number The y coordinate of the transformed point.
+function matrix:transform_point(x, y)
+ x, y = self:transform_distance(x, y)
+ return self.x0 + x, self.y0 + y
+end
+
+--- Calculate a bounding rectangle for transforming a rectangle by a matrix.
+-- @tparam number x The x coordinate of the rectangle.
+-- @tparam number y The y coordinate of the rectangle.
+-- @tparam number width The width of the rectangle.
+-- @tparam number height The height of the rectangle.
+-- @treturn number X coordinate of the bounding rectangle.
+-- @treturn number Y coordinate of the bounding rectangle.
+-- @treturn number Width of the bounding rectangle.
+-- @treturn number Height of the bounding rectangle.
+function matrix:transform_rectangle(x, y, width, height)
+ -- Transform all four corners of the rectangle
+ local x1, y1 = self:transform_point(x, y)
+ local x2, y2 = self:transform_point(x, y + height)
+ local x3, y3 = self:transform_point(x + width, y + height)
+ local x4, y4 = self:transform_point(x + width, y)
+ -- Find the extremal points of the result
+ x = math.min(x1, x2, x3, x4)
+ y = math.min(y1, y2, y3, y4)
+ width = math.max(x1, x2, x3, x4) - x
+ height = math.max(y1, y2, y3, y4) - y
+
+ return x, y, width, height
+end
+
+--- Convert to a cairo matrix
+-- @treturn cairo.Matrix A cairo matrix describing the same transformation.
+function matrix:to_cairo_matrix()
+ local ret = cairo.Matrix()
+ ret:init(self.xx, self.yx, self.xy, self.yy, self.x0, self.y0)
+ return ret
+end
+
+--- Convert to a cairo matrix
+-- @tparam cairo.Matrix mat A cairo matrix describing the sought transformation
+-- @treturn gears.matrix A matrix instance describing the same transformation.
+function matrix.from_cairo_matrix(mat)
+ return matrix.create(mat.xx, mat.yx, mat.xy, mat.yy, mat.x0, mat.y0)
+end
+
+matrix_mt.__index = matrix
+matrix_mt.__newindex = error
+matrix_mt.__eq = matrix.equals
+matrix_mt.__mul = matrix.multiply
+matrix_mt.__tostring = matrix.tostring
+
+--- A constant for the identity matrix.
+matrix.identity = matrix.create(1, 0, 0, 1, 0, 0)
+
+return matrix
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/gears/object.lua b/lib/gears/object.lua
new file mode 100644
index 0000000..e6436e3
--- /dev/null
+++ b/lib/gears/object.lua
@@ -0,0 +1,285 @@
+---------------------------------------------------------------------------
+-- The object oriented programming base class used by various Awesome
+-- widgets and components.
+--
+-- It provide basic observer pattern, signaling and dynamic properties.
+--
+-- @author Uli Schlachter
+-- @copyright 2010 Uli Schlachter
+-- @classmod gears.object
+---------------------------------------------------------------------------
+
+local setmetatable = setmetatable
+local pairs = pairs
+local type = type
+local error = error
+local properties = require("gears.object.properties")
+
+local object = { properties = properties, mt = {} }
+
+--- Verify that obj is indeed a valid object as returned by new()
+local function check(obj)
+ if type(obj) ~= "table" or type(obj._signals) ~= "table" then
+ error("called on non-object")
+ end
+end
+
+--- Find a given signal
+-- @tparam table obj The object to search in
+-- @tparam string name The signal to find
+-- @treturn table The signal table
+local function find_signal(obj, name)
+ check(obj)
+ if not obj._signals[name] then
+ assert(type(name) == "string", "name must be a string, got: " .. type(name))
+ obj._signals[name] = {
+ strong = {},
+ weak = setmetatable({}, { __mode = "kv" })
+ }
+ end
+ return obj._signals[name]
+end
+
+function object.add_signal()
+ require("awful.util").deprecate("Use signals without explicitly adding them. This is now done implicitly.")
+end
+
+--- Connect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+function object:connect_signal(name, func)
+ assert(type(func) == "function", "callback must be a function, got: " .. type(func))
+ local sig = find_signal(self, name)
+ assert(sig.weak[func] == nil, "Trying to connect a strong callback which is already connected weakly")
+ sig.strong[func] = true
+end
+
+local function make_the_gc_obey(func)
+ if _VERSION <= "Lua 5.1" then
+ -- Lua 5.1 only has the behaviour we want if a userdata is used as the
+ -- value in a weak table. Thus, do some magic so that we get a userdata.
+
+ -- luacheck: globals newproxy getfenv setfenv
+ local userdata = newproxy(true)
+ getmetatable(userdata).__gc = function() end
+ -- Now bind the lifetime of userdata to the lifetime of func. For this,
+ -- we mess with the function's environment and add a table for all the
+ -- various userdata that it should keep alive.
+ local key = "_secret_key_used_by_gears_object_in_Lua51"
+ local old_env = getfenv(func)
+ if old_env[key] then
+ -- Assume the code in the else branch added this and the function
+ -- already has its own, private environment
+ table.insert(old_env[key], userdata)
+ else
+ -- No table yet, add it
+ local new_env = { [key] = { userdata } }
+ setmetatable(new_env, { __index = old_env, __newindex = old_env })
+ setfenv(func, new_env)
+ end
+ assert(_G[key] == nil, "Something broke, things escaped to _G")
+ return userdata
+ end
+ -- Lua 5.2+ already behaves the way we want with functions directly, no magic
+ return func
+end
+
+--- Connect to a signal weakly. This allows the callback function to be garbage
+-- collected and automatically disconnects the signal when that happens.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+function object:weak_connect_signal(name, func)
+ assert(type(func) == "function", "callback must be a function, got: " .. type(func))
+ local sig = find_signal(self, name)
+ assert(sig.strong[func] == nil, "Trying to connect a weak callback which is already connected strongly")
+ sig.weak[func] = make_the_gc_obey(func)
+end
+
+--- Disonnect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback that should be disconnected
+function object:disconnect_signal(name, func)
+ local sig = find_signal(self, name)
+ sig.weak[func] = nil
+ sig.strong[func] = nil
+end
+
+--- Emit a signal.
+--
+-- @tparam string name The name of the signal
+-- @param ... Extra arguments for the callback functions. Each connected
+-- function receives the object as first argument and then any extra arguments
+-- that are given to emit_signal()
+function object:emit_signal(name, ...)
+ local sig = find_signal(self, name)
+ for func in pairs(sig.strong) do
+ func(self, ...)
+ end
+ for func in pairs(sig.weak) do
+ func(self, ...)
+ end
+end
+
+local function get_miss(self, key)
+ local class = rawget(self, "_class")
+
+ if rawget(self, "get_"..key) then
+ return rawget(self, "get_"..key)(self)
+ elseif class and class["get_"..key] then
+ return class["get_"..key](self)
+ elseif class then
+ return class[key]
+ end
+
+end
+
+local function set_miss(self, key, value)
+ local class = rawget(self, "_class")
+
+ if rawget(self, "set_"..key) then
+ return rawget(self, "set_"..key)(self, value)
+ elseif class and class["set_"..key] then
+ return class["set_"..key](self, value)
+ elseif rawget(self, "_enable_auto_signals") then
+ local changed = class[key] ~= value
+ class[key] = value
+
+ if changed then
+ self:emit_signal("property::"..key, value)
+ end
+ elseif (not rawget(self, "get_"..key))
+ and not (class and class["get_"..key]) then
+ return rawset(self, key, value)
+ else
+ error("Cannot set '" .. tostring(key) .. "' on " .. tostring(self)
+ .. " because it is read-only")
+ end
+end
+
+--- Returns a new object. You can call `:emit_signal()`, `:disconnect_signal()`
+-- and `:connect_signal()` on the resulting object.
+--
+-- Note that `args.enable_auto_signals` is only supported when
+-- `args.enable_properties` is true.
+--
+--
+--
+--
+--**Usage example output**:
+--
+-- In get foo bar
+-- bar
+-- In set foo 42
+-- In get foo 42
+-- 42
+-- In a mathod 1 2 3
+-- nil
+-- In the connection handler! a cow
+-- a cow
+--
+--
+-- @usage
+-- -- Create a class for this object. It will be used as a backup source for
+-- -- methods and accessors. It is also possible to set them directly on the
+-- -- object.
+--local class = {}
+--function class:get_foo()
+-- print('In get foo', self._foo or 'bar')
+-- return self._foo or 'bar'
+--end
+--function class:set_foo(value)
+-- print('In set foo', value)
+-- -- In case it is necessary to bypass the object property system, use
+-- -- `rawset`
+-- rawset(self, '_foo', value)
+-- -- When using custom accessors, the signals need to be handled manually
+-- self:emit_signal('property::foo', value)
+--end
+--function class:method(a, b, c)
+-- print('In a mathod', a, b, c)
+--end
+--local o = gears.object {
+-- class = class,
+-- enable_properties = true,
+-- enable_auto_signals = true,
+--}
+--print(o.foo)
+--o.foo = 42
+--print(o.foo)
+--o:method(1, 2, 3)
+-- -- Random properties can also be added, the signal will be emitted automatically.
+--o:connect_signal('property::something', function(obj, value)
+-- assert(obj == o)
+-- print('In the connection handler!', value)
+--end)
+--print(o.something)
+--o.something = 'a cow'
+--print(o.something)
+-- @tparam[opt={}] table args The arguments
+-- @tparam[opt=false] boolean args.enable_properties Automatically call getters and setters
+-- @tparam[opt=false] boolean args.enable_auto_signals Generate "property::xxxx" signals
+-- when an unknown property is set.
+-- @tparam[opt=nil] table args.class
+-- @treturn table A new object
+-- @function gears.object
+local function new(args)
+ args = args or {}
+ local ret = {}
+
+ -- Automatic signals cannot work without both miss handlers.
+ assert(not (args.enable_auto_signals and args.enable_properties ~= true))
+
+ -- Copy all our global functions to our new object
+ for k, v in pairs(object) do
+ if type(v) == "function" then
+ ret[k] = v
+ end
+ end
+
+ ret._signals = {}
+
+ local mt = {}
+
+ -- Look for methods in another table
+ ret._class = args.class
+ ret._enable_auto_signals = args.enable_auto_signals
+
+ -- To catch all changes, a proxy is required
+ if args.enable_auto_signals then
+ ret._class = ret._class and setmetatable({}, {__index = args.class}) or {}
+ end
+
+ if args.enable_properties then
+ -- Check got existing get_xxxx and set_xxxx
+ mt.__index = get_miss
+ mt.__newindex = set_miss
+ elseif args.class then
+ -- Use the class table a miss handler
+ mt.__index = ret._class
+ end
+
+ return setmetatable(ret, mt)
+end
+
+function object.mt.__call(_, ...)
+ return new(...)
+end
+
+--- Helper function to get the module name out of `debug.getinfo`.
+-- @usage
+-- local mt = {}
+-- mt.__tostring = function(o)
+-- return require("gears.object").modulename(2)
+-- end
+-- return setmetatable(ret, mt)
+--
+-- @tparam[opt=2] integer level Level for `debug.getinfo(level, "S")`.
+-- Typically 2 or 3.
+-- @treturn string The module name, e.g. "wibox.container.background".
+function object.modulename(level)
+ return debug.getinfo(level, "S").source:gsub(".*/lib/", ""):gsub("/", "."):gsub("%.lua", "")
+end
+
+return setmetatable(object, object.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/gears/object/properties.lua b/lib/gears/object/properties.lua
new file mode 100644
index 0000000..36b8fcb
--- /dev/null
+++ b/lib/gears/object/properties.lua
@@ -0,0 +1,88 @@
+---------------------------------------------------------------------------
+--- An helper module to map userdata __index and __newindex entries to
+-- lua classes.
+--
+-- @author Emmanuel Lepage-Vallee &lt;elv1313@gmail.com&gt;
+-- @copyright 2016 Emmanuel Lepage-Vallee
+-- @module gears.object.properties
+---------------------------------------------------------------------------
+
+local object = {}
+
+
+--- Add the missing properties handler to a CAPI object such as client/tag/screen.
+-- Valid args:
+--
+-- * **getter**: A smart getter (handle property getter itself)
+-- * **getter_fallback**: A dumb getter method (don't handle individual property getter)
+-- * **getter_class**: A module with individual property getter/setter
+-- * **getter_prefix**: A special getter prefix (like "get" or "get_" (default))
+-- * **setter**: A smart setter (handle property setter itself)
+-- * **setter_fallback**: A dumb setter method (don't handle individual property setter)
+-- * **setter_class**: A module with individual property getter/setter
+-- * **setter_prefix**: A special setter prefix (like "set" or "set_" (default))
+-- * **auto_emit**: Emit "property::___" automatically (default: false). This is
+-- ignored when setter_fallback is set or a setter is found
+--
+-- @param class A standard luaobject derived object
+-- @tparam[opt={}] table args A set of accessors configuration parameters
+function object.capi_index_fallback(class, args)
+ args = args or {}
+
+ local getter_prefix = args.getter_prefix or "get_"
+ local setter_prefix = args.setter_prefix or "set_"
+
+ local getter = args.getter or function(cobj, prop)
+ -- Look for a getter method
+ if args.getter_class and args.getter_class[getter_prefix..prop] then
+ return args.getter_class[getter_prefix..prop](cobj)
+ elseif args.getter_class and args.getter_class["is_"..prop] then
+ return args.getter_class["is_"..prop](cobj)
+ end
+
+ -- Make sure something like c:a_mutator() works
+ if args.getter_class and args.getter_class[prop] then
+ return args.getter_class[prop]
+ end
+ -- In case there is already a "dumb" getter like `awful.tag.getproperty'
+ if args.getter_fallback then
+ return args.getter_fallback(cobj, prop)
+ end
+
+ -- Use the fallback property table
+ return cobj.data[prop]
+ end
+
+ local setter = args.setter or function(cobj, prop, value)
+ -- Look for a setter method
+ if args.setter_class and args.setter_class[setter_prefix..prop] then
+ return args.setter_class[setter_prefix..prop](cobj, value)
+ end
+
+ -- In case there is already a "dumb" setter like `awful.client.property.set'
+ if args.setter_fallback then
+ return args.setter_fallback(cobj, prop, value)
+ end
+
+ -- If a getter exists but not a setter, then the property is read-only
+ if args.getter_class and args.getter_class[getter_prefix..prop] then
+ return
+ end
+
+ -- Use the fallback property table
+ cobj.data[prop] = value
+
+ -- Emit the signal
+ if args.auto_emit then
+ cobj:emit_signal("property::"..prop, value)
+ end
+ end
+
+ -- Attach the accessor methods
+ class.set_index_miss_handler(getter)
+ class.set_newindex_miss_handler(setter)
+end
+
+return setmetatable( object, {__call = function(_,...) object.capi_index_fallback(...) end})
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/gears/protected_call.lua b/lib/gears/protected_call.lua
new file mode 100644
index 0000000..c182e14
--- /dev/null
+++ b/lib/gears/protected_call.lua
@@ -0,0 +1,57 @@
+---------------------------------------------------------------------------
+-- @author Uli Schlachter
+-- @copyright 2016 Uli Schlachter
+-- @module gears.protected_call
+---------------------------------------------------------------------------
+
+local gdebug = require("gears.debug")
+local tostring = tostring
+local traceback = debug.traceback
+local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
+local xpcall = xpcall
+
+local protected_call = {}
+
+local function error_handler(err)
+ gdebug.print_error(traceback("Error during a protected call: " .. tostring(err), 2))
+end
+
+local function handle_result(success, ...)
+ if success then
+ return ...
+ end
+end
+
+local do_pcall
+if _VERSION <= "Lua 5.1" then
+ -- Lua 5.1 doesn't support arguments in xpcall :-(
+ do_pcall = function(func, ...)
+ local args = { ... }
+ return handle_result(xpcall(function()
+ return func(unpack(args))
+ end, error_handler))
+ end
+else
+ do_pcall = function(func, ...)
+ return handle_result(xpcall(func, error_handler, ...))
+ end
+end
+
+--- Call a function in protected mode and handle error-reporting.
+-- If the function call succeeds, all results of the function are returned.
+-- Otherwise, an error message is printed and nothing is returned.
+-- @tparam function func The function to call
+-- @param ... Arguments to the function
+-- @return The result of the given function, or nothing if an error occurred.
+function protected_call.call(func, ...)
+ return do_pcall(func, ...)
+end
+
+local pcall_mt = {}
+function pcall_mt:__call(...)
+ return do_pcall(...)
+end
+
+return setmetatable(protected_call, pcall_mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/gears/shape.lua b/lib/gears/shape.lua
new file mode 100644
index 0000000..4962d78
--- /dev/null
+++ b/lib/gears/shape.lua
@@ -0,0 +1,785 @@
+---------------------------------------------------------------------------
+--- Module dedicated to gather common shape painters.
+--
+-- It add the concept of "shape" to Awesome. A shape can be applied to a
+-- background, a margin, a mask or a drawable shape bounding.
+--
+-- The functions exposed by this module always take a context as first
+-- parameter followed by the widget and height and additional parameters.
+--
+-- The functions provided by this module only create a path in the content.
+-- to actually draw the content, use `cr:fill()`, `cr:mask()`, `cr:clip()` or
+-- `cr:stroke()`
+--
+-- In many case, it is necessary to apply the shape using a transformation
+-- such as a rotation. The preferred way to do this is to wrap the function
+-- in another function calling `cr:rotate()` (or any other transformation
+-- matrix).
+--
+-- To specialize a shape where the API doesn't allows extra arguments to be
+-- passed, it is possible to wrap the shape function like:
+--
+-- local new_shape = function(cr, width, height)
+-- gears.shape.rounded_rect(cr, width, height, 2)
+-- end
+--
+-- Many elements can be shaped. This include:
+--
+-- * `client`s (see `gears.surface.apply_shape_bounding`)
+-- * `wibox`es (see `wibox.shape`)
+-- * All widgets (see `wibox.container.background`)
+-- * The progressbar (see `wibox.widget.progressbar.bar_shape`)
+-- * The graph (see `wibox.widget.graph.step_shape`)
+-- * The checkboxes (see `wibox.widget.checkbox.check_shape`)
+-- * Images (see `wibox.widget.imagebox.clip_shape`)
+-- * The taglist tags (see `awful.widget.taglist`)
+-- * The tasklist clients (see `awful.widget.tasklist`)
+-- * The tooltips (see `awful.tooltip`)
+--
+-- @author Emmanuel Lepage Vallee
+-- @copyright 2011-2016 Emmanuel Lepage Vallee
+-- @module gears.shape
+---------------------------------------------------------------------------
+local g_matrix = require( "gears.matrix" )
+local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
+local atan2 = math.atan2 or math.atan -- lua 5.3 compat
+
+local module = {}
+
+--- Add a rounded rectangle to the current path.
+-- Note: If the radius is bigger than either half side, it will be reduced.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_gears_shape_rounded_rect.svg)
+--
+-- @usage
+--shape.rounded_rect(cr, 70, 70, 10)
+--shape.rounded_rect(cr,20,70, 5)
+--shape.transform(shape.rounded_rect) : translate(0,25) (cr,70,20, 5)
+--
+-- @param cr A cairo content
+-- @tparam number width The rectangle width
+-- @tparam number height The rectangle height
+-- @tparam number radius the corner radius
+function module.rounded_rect(cr, width, height, radius)
+
+ radius = radius or 10
+
+ if width / 2 < radius then
+ radius = width / 2
+ end
+
+ if height / 2 < radius then
+ radius = height / 2
+ end
+
+ cr:move_to(0, radius)
+
+ cr:arc( radius , radius , radius, math.pi , 3*(math.pi/2) )
+ cr:arc( width-radius, radius , radius, 3*(math.pi/2), math.pi*2 )
+ cr:arc( width-radius, height-radius, radius, math.pi*2 , math.pi/2 )
+ cr:arc( radius , height-radius, radius, math.pi/2 , math.pi )
+
+ cr:close_path()
+end
+
+--- Add a rectangle delimited by 2 180 degree arcs to the path.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_gears_shape_rounded_bar.svg)
+--
+-- @usage
+--shape.rounded_bar(cr, 70, 70)
+--shape.rounded_bar(cr, 20, 70)
+--shape.rounded_bar(cr, 70, 20)
+--
+-- @param cr A cairo content
+-- @param width The rectangle width
+-- @param height The rectangle height
+function module.rounded_bar(cr, width, height)
+ module.rounded_rect(cr, width, height, height / 2)
+end
+
+--- A rounded rect with only some of the corners rounded.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_gears_shape_partially_rounded_rect.svg)
+--
+-- @usage
+--shape.partially_rounded_rect(cr, 70, 70)
+--shape.partially_rounded_rect(cr, 70, 70, true)
+--shape.partially_rounded_rect(cr, 70, 70, true, true, false, true, 30)
+--
+-- @param cr A cairo context
+-- @tparam number width The shape width
+-- @tparam number height The shape height
+-- @tparam boolean tl If the top left corner is rounded
+-- @tparam boolean tr If the top right corner is rounded
+-- @tparam boolean br If the bottom right corner is rounded
+-- @tparam boolean bl If the bottom left corner is rounded
+-- @tparam number rad The corner radius
+function module.partially_rounded_rect(cr, width, height, tl, tr, br, bl, rad)
+ rad = rad or 10
+ if width / 2 < rad then
+ rad = width / 2
+ end
+
+ if height / 2 < rad then
+ rad = height / 2
+ end
+
+ -- Top left
+ if tl then
+ cr:arc( rad, rad, rad, math.pi, 3*(math.pi/2))
+ else
+ cr:move_to(0,0)
+ end
+
+ -- Top right
+ if tr then
+ cr:arc( width-rad, rad, rad, 3*(math.pi/2), math.pi*2)
+ else
+ cr:line_to(width, 0)
+ end
+
+ -- Bottom right
+ if br then
+ cr:arc( width-rad, height-rad, rad, math.pi*2 , math.pi/2)
+ else
+ cr:line_to(width, height)
+ end
+
+ -- Bottom left
+ if bl then
+ cr:arc( rad, height-rad, rad, math.pi/2, math.pi)
+ else
+ cr:line_to(0, height)
+ end
+
+ cr:close_path()
+end
+
+--- A rounded rectangle with a triangle at the top.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_gears_shape_infobubble.svg)
+--
+-- @usage
+--shape.infobubble(cr, 70, 70)
+--shape.transform(shape.infobubble) : translate(0, 20)
+-- : rotate_at(35,35,math.pi) (cr,70,20,10, 5, 35 - 5)
+--shape.transform(shape.infobubble)
+-- : rotate_at(35,35,3*math.pi/2) (cr,70,70, nil, nil, 40)
+--
+-- @param cr A cairo context
+-- @tparam number width The shape width
+-- @tparam number height The shape height
+-- @tparam[opt=5] number corner_radius The corner radius
+-- @tparam[opt=10] number arrow_size The width and height of the arrow
+-- @tparam[opt=width/2 - arrow_size/2] number arrow_position The position of the arrow
+function module.infobubble(cr, width, height, corner_radius, arrow_size, arrow_position)
+ arrow_size = arrow_size or 10
+ corner_radius = math.min((height-arrow_size)/2, corner_radius or 5)
+ arrow_position = arrow_position or width/2 - arrow_size/2
+
+
+ cr:move_to(0 ,corner_radius+arrow_size)
+
+ -- Top left corner
+ cr:arc(corner_radius, corner_radius+arrow_size, (corner_radius), math.pi, 3*(math.pi/2))
+
+ -- The arrow triangle (still at the top)
+ cr:line_to(arrow_position , arrow_size )
+ cr:line_to(arrow_position + arrow_size , 0 )
+ cr:line_to(arrow_position + 2*arrow_size , arrow_size )
+
+ -- Complete the rounded rounded rectangle
+ cr:arc(width-corner_radius, corner_radius+arrow_size , (corner_radius) , 3*(math.pi/2) , math.pi*2 )
+ cr:arc(width-corner_radius, height-(corner_radius) , (corner_radius) , math.pi*2 , math.pi/2 )
+ cr:arc(corner_radius , height-(corner_radius) , (corner_radius) , math.pi/2 , math.pi )
+
+ -- Close path
+ cr:close_path()
+end
+
+--- A rectangle terminated by an arrow.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_gears_shape_rectangular_tag.svg)
+--
+-- @usage
+--shape.rectangular_tag(cr, 70, 70)
+--shape.transform(shape.rectangular_tag) : translate(0, 30) (cr, 70, 10, 10)
+--shape.transform(shape.rectangular_tag) : translate(0, 30) (cr, 70, 10, -10)
+--
+-- @param cr A cairo context
+-- @tparam number width The shape width
+-- @tparam number height The shape height
+-- @tparam[opt=height/2] number arrow_length The length of the arrow part
+function module.rectangular_tag(cr, width, height, arrow_length)
+ arrow_length = arrow_length or height/2
+ if arrow_length > 0 then
+ cr:move_to(0 , height/2 )
+ cr:line_to(arrow_length , 0 )
+ cr:line_to(width , 0 )
+ cr:line_to(width , height )
+ cr:line_to(arrow_length , height )
+ else
+ cr:move_to(0 , 0 )
+ cr:line_to(-arrow_length, height/2 )
+ cr:line_to(0 , height )
+ cr:line_to(width , height )
+ cr:line_to(width , 0 )
+ end
+
+ cr:close_path()
+end
+
+--- A simple arrow shape.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_gears_shape_arrow.svg)
+--
+-- @usage
+--shape.arrow(cr, 70, 70)
+--shape.arrow(cr,70,70, 30, 10, 60)
+--shape.transform(shape.arrow) : rotate_at(35,35,math.pi/2)(cr,70,70)
+--
+-- @param cr A cairo context
+-- @tparam number width The shape width
+-- @tparam number height The shape height
+-- @tparam[opt=head_width] number head_width The width of the head (/\) of the arrow
+-- @tparam[opt=width /2] number shaft_width The width of the shaft of the arrow
+-- @tparam[opt=height/2] number shaft_length The head_length of the shaft (the rest is the head)
+function module.arrow(cr, width, height, head_width, shaft_width, shaft_length)
+ shaft_length = shaft_length or height / 2
+ shaft_width = shaft_width or width / 2
+ head_width = head_width or width
+ local head_length = height - shaft_length
+
+ cr:move_to ( width/2 , 0 )
+ cr:rel_line_to( head_width/2 , head_length )
+ cr:rel_line_to( -(head_width-shaft_width)/2 , 0 )
+ cr:rel_line_to( 0 , shaft_length )
+ cr:rel_line_to( -shaft_width , 0 )
+ cr:rel_line_to( 0 , -shaft_length )
+ cr:rel_line_to( -(head_width-shaft_width)/2 , 0 )
+
+ cr:close_path()
+end
+
+--- A squeezed hexagon filling the rectangle.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_gears_shape_hexagon.svg)
+--
+-- @usage
+--shape.hexagon(cr, 70, 70)
+--shape.transform(shape.hexagon) : translate(0,15)(cr,70,20)
+--shape.transform(shape.hexagon) : rotate_at(35,35,math.pi/2)(cr,70,40)
+--
+-- @param cr A cairo context
+-- @tparam number width The shape width
+-- @tparam number height The shape height
+function module.hexagon(cr, width, height)
+ cr:move_to(height/2,0)
+ cr:line_to(width-height/2,0)
+ cr:line_to(width,height/2)
+ cr:line_to(width-height/2,height)
+ cr:line_to(height/2,height)
+ cr:line_to(0,height/2)
+ cr:line_to(height/2,0)
+ cr:close_path()
+end
+
+--- Double arrow popularized by the vim-powerline module.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_gears_shape_powerline.svg)
+--
+-- @usage
+--shape.powerline(cr, 70, 70)
+--shape.transform(shape.powerline) : translate(0, 25) (cr,70,20)
+--shape.transform(shape.powerline) : translate(0, 25) (cr,70,20, -20)
+--
+-- @param cr A cairo context
+-- @tparam number width The shape width
+-- @tparam number height The shape height
+-- @tparam[opt=height/2] number arrow_depth The width of the arrow part of the shape
+function module.powerline(cr, width, height, arrow_depth)
+ arrow_depth = arrow_depth or height/2
+ local offset = 0
+
+ -- Avoid going out of the (potential) clip area
+ if arrow_depth < 0 then
+ width = width + 2*arrow_depth
+ offset = -arrow_depth
+ end
+
+ cr:move_to(offset , 0 )
+ cr:line_to(offset + width - arrow_depth , 0 )
+ cr:line_to(offset + width , height/2 )
+ cr:line_to(offset + width - arrow_depth , height )
+ cr:line_to(offset , height )
+ cr:line_to(offset + arrow_depth , height/2 )
+
+ cr:close_path()
+end
+
+--- An isosceles triangle.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_gears_shape_isosceles_triangle.svg)
+--
+-- @usage
+--shape.isosceles_triangle(cr, 70, 70)
+--shape.isosceles_triangle(cr,20,70)
+--shape.transform(shape.isosceles_triangle) : rotate_at(35, 35, math.pi/2)(cr,70,70)
+--
+-- @param cr A cairo context
+-- @tparam number width The shape width
+-- @tparam number height The shape height
+function module.isosceles_triangle(cr, width, height)
+ cr:move_to( width/2, 0 )
+ cr:line_to( width , height )
+ cr:line_to( 0 , height )
+ cr:close_path()
+end
+
+--- A cross (**+**) symbol.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_gears_shape_cross.svg)
+--
+-- @usage
+--shape.cross(cr, 70, 70)
+--shape.cross(cr,20,70)
+--shape.transform(shape.cross) : scale(0.5, 1)(cr,70,70)
+--
+-- @param cr A cairo context
+-- @tparam number width The shape width
+-- @tparam number height The shape height
+-- @tparam[opt=width/3] number thickness The cross section thickness
+function module.cross(cr, width, height, thickness)
+ thickness = thickness or width/3
+ local xpadding = (width - thickness) / 2
+ local ypadding = (height - thickness) / 2
+ cr:move_to(xpadding, 0)
+ cr:line_to(width - xpadding, 0)
+ cr:line_to(width - xpadding, ypadding)
+ cr:line_to(width , ypadding)
+ cr:line_to(width , height-ypadding)
+ cr:line_to(width - xpadding, height-ypadding)
+ cr:line_to(width - xpadding, height )
+ cr:line_to(xpadding , height )
+ cr:line_to(xpadding , height-ypadding)
+ cr:line_to(0 , height-ypadding)
+ cr:line_to(0 , ypadding )
+ cr:line_to(xpadding , ypadding )
+ cr:close_path()
+end
+
+--- A similar shape to the `rounded_rect`, but with sharp corners.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_gears_shape_octogon.svg)
+--
+-- @usage
+--shape.octogon(cr, 70, 70)
+--shape.octogon(cr,70,70,70/2.5)
+--shape.transform(shape.octogon) : translate(0, 25) (cr,70,20)
+--
+-- @param cr A cairo context
+-- @tparam number width The shape width
+-- @tparam number height The shape height
+-- @tparam number corner_radius
+function module.octogon(cr, width, height, corner_radius)
+ corner_radius = corner_radius or math.min(10, math.min(width, height)/4)
+ local offset = math.sqrt( (corner_radius*corner_radius) / 2 )
+
+ cr:move_to(offset, 0)
+ cr:line_to(width-offset, 0)
+ cr:line_to(width, offset)
+ cr:line_to(width, height-offset)
+ cr:line_to(width-offset, height)
+ cr:line_to(offset, height)
+ cr:line_to(0, height-offset)
+ cr:line_to(0, offset)
+ cr:close_path()
+end
+
+--- A circle shape.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_gears_shape_circle.svg)
+--
+-- @usage
+--shape.circle(cr, 70, 70)
+--shape.circle(cr,20,70)
+--shape.transform(shape.circle) : scale(0.5, 1)(cr,70,70)
+--
+-- @param cr A cairo context
+-- @tparam number width The shape width
+-- @tparam number height The shape height
+-- @tparam[opt=math.min(width height) / 2)] number radius The radius
+function module.circle(cr, width, height, radius)
+ radius = radius or math.min(width, height) / 2
+ cr:move_to(width/2+radius, height/2)
+ cr:arc(width / 2, height / 2, radius, 0, 2*math.pi)
+ cr:close_path()
+end
+
+--- A simple rectangle.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_gears_shape_rectangle.svg)
+--
+-- @usage
+--shape.rectangle(cr, 70, 70)
+--shape.rectangle(cr,20,70)
+--shape.transform(shape.rectangle) : scale(0.5, 1)(cr,70,70)
+--
+-- @param cr A cairo context
+-- @tparam number width The shape width
+-- @tparam number height The shape height
+function module.rectangle(cr, width, height)
+ cr:rectangle(0, 0, width, height)
+end
+
+--- A diagonal parallelogram with the bottom left corner at x=0 and top right
+-- at x=width.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_gears_shape_parallelogram.svg)
+--
+-- @usage
+--shape.parallelogram(cr, 70, 70)
+--shape.parallelogram(cr,70,20)
+--shape.transform(shape.parallelogram) : scale(0.5, 1)(cr,70,70)
+--
+-- @param cr A cairo context
+-- @tparam number width The shape width
+-- @tparam number height The shape height
+-- @tparam[opt=width/3] number base_width The parallelogram base width
+function module.parallelogram(cr, width, height, base_width)
+ base_width = base_width or width/3
+ cr:move_to(width-base_width, 0 )
+ cr:line_to(width , 0 )
+ cr:line_to(base_width , height )
+ cr:line_to(0 , height )
+ cr:close_path()
+end
+
+--- A losange.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_gears_shape_losange.svg)
+--
+-- @usage
+--shape.losange(cr, 70, 70)
+--shape.losange(cr,20,70)
+--shape.transform(shape.losange) : scale(0.5, 1)(cr,70,70)
+--
+-- @param cr A cairo context
+-- @tparam number width The shape width
+-- @tparam number height The shape height
+function module.losange(cr, width, height)
+ cr:move_to(width/2 , 0 )
+ cr:line_to(width , height/2 )
+ cr:line_to(width/2 , height )
+ cr:line_to(0 , height/2 )
+ cr:close_path()
+end
+
+--- A pie.
+--
+-- The pie center is the center of the area.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_gears_shape_pie.svg)
+--
+-- @usage
+--shape.pie(cr, 70, 70)
+--shape.pie(cr,70,70, 1.0471975511966, 4.1887902047864)
+--shape.pie(cr,70,70, 0, 2*math.pi, 10)
+--
+-- @param cr A cairo context
+-- @tparam number width The shape width
+-- @tparam number height The shape height
+-- @tparam[opt=0] number start_angle The start angle (in radian)
+-- @tparam[opt=math.pi/2] number end_angle The end angle (in radian)
+-- @tparam[opt=math.min(width height)/2] number radius The shape height
+function module.pie(cr, width, height, start_angle, end_angle, radius)
+ radius = radius or math.floor(math.min(width, height)/2)
+ start_angle, end_angle = start_angle or 0, end_angle or math.pi/2
+
+ -- If the shape is a circle, then avoid the lines
+ if math.abs(start_angle + end_angle - 2*math.pi) <= 0.01 then
+ cr:arc(width/2, height/2, radius, 0, 2*math.pi)
+ else
+ cr:move_to(width/2, height/2)
+ cr:line_to(
+ width/2 + math.cos(start_angle)*radius,
+ height/2 + math.sin(start_angle)*radius
+ )
+ cr:arc(width/2, height/2, radius, start_angle, end_angle)
+ end
+
+ cr:close_path()
+end
+
+--- A rounded arc.
+--
+-- The pie center is the center of the area.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_gears_shape_arc.svg)
+--
+-- @usage
+--shape.arc(cr,70,70, 10)
+--shape.arc(cr,70,70, 10, nil, nil, true, true)
+--shape.arc(cr,70,70, nil, 0, 2*math.pi)
+--
+-- @param cr A cairo context
+-- @tparam number width The shape width
+-- @tparam number height The shape height
+-- @tparam[opt=math.min(width height)/2] number thickness The arc thickness
+-- @tparam[opt=0] number start_angle The start angle (in radian)
+-- @tparam[opt=math.pi/2] number end_angle The end angle (in radian)
+-- @tparam[opt=false] boolean start_rounded if the arc start rounded
+-- @tparam[opt=false] boolean end_rounded if the arc end rounded
+function module.arc(cr, width, height, thickness, start_angle, end_angle, start_rounded, end_rounded)
+ start_angle = start_angle or 0
+ end_angle = end_angle or math.pi/2
+
+ -- This shape is a partial circle
+ local radius = math.min(width, height)/2
+
+ thickness = thickness or radius/2
+
+ local inner_radius = radius - thickness
+
+ -- As the edge of the small arc need to touch the [start_p1, start_p2]
+ -- line, a small subset of the arc circumference has to be substracted
+ -- that's (less or more) equal to the thickness/2 (a little longer given
+ -- it is an arc and not a line, but it wont show)
+ local arc_percent = math.abs(end_angle-start_angle)/(2*math.pi)
+ local arc_length = ((radius-thickness/2)*2*math.pi)*arc_percent
+
+ if start_rounded then
+ arc_length = arc_length - thickness/2
+
+ -- And back to angles
+ start_angle = end_angle - (arc_length/(radius - thickness/2))
+ end
+
+ if end_rounded then
+ arc_length = arc_length - thickness/2
+
+ -- And back to angles
+ end_angle = start_angle + (arc_length/(radius - thickness/2))
+ end
+
+ -- The path is a curcular arc joining 4 points
+
+ -- Outer first corner
+ local start_p1 = {
+ width /2 + math.cos(start_angle)*radius,
+ height/2 + math.sin(start_angle)*radius
+ }
+
+ if start_rounded then
+
+ -- Inner first corner
+ local start_p2 = {
+ width /2 + math.cos(start_angle)*inner_radius,
+ height/2 + math.sin(start_angle)*inner_radius
+ }
+
+ local median_angle = atan2(
+ start_p2[1] - start_p1[1],
+ -(start_p2[2] - start_p1[2])
+ )
+
+ local arc_center = {
+ (start_p1[1] + start_p2[1])/2,
+ (start_p1[2] + start_p2[2])/2,
+ }
+
+ cr:arc(arc_center[1], arc_center[2], thickness/2,
+ median_angle-math.pi/2, median_angle+math.pi/2
+ )
+
+ else
+ cr:move_to(unpack(start_p1))
+ end
+
+ cr:arc(width/2, height/2, radius, start_angle, end_angle)
+
+ if end_rounded then
+
+ -- Outer second corner
+ local end_p1 = {
+ width /2 + math.cos(end_angle)*radius,
+ height/2 + math.sin(end_angle)*radius
+ }
+
+ -- Inner first corner
+ local end_p2 = {
+ width /2 + math.cos(end_angle)*inner_radius,
+ height/2 + math.sin(end_angle)*inner_radius
+ }
+ local median_angle = atan2(
+ end_p2[1] - end_p1[1],
+ -(end_p2[2] - end_p1[2])
+ ) - math.pi
+
+ local arc_center = {
+ (end_p1[1] + end_p2[1])/2,
+ (end_p1[2] + end_p2[2])/2,
+ }
+
+ cr:arc(arc_center[1], arc_center[2], thickness/2,
+ median_angle-math.pi/2, median_angle+math.pi/2
+ )
+
+ end
+
+ cr:arc_negative(width/2, height/2, inner_radius, end_angle, start_angle)
+
+ cr:close_path()
+end
+
+--- A partial rounded bar. How much of the rounded bar is visible depends on
+-- the given percentage value.
+--
+-- Note that this shape is not closed and thus filling it doesn't make much
+-- sense.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_gears_shape_radial_progress.svg)
+--
+-- @usage
+--shape.radial_progress(cr, 70, 20, .3)
+--shape.radial_progress(cr, 70, 20, .6)
+--shape.radial_progress(cr, 70, 20, .9)
+--
+-- @param cr A cairo context
+-- @tparam number w The shape width
+-- @tparam number h The shape height
+-- @tparam number percent The progressbar percent
+-- @tparam boolean hide_left Do not draw the left side of the shape
+function module.radial_progress(cr, w, h, percent, hide_left)
+ percent = percent or 1
+ local total_length = (2*(w-h))+2*((h/2)*math.pi)
+ local bar_percent = (w-h)/total_length
+ local arc_percent = ((h/2)*math.pi)/total_length
+
+ -- Bottom line
+ if percent > bar_percent then
+ cr:move_to(h/2,h)
+ cr:line_to((h/2) + (w-h),h)
+ cr:stroke()
+ elseif percent < bar_percent then
+ cr:move_to(h/2,h)
+ cr:line_to(h/2+(total_length*percent),h)
+ cr:stroke()
+ end
+
+ -- Right arc
+ if percent >= bar_percent+arc_percent then
+ cr:arc(w-h/2 , h/2, h/2,3*(math.pi/2),math.pi/2)
+ cr:stroke()
+ elseif percent > bar_percent and percent < bar_percent+(arc_percent/2) then
+ cr:arc(w-h/2 , h/2, h/2,(math.pi/2)-((math.pi/2)*((percent-bar_percent)/(arc_percent/2))),math.pi/2)
+ cr:stroke()
+ elseif percent >= bar_percent+arc_percent/2 and percent < bar_percent+arc_percent then
+ cr:arc(w-h/2 , h/2, h/2,0,math.pi/2)
+ cr:stroke()
+ local add = (math.pi/2)*((percent-bar_percent-arc_percent/2)/(arc_percent/2))
+ cr:arc(w-h/2 , h/2, h/2,2*math.pi-add,0)
+ cr:stroke()
+ end
+
+ -- Top line
+ if percent > 2*bar_percent+arc_percent then
+ cr:move_to((h/2) + (w-h),0)
+ cr:line_to(h/2,0)
+ cr:stroke()
+ elseif percent > bar_percent+arc_percent and percent < 2*bar_percent+arc_percent then
+ cr:move_to((h/2) + (w-h),0)
+ cr:line_to(((h/2) + (w-h))-total_length*(percent-bar_percent-arc_percent),0)
+ cr:stroke()
+ end
+
+ -- Left arc
+ if not hide_left then
+ if percent > 0.985 then
+ cr:arc(h/2, h/2, h/2,math.pi/2,3*(math.pi/2))
+ cr:stroke()
+ elseif percent > 2*bar_percent+arc_percent then
+ local relpercent = (percent - 2*bar_percent - arc_percent)/arc_percent
+ cr:arc(h/2, h/2, h/2,3*(math.pi/2)-(math.pi)*relpercent,3*(math.pi/2))
+ cr:stroke()
+ end
+ end
+end
+
+--- Adjust the shape using a transformation object
+--
+-- Apply various transformations to the shape
+--
+-- @usage gears.shape.transform(gears.shape.rounded_bar)
+-- : rotate(math.pi/2)
+-- : translate(10, 10)
+--
+-- @param shape A shape function
+-- @return A transformation handle, also act as a shape function
+function module.transform(shape)
+
+ -- Apply the transformation matrix and apply the shape, then restore
+ local function apply(self, cr, width, height, ...)
+ cr:save()
+ cr:transform(self.matrix:to_cairo_matrix())
+ shape(cr, width, height, ...)
+ cr:restore()
+ end
+ -- Redirect function calls like :rotate() to the underlying matrix
+ local function index(_, key)
+ return function(self, ...)
+ self.matrix = self.matrix[key](self.matrix, ...)
+ return self
+ end
+ end
+
+ local result = setmetatable({
+ matrix = g_matrix.identity
+ }, {
+ __call = apply,
+ __index = index
+ })
+
+ return result
+end
+
+return module
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/gears/surface.lua b/lib/gears/surface.lua
new file mode 100644
index 0000000..78f2216
--- /dev/null
+++ b/lib/gears/surface.lua
@@ -0,0 +1,252 @@
+---------------------------------------------------------------------------
+-- @author Uli Schlachter
+-- @copyright 2012 Uli Schlachter
+-- @module gears.surface
+---------------------------------------------------------------------------
+
+local setmetatable = setmetatable
+local type = type
+local capi = { awesome = awesome }
+local cairo = require("lgi").cairo
+local color = nil
+local gdebug = require("gears.debug")
+local hierarchy = require("wibox.hierarchy")
+
+-- Keep this in sync with build-utils/lgi-check.sh!
+local ver_major, ver_minor, ver_patch = string.match(require('lgi.version'), '(%d)%.(%d)%.(%d)')
+if tonumber(ver_major) <= 0 and (tonumber(ver_minor) < 7 or (tonumber(ver_minor) == 7 and tonumber(ver_patch) < 1)) then
+ error("lgi too old, need at least version 0.7.1")
+end
+
+local surface = { mt = {} }
+local surface_cache = setmetatable({}, { __mode = 'v' })
+
+local function get_default(arg)
+ if type(arg) == 'nil' then
+ return cairo.ImageSurface(cairo.Format.ARGB32, 0, 0)
+ end
+ return arg
+end
+
+--- Try to convert the argument into an lgi cairo surface.
+-- This is usually needed for loading images by file name.
+-- @param _surface The surface to load or nil
+-- @param default The default value to return on error; when nil, then a surface
+-- in an error state is returned.
+-- @return The loaded surface, or the replacement default
+-- @return An error message, or nil on success
+function surface.load_uncached_silently(_surface, default)
+ local file
+ -- On nil, return some sane default
+ if not _surface then
+ return get_default(default)
+ end
+ -- lgi cairo surfaces don't get changed either
+ if cairo.Surface:is_type_of(_surface) then
+ return _surface
+ end
+ -- Strings are assumed to be file names and get loaded
+ if type(_surface) == "string" then
+ local err
+ file = _surface
+ _surface, err = capi.awesome.load_image(file)
+ if not _surface then
+ return get_default(default), err
+ end
+ end
+ -- Everything else gets forced into a surface
+ return cairo.Surface(_surface, true)
+end
+
+--- Try to convert the argument into an lgi cairo surface.
+-- This is usually needed for loading images by file name and uses a cache.
+-- In contrast to `load()`, errors are returned to the caller.
+-- @param _surface The surface to load or nil
+-- @param default The default value to return on error; when nil, then a surface
+-- in an error state is returned.
+-- @return The loaded surface, or the replacement default, or nil if called with
+-- nil.
+-- @return An error message, or nil on success
+function surface.load_silently(_surface, default)
+ if type(_surface) == "string" then
+ local cache = surface_cache[_surface]
+ if cache then
+ return cache
+ end
+ local result, err = surface.load_uncached_silently(_surface, default)
+ if not err then
+ -- Cache the file
+ surface_cache[_surface] = result
+ end
+ return result, err
+ end
+ return surface.load_uncached_silently(_surface, default)
+end
+
+local function do_load_and_handle_errors(_surface, func)
+ if type(_surface) == 'nil' then
+ return get_default()
+ end
+ local result, err = func(_surface, false)
+ if result then
+ return result
+ end
+ gdebug.print_error(debug.traceback(
+ "Failed to load '" .. tostring(_surface) .. "': " .. tostring(err)))
+ return get_default()
+end
+
+--- Try to convert the argument into an lgi cairo surface.
+-- This is usually needed for loading images by file name. Errors are handled
+-- via `gears.debug.print_error`.
+-- @param _surface The surface to load or nil
+-- @return The loaded surface, or nil
+function surface.load_uncached(_surface)
+ return do_load_and_handle_errors(_surface, surface.load_uncached_silently)
+end
+
+--- Try to convert the argument into an lgi cairo surface.
+-- This is usually needed for loading images by file name. Errors are handled
+-- via `gears.debug.print_error`.
+-- @param _surface The surface to load or nil
+-- @return The loaded surface, or nil
+function surface.load(_surface)
+ return do_load_and_handle_errors(_surface, surface.load_silently)
+end
+
+function surface.mt.__call(_, ...)
+ return surface.load(...)
+end
+
+--- Get the size of a cairo surface
+-- @param surf The surface you are interested in
+-- @return The surface's width and height
+function surface.get_size(surf)
+ local cr = cairo.Context(surf)
+ local x, y, w, h = cr:clip_extents()
+ return w - x, h - y
+end
+
+--- Create a copy of a cairo surface.
+-- The surfaces returned by `surface.load` are cached and must not be
+-- modified to avoid unintended side-effects. This function allows to create
+-- a copy of a cairo surface. This copy can then be freely modified.
+-- The surface returned will be as compatible as possible to the input
+-- surface. For example, it will likely be of the same surface type as the
+-- input. The details are explained in the `create_similar` function on a cairo
+-- surface.
+-- @param s Source surface.
+-- @return The surface's duplicate.
+function surface.duplicate_surface(s)
+ s = surface.load(s)
+
+ -- Figure out surface size (this does NOT work for unbounded recording surfaces)
+ local cr = cairo.Context(s)
+ local x, y, w, h = cr:clip_extents()
+
+ -- Create a copy
+ local result = s:create_similar(s.content, w - x, h - y)
+ cr = cairo.Context(result)
+ cr:set_source_surface(s, 0, 0)
+ cr.operator = cairo.Operator.SOURCE
+ cr:paint()
+ return result
+end
+
+--- Create a surface from a `gears.shape`
+-- Any additional parameters will be passed to the shape function
+-- @tparam number width The surface width
+-- @tparam number height The surface height
+-- @param shape A `gears.shape` compatible function
+-- @param[opt=white] shape_color The shape color or pattern
+-- @param[opt=transparent] bg_color The surface background color
+-- @treturn cairo.surface the new surface
+function surface.load_from_shape(width, height, shape, shape_color, bg_color, ...)
+ color = color or require("gears.color")
+
+ local img = cairo.ImageSurface(cairo.Format.ARGB32, width, height)
+ local cr = cairo.Context(img)
+
+ cr:set_source(color(bg_color or "#00000000"))
+ cr:paint()
+
+ cr:set_source(color(shape_color or "#000000"))
+
+ shape(cr, width, height, ...)
+
+ cr:fill()
+
+ return img
+end
+
+--- Apply a shape to a client or a wibox.
+--
+-- If the wibox or client size change, this function need to be called
+-- again.
+-- @param draw A wibox or a client
+-- @param shape or gears.shape function or a custom function with a context,
+-- width and height as parameter.
+-- @param[opt] Any additional parameters will be passed to the shape function
+function surface.apply_shape_bounding(draw, shape, ...)
+ local geo = draw:geometry()
+
+ local img = cairo.ImageSurface(cairo.Format.A1, geo.width, geo.height)
+ local cr = cairo.Context(img)
+
+ cr:set_operator(cairo.Operator.CLEAR)
+ cr:set_source_rgba(0,0,0,1)
+ cr:paint()
+ cr:set_operator(cairo.Operator.SOURCE)
+ cr:set_source_rgba(1,1,1,1)
+
+ shape(cr, geo.width, geo.height, ...)
+
+ cr:fill()
+
+ draw.shape_bounding = img._native
+end
+
+local function no_op() end
+
+local function run_in_hierarchy(self, cr, width, height)
+ local context = {dpi=96}
+ local h = hierarchy.new(context, self, width, height, no_op, no_op, {})
+ h:draw(context, cr)
+ return h
+end
+
+--- Create an SVG file with this widget content.
+-- This is dynamic, so the SVG will be updated along with the widget content.
+-- because of this, the painting may happen hover multiple event loop cycles.
+-- @tparam widget widget A widget
+-- @tparam string path The output file path
+-- @tparam number width The surface width
+-- @tparam number height The surface height
+-- @return The cairo surface
+-- @return The hierarchy
+function surface.widget_to_svg(widget, path, width, height)
+ local img = cairo.SvgSurface.create(path, width, height)
+ local cr = cairo.Context(img)
+
+ return img, run_in_hierarchy(widget, cr, width, height)
+end
+
+--- Create a cairo surface with this widget content.
+-- This is dynamic, so the SVG will be updated along with the widget content.
+-- because of this, the painting may happen hover multiple event loop cycles.
+-- @tparam widget widget A widget
+-- @tparam number width The surface width
+-- @tparam number height The surface height
+-- @param[opt=cairo.Format.ARGB32] format The surface format
+-- @return The cairo surface
+-- @return The hierarchy
+function surface.widget_to_surface(widget, width, height, format)
+ local img = cairo.ImageSurface(format or cairo.Format.ARGB32, width, height)
+ local cr = cairo.Context(img)
+
+ return img, run_in_hierarchy(widget, cr, width, height)
+end
+
+return setmetatable(surface, surface.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/gears/timer.lua b/lib/gears/timer.lua
new file mode 100644
index 0000000..110c39a
--- /dev/null
+++ b/lib/gears/timer.lua
@@ -0,0 +1,187 @@
+---------------------------------------------------------------------------
+--- Timer objects and functions.
+--
+-- @author Uli Schlachter
+-- @copyright 2014 Uli Schlachter
+-- @classmod gears.timer
+---------------------------------------------------------------------------
+
+local capi = { awesome = awesome }
+local ipairs = ipairs
+local pairs = pairs
+local setmetatable = setmetatable
+local table = table
+local tonumber = tonumber
+local traceback = debug.traceback
+local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
+local glib = require("lgi").GLib
+local object = require("gears.object")
+local protected_call = require("gears.protected_call")
+
+--- Timer objects. This type of object is useful when triggering events repeatedly.
+-- The timer will emit the "timeout" signal every N seconds, N being the timeout
+-- value. Note that a started timer will not be garbage collected. Call `:stop`
+-- to enable garbage collection.
+-- @tfield number timeout Interval in seconds to emit the timeout signal.
+-- Can be any value, including floating point ones (e.g. 1.5 seconds).
+-- @tfield boolean started Read-only boolean field indicating if the timer has been
+-- started.
+-- @table timer
+
+--- When the timer is started.
+-- @signal .start
+
+--- When the timer is stopped.
+-- @signal .stop
+
+--- When the timer had a timeout event.
+-- @signal .timeout
+
+local timer = { mt = {} }
+
+--- Start the timer.
+function timer:start()
+ if self.data.source_id ~= nil then
+ print(traceback("timer already started"))
+ return
+ end
+ self.data.source_id = glib.timeout_add(glib.PRIORITY_DEFAULT, self.data.timeout * 1000, function()
+ protected_call(self.emit_signal, self, "timeout")
+ return true
+ end)
+ self:emit_signal("start")
+end
+
+--- Stop the timer.
+function timer:stop()
+ if self.data.source_id == nil then
+ print(traceback("timer not started"))
+ return
+ end
+ glib.source_remove(self.data.source_id)
+ self.data.source_id = nil
+ self:emit_signal("stop")
+end
+
+--- Restart the timer.
+-- This is equivalent to stopping the timer if it is running and then starting
+-- it.
+function timer:again()
+ if self.data.source_id ~= nil then
+ self:stop()
+ end
+ self:start()
+end
+
+--- The timer is started.
+-- @property started
+-- @param boolean
+
+--- The timer timeout value.
+-- **Signal:** property::timeout
+-- @property timeout
+-- @param number
+
+local timer_instance_mt = {
+ __index = function(self, property)
+ if property == "timeout" then
+ return self.data.timeout
+ elseif property == "started" then
+ return self.data.source_id ~= nil
+ end
+
+ return timer[property]
+ end,
+
+ __newindex = function(self, property, value)
+ if property == "timeout" then
+ self.data.timeout = tonumber(value)
+ self:emit_signal("property::timeout")
+ end
+ end
+}
+
+--- Create a new timer object.
+-- @tparam table args Arguments.
+-- @tparam number args.timeout Timeout in seconds (e.g. 1.5).
+-- @treturn timer
+-- @function gears.timer
+timer.new = function(args)
+ local ret = object()
+
+ ret.data = { timeout = 0 }
+ setmetatable(ret, timer_instance_mt)
+
+ for k, v in pairs(args) do
+ ret[k] = v
+ end
+
+ return ret
+end
+
+--- Create a timeout for calling some callback function.
+-- When the callback function returns true, it will be called again after the
+-- same timeout. If false is returned, no more calls will be done. If the
+-- callback function causes an error, no more calls are done.
+-- @tparam number timeout Timeout in seconds (e.g. 1.5).
+-- @tparam function callback Function to run.
+-- @treturn timer The timer object that was set up.
+-- @see timer.weak_start_new
+-- @function gears.timer.start_new
+function timer.start_new(timeout, callback)
+ local t = timer.new({ timeout = timeout })
+ t:connect_signal("timeout", function()
+ local cont = protected_call(callback)
+ if not cont then
+ t:stop()
+ end
+ end)
+ t:start()
+ return t
+end
+
+--- Create a timeout for calling some callback function.
+-- This function is almost identical to `timer.start_new`. The only difference
+-- is that this does not prevent the callback function from being garbage
+-- collected. After the callback function was collected, the timer returned
+-- will automatically be stopped.
+-- @tparam number timeout Timeout in seconds (e.g. 1.5).
+-- @tparam function callback Function to start.
+-- @treturn timer The timer object that was set up.
+-- @see timer.start_new
+-- @function gears.timer.weak_start_new
+function timer.weak_start_new(timeout, callback)
+ local indirection = setmetatable({}, { __mode = "v" })
+ indirection.callback = callback
+ return timer.start_new(timeout, function()
+ local cb = indirection.callback
+ if cb then
+ return cb()
+ end
+ end)
+end
+
+local delayed_calls = {}
+capi.awesome.connect_signal("refresh", function()
+ for _, callback in ipairs(delayed_calls) do
+ protected_call(unpack(callback))
+ end
+ delayed_calls = {}
+end)
+
+--- Call the given function at the end of the current main loop iteration
+-- @tparam function callback The function that should be called
+-- @param ... Arguments to the callback function
+-- @function gears.timer.delayed_call
+function timer.delayed_call(callback, ...)
+ assert(type(callback) == "function", "callback must be a function, got: " .. type(callback))
+ table.insert(delayed_calls, { callback, ... })
+end
+
+function timer.mt.__call(_, ...)
+ return timer.new(...)
+end
+
+return setmetatable(timer, timer.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/gears/wallpaper.lua b/lib/gears/wallpaper.lua
new file mode 100644
index 0000000..70ecf48
--- /dev/null
+++ b/lib/gears/wallpaper.lua
@@ -0,0 +1,221 @@
+---------------------------------------------------------------------------
+-- @author Uli Schlachter
+-- @copyright 2012 Uli Schlachter
+-- @module gears.wallpaper
+---------------------------------------------------------------------------
+
+local cairo = require("lgi").cairo
+local color = require("gears.color")
+local surface = require("gears.surface")
+local timer = require("gears.timer")
+local root = root
+
+local wallpaper = { mt = {} }
+
+local function root_geometry()
+ local width, height = root.size()
+ return { x = 0, y = 0, width = width, height = height }
+end
+
+-- Information about a pending wallpaper change, see prepare_context()
+local pending_wallpaper = nil
+
+local function get_screen(s)
+ return s and screen[s]
+end
+
+--- Prepare the needed state for setting a wallpaper.
+-- This function returns a cairo context through which a wallpaper can be drawn.
+-- The context is only valid for a short time and should not be saved in a
+-- global variable.
+-- @param s The screen to set the wallpaper on or nil for all screens
+-- @return[1] The available geometry (table with entries width and height)
+-- @return[1] A cairo context that the wallpaper should be drawn to
+function wallpaper.prepare_context(s)
+ s = get_screen(s)
+
+ local root_width, root_height = root.size()
+ local geom = s and s.geometry or root_geometry()
+ local source, target, cr
+
+ if not pending_wallpaper then
+ -- Prepare a pending wallpaper
+ source = surface(root.wallpaper())
+ target = source:create_similar(cairo.Content.COLOR, root_width, root_height)
+
+ -- Set the wallpaper (delayed)
+ timer.delayed_call(function()
+ local paper = pending_wallpaper
+ pending_wallpaper = nil
+ wallpaper.set(paper.surface)
+ paper.surface:finish()
+ end)
+ elseif root_width > pending_wallpaper.width or root_height > pending_wallpaper.height then
+ -- The root window was resized while a wallpaper is pending
+ source = pending_wallpaper.surface
+ target = source:create_similar(cairo.Content.COLOR, root_width, root_height)
+ else
+ -- Draw to the already-pending wallpaper
+ source = nil
+ target = pending_wallpaper.surface
+ end
+
+ cr = cairo.Context(target)
+
+ if source then
+ -- Copy the old wallpaper to the new one
+ cr:save()
+ cr.operator = cairo.Operator.SOURCE
+ cr:set_source_surface(source, 0, 0)
+ cr:paint()
+ cr:restore()
+ end
+
+ pending_wallpaper = {
+ surface = target,
+ width = root_width,
+ height = root_height
+ }
+
+ -- Only draw to the selected area
+ cr:translate(geom.x, geom.y)
+ cr:rectangle(0, 0, geom.width, geom.height)
+ cr:clip()
+
+ return geom, cr
+end
+
+--- Set the current wallpaper.
+-- @param pattern The wallpaper that should be set. This can be a cairo surface,
+-- a description for gears.color or a cairo pattern.
+-- @see gears.color
+function wallpaper.set(pattern)
+ if cairo.Surface:is_type_of(pattern) then
+ pattern = cairo.Pattern.create_for_surface(pattern)
+ end
+ if type(pattern) == "string" or type(pattern) == "table" then
+ pattern = color(pattern)
+ end
+ if not cairo.Pattern:is_type_of(pattern) then
+ error("wallpaper.set() called with an invalid argument")
+ end
+ root.wallpaper(pattern._native)
+end
+
+--- Set a centered wallpaper.
+-- @param surf The wallpaper to center. Either a cairo surface or a file name.
+-- @param s The screen whose wallpaper should be set. Can be nil, in which case
+-- all screens are set.
+-- @param background The background color that should be used. Gets handled via
+-- gears.color. The default is black.
+-- @see gears.color
+function wallpaper.centered(surf, s, background)
+ local geom, cr = wallpaper.prepare_context(s)
+ surf = surface.load_uncached(surf)
+ background = color(background)
+
+ -- Fill the area with the background
+ cr.operator = cairo.Operator.SOURCE
+ cr.source = background
+ cr:paint()
+
+ -- Now center the surface
+ local w, h = surface.get_size(surf)
+ cr:translate((geom.width - w) / 2, (geom.height - h) / 2)
+ cr:rectangle(0, 0, w, h)
+ cr:clip()
+ cr:set_source_surface(surf, 0, 0)
+ cr:paint()
+ surf:finish()
+end
+
+--- Set a tiled wallpaper.
+-- @param surf The wallpaper to tile. Either a cairo surface or a file name.
+-- @param s The screen whose wallpaper should be set. Can be nil, in which case
+-- all screens are set.
+-- @param offset This can be set to a table with entries x and y.
+function wallpaper.tiled(surf, s, offset)
+ local _, cr = wallpaper.prepare_context(s)
+
+ if offset then
+ cr:translate(offset.x, offset.y)
+ end
+
+ surf = surface.load_uncached(surf)
+ local pattern = cairo.Pattern.create_for_surface(surf)
+ pattern.extend = cairo.Extend.REPEAT
+ cr.source = pattern
+ cr.operator = cairo.Operator.SOURCE
+ cr:paint()
+ surf:finish()
+end
+
+--- Set a maximized wallpaper.
+-- @param surf The wallpaper to set. Either a cairo surface or a file name.
+-- @param s The screen whose wallpaper should be set. Can be nil, in which case
+-- all screens are set.
+-- @param ignore_aspect If this is true, the image's aspect ratio is ignored.
+-- The default is to honor the aspect ratio.
+-- @param offset This can be set to a table with entries x and y.
+function wallpaper.maximized(surf, s, ignore_aspect, offset)
+ local geom, cr = wallpaper.prepare_context(s)
+ surf = surface.load_uncached(surf)
+ local w, h = surface.get_size(surf)
+ local aspect_w = geom.width / w
+ local aspect_h = geom.height / h
+
+ if not ignore_aspect then
+ aspect_h = math.max(aspect_w, aspect_h)
+ aspect_w = math.max(aspect_w, aspect_h)
+ end
+ cr:scale(aspect_w, aspect_h)
+
+ if offset then
+ cr:translate(offset.x, offset.y)
+ elseif not ignore_aspect then
+ local scaled_width = geom.width / aspect_w
+ local scaled_height = geom.height / aspect_h
+ cr:translate((scaled_width - w) / 2, (scaled_height - h) / 2)
+ end
+
+ cr:set_source_surface(surf, 0, 0)
+ cr.operator = cairo.Operator.SOURCE
+ cr:paint()
+ surf:finish()
+end
+
+--- Set a fitting wallpaper.
+-- @param surf The wallpaper to set. Either a cairo surface or a file name.
+-- @param s The screen whose wallpaper should be set. Can be nil, in which case
+-- all screens are set.
+-- @param background The background color that should be used. Gets handled via
+-- gears.color. The default is black.
+-- @see gears.color
+function wallpaper.fit(surf, s, background)
+ local geom, cr = wallpaper.prepare_context(s)
+ surf = surface.load_uncached(surf)
+ background = color(background)
+
+ -- Fill the area with the background
+ cr.operator = cairo.Operator.SOURCE
+ cr.source = background
+ cr:paint()
+
+ -- Now fit the surface
+ local w, h = surface.get_size(surf)
+ local scale = geom.width / w
+ if h * scale > geom.height then
+ scale = geom.height / h
+ end
+ cr:translate((geom.width - (w * scale)) / 2, (geom.height - (h * scale)) / 2)
+ cr:rectangle(0, 0, w * scale, h * scale)
+ cr:clip()
+ cr:scale(scale, scale)
+ cr:set_source_surface(surf, 0, 0)
+ cr:paint()
+ surf:finish()
+end
+
+return wallpaper
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80