diff --git a/awesomerc.lua b/awesomerc.lua
index 5981f8a4..bdbdbaa7 100644
--- a/awesomerc.lua
+++ b/awesomerc.lua
@@ -77,7 +77,7 @@ mylauncher = awful.widget.launcher({ image = beautiful.awesome_icon,
menubar.utils.terminal = terminal -- Set the terminal for applications that require it
-- }}}
--- {{{ Tag
+-- {{{ Tag layout
-- @DOC_LAYOUT@
-- Table of layouts to cover with awful.layout.inc, order matters.
tag.connect_signal("request::default_layouts", function()
@@ -99,6 +99,27 @@ tag.connect_signal("request::default_layouts", function()
end)
-- }}}
+-- {{{ Wallpaper
+-- @DOC_WALLPAPER@
+screen.connect_signal("request::wallpaper", function(s)
+ awful.wallpaper {
+ screen = s,
+ widget = {
+ {
+ image = beautiful.wallpaper,
+ upscale = true,
+ downscale = true,
+ widget = wibox.widget.imagebox,
+ },
+ valign = "center",
+ halign = "center",
+ tiled = false,
+ widget = wibox.container.tile,
+ }
+ }
+end)
+-- }}}
+
-- {{{ Wibar
-- Keyboard map indicator and switcher
@@ -107,19 +128,6 @@ mykeyboardlayout = awful.widget.keyboardlayout()
-- Create a textclock widget
mytextclock = wibox.widget.textclock()
--- @DOC_WALLPAPER@
-screen.connect_signal("request::wallpaper", function(s)
- -- Wallpaper
- if beautiful.wallpaper then
- local wallpaper = beautiful.wallpaper
- -- If wallpaper is a function, call it with the screen
- if type(wallpaper) == "function" then
- wallpaper = wallpaper(s)
- end
- gears.wallpaper.maximized(wallpaper, s, true)
- end
-end)
-
-- @DOC_FOR_EACH_SCREEN@
screen.connect_signal("request::desktop_decoration", function(s)
-- Each screen has its own tag table.
diff --git a/docs/03-declarative-layout.md b/docs/03-declarative-layout.md
index 12143b54..1555a7c0 100644
--- a/docs/03-declarative-layout.md
+++ b/docs/03-declarative-layout.md
@@ -124,6 +124,10 @@ to an object such as the mouse.
The `naughty.layout.box` allows to provide custom widgets to use within the
notifications.
+The `awful.wallpaper` provides a non-intereactive "backgroud" for one or more
+`screen`. While it uses normal widget, it will not automatically be repainted
+if they change. It will also not provide any mouse events.
+
Finally, the `awful.titlebar`, while not technically a real `wibox`, acts
exactly the same way and allows to attach widgets on each side of clients.
diff --git a/docs/05-awesomerc.md.lua b/docs/05-awesomerc.md.lua
index 0fc005d0..50965a47 100644
--- a/docs/05-awesomerc.md.lua
+++ b/docs/05-awesomerc.md.lua
@@ -72,6 +72,17 @@ variables such as `bg_normal`. To get a list of all official variables, see
the [appearance guide](../documentation/06-appearance.md.html).
]]
+sections.DOC_WALLPAPER = [[
+The AwesomeWM wallpaper module, `awful.wallpaper` support both per-screen wallpaper
+and wallpaper across multiple screens. In the default configuration, the `"request::wallpaper"` signal
+is emitted everytime a screen is added, moved, resized or when the bars
+(`awful.wibar`) are moved.
+
+This is will suited for single-screen wallpapers. If you wish to use multi-screen wallpaper,
+it is better to create a global wallpaper object and edit it when the screen change. See
+the `add_screen`/`remove_screens` methods and the `screens` property of `awful.wallpaper` for
+examples.
+]]
sections.DOC_DEFAULT_APPLICATIONS = [[
diff --git a/docs/ldoc.ltp b/docs/ldoc.ltp
index 148ac6d2..01b260b9 100644
--- a/docs/ldoc.ltp
+++ b/docs/ldoc.ltp
@@ -38,7 +38,8 @@
# local use_li = ldoc.use_li
# local display_name = ldoc.display_name
# local iter = ldoc.modules.iter
-# local function M(txt,item) return ldoc.markup(txt,item,ldoc.plain) end
+# local function un_cmake(s) return s:gsub(";", ";"):gsub(""", '"') end
+# local function M(txt,item) return ldoc.markup(txt and un_cmake(txt) or nil,item,ldoc.plain) end
# local nowrap = ldoc.wrap and '' or 'nowrap'
# local html_space = function(s) return s:gsub(" ", "%%20") end
# local no_underscores = function(s) return s:gsub("_", " ") end
@@ -189,7 +190,7 @@
Usage:
# for usage in iter(module.usage) do
- $(li)
$(ldoc.escape(usage))
$(il)
+ $(li)
$(ldoc.escape(un_cmake(usage)))
$(il)
# end -- for
# end -- if usage
@@ -331,7 +332,7 @@
# end
# if kitem.usage then
Usage:
-
$(ldoc.prettify(kitem.usage[1]))
+
$(ldoc.prettify(un_cmake(kitem.usage[1])))
# end
# end
# if not kind:match("^ldoc_skip") then
@@ -450,7 +451,7 @@
Usage:
# for usage in iter(item.usage) do
- $(li)
$(ldoc.prettify(usage))
$(il)
+ $(li)
$(ldoc.prettify(un_cmake(usage)))
$(il)
# end -- for
# end -- if usage
diff --git a/lib/awful/_compat.lua b/lib/awful/_compat.lua
index c25714f0..496b090b 100644
--- a/lib/awful/_compat.lua
+++ b/lib/awful/_compat.lua
@@ -58,6 +58,28 @@ do
assert(root.object == root_object)
end
+--- The old wallpaper only took native surfaces.
+--
+-- This was a problem for the test backend. The new function takes both
+-- native surfaces and LGI-ified Cairo surfaces.
+function root.wallpaper(pattern)
+ if not pattern then return root._wallpaper() end
+
+ -- Checking for type will either potentially `error()` or always
+ -- return `userdata`. This check will error() when the surface is
+ -- already native.
+ local err = pcall(function() return pattern._native end)
+
+ -- The presence of `root._write_string` means the test backend is
+ -- used. Avoid passing the native surface.
+ if err and not root._write_string then
+ return root._wallpaper(pattern._native)
+ else
+ return root._wallpaper(pattern)
+ end
+end
+
+
-- root.bottons() used to be a capi function. However this proved confusing
-- as rc.lua used `awful.button` and `root.buttons()` used capi.button. There
-- was a little documented hack to "flatten" awful.button into a pair of
diff --git a/lib/awful/init.lua b/lib/awful/init.lua
index cff95db1..d7ebbf81 100644
--- a/lib/awful/init.lua
+++ b/lib/awful/init.lua
@@ -35,6 +35,7 @@ local ret = {
tooltip = require("awful.tooltip");
permissions = require("awful.permissions");
titlebar = require("awful.titlebar");
+ wallpaper = require("awful.wallpaper");
rules = require("awful.rules");
popup = require("awful.popup");
spawn = require("awful.spawn");
diff --git a/lib/awful/wallpaper.lua b/lib/awful/wallpaper.lua
new file mode 100644
index 00000000..e9374d1b
--- /dev/null
+++ b/lib/awful/wallpaper.lua
@@ -0,0 +1,862 @@
+---------------------------------------------------------------------------
+--- Allows to use the wibox widget system to draw the wallpaper.
+--
+-- Rather than simply having a function to set an image
+-- (stretched, centered or tiled) like most wallpaper tools, this module
+-- leverage the full widget system to draw the wallpaper. Note that the result
+-- is **not** interactive. If you want an interactive wallpaper, better use
+-- a `wibox` object with the `below` property set to `true` and maximized
+-- using `awful.placement.maximized`.
+--
+-- It is possible to create an `awful.wallpaper` object from any places, but
+-- it is recommanded to do it from the `request::wallpaper` signal handler.
+-- That signal is called everytime something which could affect the wallpaper
+-- rendering changes, such as new screens.
+--
+-- Single image
+-- ============
+--
+-- This is the default `rc.lua` wallpaper format. It fills the whole screen
+-- and stretches the image while keeping the aspect ratio.
+--
+--@DOC_awful_wallpaper_mazimized1_EXAMPLE@
+--
+-- If the image aspect ratio doesn't match, the `bg` property can be used to
+-- fill the empty area:
+--
+--@DOC_awful_wallpaper_mazimized2_EXAMPLE@
+--
+-- It is also possible to stretch the image:
+--
+--@DOC_awful_wallpaper_mazimized3_EXAMPLE@
+--
+-- Finally, it is also possible to use simpler "branding" in a corner using
+-- `awful.placement`:
+--
+--@DOC_awful_wallpaper_corner1_EXAMPLE@
+--
+-- Tiled
+-- =====
+--
+-- This example tiles an image:
+--
+--@DOC_awful_wallpaper_tiled1_EXAMPLE@
+--
+-- This one tiles a shape using the `wibox.widget.separator` widget:
+--
+--@DOC_awful_wallpaper_tiled2_EXAMPLE@
+--
+-- See the `wibox.container.tile` for more advanced tiling configuration
+-- options.
+--
+-- Solid colors and gradients
+-- ==========================
+--
+-- Solid colors can be set using the `bg` property mentionned above. It
+-- is also possible to set gradients:
+--
+--@DOC_awful_wallpaper_gradient1_EXAMPLE@
+--
+--@DOC_awful_wallpaper_gradient2_EXAMPLE@
+--
+-- Widgets
+-- =======
+--
+-- It is possible to create a wallpaper using any widgets. However, keep
+-- in mind that the wallpaper surface is not interactive, so some widgets
+-- like the sliders will render, but will not behave correctly. Also, it
+ -- is not recommanded to update the wallpaper too often. This is very slow.
+--
+--@DOC_awful_wallpaper_widget2_EXAMPLE@
+--
+-- Cairo graphics API
+-- ==================
+--
+-- AwesomeWM widgets are backed by Cairo. So it is always possible to get
+-- access to the Cairo context directly to do some vector art:
+--
+--@DOC_awful_wallpaper_widget1_EXAMPLE@
+--
+--
+-- SVG vector images
+-- =================
+--
+-- SVG are supported if `librsvg` is installed. Please note that `librsvg`
+-- doesn't implement all filters you might find in the latest version of
+-- your web browser. It is possible some advanced SVG will not look exactly
+-- as they do in a web browser or even Inkscape. However, for most images,
+-- it should look identical.
+--
+-- Our SVG support goes beyond simple rendering. It is possible to set a
+-- custom CSS stylesheet (see `wibox.widget.imagebox.stylesheet`):
+--
+--@DOC_awful_wallpaper_svg_EXAMPLE@
+--
+-- Note that in the example above, it is raw SVG code, but it is also possible
+-- to use a file path. If you have a `.svgz`, you need to uncompress it first
+-- using `gunzip` or a software like Inkscape.
+--
+-- Multiple screen
+-- ===============
+--
+-- The default `rc.lua` creates a new wallpaper everytime `request::wallpaper`
+-- is emitted. This is well suited for having a single wallpaper per screen.
+-- It is also much simpler to implement slideshows and add/remove screens.
+--
+-- However, it isn't wall suited for wallpaper rendered across multiple screens.
+-- For this case, it is better to capture the return value of `awful.wallpaper {}`
+-- as a global variable. Then manually call `add_screen` and `remove_screen` when
+-- needed. A shortcut can be to do:
+--
+-- @DOC_text_awful_wallpaper_multi_screen_EXAMPLE@
+--
+-- Slideshow
+-- =========
+--
+-- Slideshows (changing the wallpaper after a few minutes) can be implemented
+-- directly using a timer and callback, but it is more elegant to simply request
+-- a new wallpaper, then get a random image from within the request handler. This
+-- way, corner cases such as adding and removing screens are handled:
+--
+--@DOC_awful_wallpaper_slideshow1_EXAMPLE@
+--
+-- @author Emmanuel Lepage Vallee <elv1313@gmail.com>
+-- @copyright 2019 Emmanuel Lepage Vallee
+-- @popupmod awful.wallpaper
+---------------------------------------------------------------------------
+require("awful._compat")
+local gtable = require( "gears.table" )
+local gobject = require( "gears.object" )
+local gcolor = require( "gears.color" )
+local gtimer = require( "gears.timer" )
+local surface = require( "gears.surface" )
+local base = require( "wibox.widget.base" )
+local background = require( "wibox.container.background")
+local beautiful = require( "beautiful" )
+local cairo = require( "lgi" ).cairo
+local draw = require( "wibox.widget" ).draw_to_cairo_context
+local grect = require( "gears.geometry" ).rectangle
+
+local capi = { screen = screen, root = root }
+
+local module = {}
+
+local function get_screen(s)
+ return s and capi.screen[s]
+end
+
+-- Screen as key, wallpaper as values.
+local pending_repaint = setmetatable({}, {__mode = 'k'})
+
+local backgrounds = setmetatable({}, {__mode = 'k'})
+
+local panning_modes = {}
+
+-- Get a list of all screen areas.
+local function get_rectangles(screens, honor_workarea, honor_padding)
+ local ret = {}
+
+ for _, s in ipairs(screens) do
+ table.insert(ret, s:get_bounding_geometry {
+ honor_padding = honor_padding,
+ honor_workarea = honor_workarea
+ })
+ end
+
+ return ret
+end
+
+-- Outer perimeter of all rectangles.
+function panning_modes.outer(self)
+ local rectangles = get_rectangles(self.screens, self.honor_workarea, self.honor_padding)
+ local p1, p2 = {x = math.huge, y = math.huge}, {x = 0, y = 0}
+
+ for _, rect in ipairs(rectangles) do
+ p1.x, p1.y = math.min(p1.x, rect.x), math.min(p1.y, rect.y)
+ p2.x, p2.y = math.max(p2.x, rect.x + rect.width), math.max(p2.y, rect.y + rect.height)
+ end
+
+ -- Never try to paint this, it would freeze the system.
+ assert(p1.x ~= math.huge and p1.y ~= math.huge, "Setting wallpaper failed"..#self.screens)
+
+ return {
+ x = p1.x,
+ y = p1.y,
+ width = p2.x - p1.x,
+ height = p2.y - p1.y,
+ }
+end
+
+-- Horizontal inner perimeter of all rectangles.
+function panning_modes.inner_horizontal(self)
+ local rectangles = get_rectangles(self.screens, self.honor_workarea, self.honor_padding)
+ local p1, p2 = {x = math.huge, y = 0}, {x = 0, y = math.huge}
+
+ for _, rect in ipairs(rectangles) do
+ p1.x, p1.y = math.min(p1.x, rect.x), math.max(p1.y, rect.y)
+ p2.x, p2.y = math.max(p2.x, rect.x + rect.width), math.min(p2.y, rect.y + rect.height)
+ end
+
+ -- Never try to paint this, it would freeze the system.
+ assert(p1.x ~= math.huge and p2.y ~= math.huge, "Setting wallpaper failed")
+
+ return {
+ x = p1.x,
+ y = p1.y,
+ width = p2.x - p1.x,
+ height = p2.y - p1.y,
+ }
+end
+
+-- Vertical inner perimeter of all rectangles.
+function panning_modes.inner_vertical(self)
+ local rectangles = get_rectangles(self.screens, self.honor_workarea, self.honor_padding)
+ local p1, p2 = {x = 0, y = math.huge}, {x = math.huge, y = 0}
+
+ for _, rect in ipairs(rectangles) do
+ p1.x, p1.y = math.max(p1.x, rect.x), math.min(p1.y, rect.y)
+ p2.x, p2.y = math.min(p2.x, rect.x + rect.width), math.max(p2.y, rect.y + rect.height)
+ end
+
+ -- Never try to paint this, it would freeze the system.
+ assert(p1.y ~= math.huge and p2.a ~= math.huge, "Setting wallpaper failed")
+
+ return {
+ x = p1.x,
+ y = p1.y,
+ width = p2.x - p1.x,
+ height = p2.y - p1.y,
+ }
+end
+
+-- Best or vertical and horizontal "inner" modes.
+function panning_modes.inner(self)
+ local vert = panning_modes.inner_vertical(self)
+ local hori = panning_modes.inner_horizontal(self)
+
+ if vert.width <= 0 or vert.height <= 0 then return hori end
+ if hori.width <= 0 or hori.height <= 0 then return vert end
+
+ if vert.width * vert.height > hori.width * hori.height then
+ return vert
+ else
+ return hori
+ end
+end
+
+
+local function paint()
+ if not next(pending_repaint) then return end
+
+ local root_width, root_height = capi.root.size()
+
+ -- Get the current wallpaper content.
+ local source = surface(root.wallpaper())
+
+ local target, cr
+
+ -- It's possible that a wallpaper for 1 screen is set using another tool, so make
+ -- sure we copy the current content.
+ if source then
+ target = source:create_similar(cairo.Content.COLOR, root_width, root_height)
+ cr = cairo.Context(target)
+
+ -- Copy the old wallpaper to the new one
+ cr:save()
+ cr.operator = cairo.Operator.SOURCE
+ cr:set_source_surface(source, 0, 0)
+
+ for s in screen do
+ cr:rectangle(
+ s.geometry.x,
+ s.geometry.y,
+ s.geometry.width,
+ s.geometry.height
+ )
+ end
+
+ cr:clip()
+
+ cr:paint()
+ cr:restore()
+ else
+ target = cairo.ImageSurface(cairo.Format.RGB32, root_width, root_height)
+ cr = cairo.Context(target)
+ end
+
+ local walls = {}
+
+ for _, wall in pairs(backgrounds) do
+ walls[wall] = true
+ end
+
+ -- Not supposed to happen, but there is enough API surface for
+ -- it to be a side effect of some signals. Calling the panning
+ -- mode callback with zero screen is not supported.
+ if not next(walls) then
+ return
+ end
+
+ for wall in pairs(walls) do
+
+ local geo = type(wall._private.panning_area) == "function" and
+ wall._private.panning_area(wall) or
+ panning_modes[wall._private.panning_area](wall)
+
+ -- If false, this panning area isn't well suited for the screen geometry.
+ if geo.width > 0 or geo.height > 0 then
+ local uncovered_areas = grect.area_remove(get_rectangles(wall.screens, false, false), geo)
+
+ cr:save()
+
+ -- Prevent overwrite then there is multiple non-continuous screens.
+ for _, s in ipairs(wall.screens) do
+ cr:rectangle(
+ s.geometry.x,
+ s.geometry.y,
+ s.geometry.width,
+ s.geometry.height
+ )
+ end
+
+ cr:clip()
+
+ -- The older surface might contain garbage, optionally clean it.
+ if wall.uncovered_areas_color then
+ cr:set_source(gcolor(wall.uncovered_areas_color))
+
+ for _, area in ipairs(uncovered_areas) do
+ cr:rectangle(area.x, area.y, area.width, area.height)
+ cr:fill()
+ end
+
+ end
+
+ if not wall._private.container then
+ wall._private.container = background()
+ wall._private.container.bg = wall._private.bg or beautiful.wallpaper_bg or "#000000"
+ wall._private.container.fg = wall._private.fg or beautiful.wallpaper_fg or "#ffffff"
+ wall._private.container.widget = wall.widget
+ end
+
+ local a_context = {
+ dpi = wall._private.context.dpi
+ }
+
+ -- Pick the lowest DPI.
+ if not a_context.dpi then
+ a_context.dpi = math.huge
+ for _, s in ipairs(wall.screens) do
+ a_context.dpi = math.min(
+ s.dpi and s.dpi or s.preferred_dpi, a_context.dpi
+ )
+ end
+ end
+
+ -- Fallback.
+ if not a_context.dpi then
+ a_context.dpi = 96
+ end
+
+ cr:translate(geo.x, geo.y)
+ draw(wall._private.container, cr, geo.width, geo.height, a_context)
+ cr:restore()
+ end
+ end
+
+ -- Set the wallpaper.
+ local pattern = cairo.Pattern.create_for_surface(target)
+ capi.root.wallpaper(pattern)
+
+ -- Limit some potential GC induced increase in memory usage.
+ -- But really, is someone is trying to apply wallpaper changes more
+ -- often than the GC is executed, they are doing it wrong.
+ target:finish()
+
+end
+
+local mutex = false
+
+-- Uploading the surface to X11 is *very* resource intensive. Given the updates
+-- will often happen in batch (like startup), make sure to only do one "real"
+-- update.
+local function update()
+ if mutex then return end
+
+ mutex = true
+
+ gtimer.delayed_call(function()
+ -- Remove the mutex first in case `paint()` raises an exception.
+ mutex = false
+ paint()
+ end)
+end
+
+capi.screen.connect_signal("removed", function(s)
+ if not backgrounds[s] then return end
+
+ backgrounds[s]:remove_screen(s)
+
+ update()
+end)
+
+capi.screen.connect_signal("property::geometry", function(s)
+ if not backgrounds[s] then return end
+
+ backgrounds[s]:repaint()
+end)
+
+
+--- The wallpaper widget.
+--
+-- When set, instead of using the `image_path` or `surface` properties, the
+-- wallpaper will be defined as a normal `wibox` widget tree.
+--
+-- @property widget
+-- @tparam wibox.widget widget
+-- @see wibox.widget.imagebox
+-- @see wibox.container.tile
+
+--- The wallpaper DPI (dots per inch).
+--
+-- Each screen has a DPI. This value will be used by default, but sometime it
+-- is useful to override the screen DPI and use a custom one. This makes
+-- possible, for example, to draw the widgets bigger than they would otherwise
+-- be.
+--
+-- If not DPI is defined, it will use the smallest DPI from any of the screen.
+--
+-- In this example, there is 3 screens with DPI of 100, 200 and 300. As you can
+-- see, only the text size is affected. Many widgetds are DPI aware, but not all
+-- of them. This is either because DPI isn't relevant to them or simply because it
+-- isn't supported (like `wibox.widget.graph`).
+--
+-- @DOC_awful_wallpaper_dpi1_EXAMPLE@
+--
+-- @property dpi
+-- @tparam[opt=screen.dpi] number dpi
+-- @see screen
+-- @see screen.dpi
+
+--- The wallpaper screen.
+--
+-- Note that there can only be one wallpaper per screen. If there is more, one
+-- will be chosen and all other ignored.
+--
+-- @property screen
+-- @tparam screen screen
+-- @see screens
+-- @see add_screen
+-- @see remove_screen
+
+--- A list of screen for this wallpaper.
+--
+--@DOC_awful_wallpaper_screens1_EXAMPLE@
+--
+-- Some large wallpaper are made to span multiple screens.
+-- @property screens
+-- @tparam table screens
+-- @see screen
+-- @see add_screen
+-- @see remove_screen
+-- @see detach
+
+--- The background color.
+--
+-- It will be used as the "fill" color if the `image` doesn't take all the
+-- screen space. It will also be the default background for the `widget.
+--
+-- As usual with colors in `AwesomeWM`, it can also be a gradient or a pattern.
+--
+-- @property bg
+-- @tparam gears.color bg
+-- @see gears.color
+
+--- The foreground color.
+--
+-- This will be used by the `widget` (if any).
+--
+-- As usual with colors in `AwesomeWM`, it can also be a gradient or a pattern.
+--
+-- @property fg
+-- @tparam gears.color fg
+-- @see gears.color
+
+--- The default wallpaper background color.
+-- @beautiful beautiful.wallpaper_bg
+-- @tparam gears.color wallpaper_bg
+-- @see bg
+
+--- The default wallpaper foreground color.
+--
+-- This is useful when using widgets or text in the wallpaper. A wallpaper
+-- created from a single image wont use this.
+--
+-- @beautiful beautiful.wallpaper_fg
+-- @tparam gears.color wallpaper_fg
+-- @see bg
+
+--- Honor the workarea.
+--
+-- When set to `true`, the wallpaper will only fill the workarea space instead
+-- of the entire screen. This means it wont be drawn below the `awful.wibar` or
+-- docked clients. This is useful when using opaque bars. Note that it can cause
+-- aspect ratio issues for the wallpaper `image` and add bars colored with the
+-- `bg` color on the sides.
+--
+--@DOC_awful_wallpaper_workarea1_EXAMPLE@
+--
+-- @property honor_workarea
+-- @tparam[opt=false] boolean honor_workarea
+-- @see honor_padding
+-- @see uncovered_areas
+
+--- Honor the screen padding.
+--
+-- When set, this will look at the `screen.padding` property to restrict the
+-- area where the wallpaper is rendered.
+--
+-- @DOC_awful_wallpaper_padding1_EXAMPLE@
+--
+-- @property honor_padding
+-- @tparam boolean honor_padding
+-- @see honor_workarea
+-- @see uncovered_areas
+
+--- Returns the list of screen(s) area which won't be covered by the wallpaper.
+--
+-- When `honor_workarea`, `honor_padding` or panning are used, some section of
+-- the screen won't have a wallpaper. This returns a list of areas tables. Each
+-- table has a `x`, `y`, `width` and `height` key.
+--
+-- @property uncovered_areas
+-- @tparam table uncovered_areas
+-- @see honor_workarea
+-- @see honor_padding
+-- @see uncovered_areas_color
+
+--- The color for the uncovered areas.
+--
+-- Some application rely on the wallpaper for "fake" transparency. Even if an
+-- area is hidden under a wibar (or other clients), its background can still
+-- become visible. If you use such application and change your screen geometry
+-- often enough, it is possible some areas would become filled with the remains
+-- of previous wallpapers. This property allows to clean those areas with a solid
+-- color or a gradient.
+--
+-- @property uncovered_areas_color
+-- @tparam gears.color uncovered_areas_color
+-- @see uncovered_areas
+
+--- Defines where the wallpaper is placed when there is multiple screens.
+--
+-- When there is more than 1 screen, it is possible they don't have the same
+-- resolution, position or orientation. Panning the wallpaper over them may look
+-- better if a continuous rectangle is used rather than creating a virtual rectangle
+-- around all screens.
+--
+-- The default algorithms are:
+--
+-- **outer:** *(default)*
+--
+-- Draw an imaginary rectangle around all screens.
+--
+-- @DOC_awful_wallpaper_panning_outer_EXAMPLE@
+--
+-- **inner:**
+--
+-- Take the largest area or either `inner_horizontal` or `inner_vertical`.
+--
+-- @DOC_awful_wallpaper_panning_inner_EXAMPLE@
+--
+-- **inner_horizontal:**
+--
+-- Take the smallest `x` value, the largest `x+width`, the smallest `y`
+-- and the smallest `y+height`.
+--
+-- @DOC_awful_wallpaper_panning_inner_horizontal_EXAMPLE@
+--
+-- **inner_vertical:**
+--
+-- Take the smallest `y` value, the largest `y+height`, the smallest `x`
+-- and the smallest `x+width`.
+--
+-- @DOC_awful_wallpaper_panning_inner_vertical_EXAMPLE@
+--
+-- **Custom function:**
+--
+-- It is also possible to define a custom function.
+--
+-- @DOC_awful_wallpaper_panning_custom_EXAMPLE@
+--
+-- @property panning_area
+-- @tparam function|string panning_area
+-- @see uncovered_areas
+
+function module:set_panning_area(value)
+ value = value or "outer"
+
+ assert(type(value) == "function" or panning_modes[value], "Invalid panning mode: "..tostring(value))
+
+ self._private.panning_area = value
+
+ self:repaint()
+
+ self:emit_signal("property::panning_area", value)
+end
+
+function module:set_widget(w)
+ self._private.widget = base.make_widget_from_value(w)
+
+ if self._private.container then
+ self._private.container.widget = self._private.widget
+ end
+
+ self:repaint()
+end
+
+function module:get_widget()
+ return self._private.widget
+end
+
+function module:set_dpi(dpi)
+ self._private.context.dpi = dpi
+ self:repaint()
+end
+
+function module:get_dpi()
+ return self._private.context.dpi
+end
+
+function module:set_screen(s)
+ if not s then return end
+
+ self:_clear()
+ self:add_screen(s)
+end
+
+for _, prop in ipairs {"bg", "fg"} do
+ module["set_"..prop] = function(self, color)
+ if self._private.container then
+ self._private.container[prop] = color
+ end
+
+ self._private[prop] = color
+
+ self:repaint()
+ end
+end
+
+function module:get_uncovered_areas()
+ local geo = type(self._private.panning_area) == "function" and
+ self._private.panning_area(self) or
+ panning_modes[self._private.panning_area](self)
+
+ return grect.area_remove(get_rectangles(self.screens, false, false), geo)
+end
+
+function module:set_screens(screens)
+ local to_rem = {}
+
+ -- All screens.
+ -- The copy is needed because it's a metatable, `ipairs` doesn't work
+ -- correctly in all Lua versions.
+ if screens == capi.screen then
+ screens = {}
+
+ for s in capi.screen do
+ table.insert(screens, s)
+ end
+ end
+
+ for _, s in ipairs(screens) do
+ to_rem[get_screen(s)] = true
+ end
+
+ for _, s in ipairs(self.screens) do
+ to_rem[get_screen(s)] = nil
+ end
+
+ for _, s in ipairs(screens) do
+ s = get_screen(s)
+ self:add_screen(s)
+ to_rem[s] = nil
+ end
+
+ for s, remove in pairs(to_rem) do
+ if remove then
+ self:remove_screen(s)
+ end
+ end
+end
+
+function module:get_screens()
+ return self._private.screens
+end
+
+--- Add another screen (enable panning).
+--
+-- **Before:**
+--
+--@DOC_awful_wallpaper_add_screen1_EXAMPLE@
+--
+-- **After:**
+--
+--@DOC_awful_wallpaper_add_screen2_EXAMPLE@
+--
+-- Also note that adding a non-continuous screen might not work well,
+-- but will not automatically add the screens in between:
+--
+--@DOC_awful_wallpaper_add_screen3_EXAMPLE@
+--
+-- @method add_screen
+-- @tparam screen screen The screen object.
+-- @see remove_screen
+function module:add_screen(s)
+ s = get_screen(s)
+
+ for _, s2 in ipairs(self._private.screens) do
+ if s == s2 then return end
+ end
+
+ table.insert(self._private.screens, s)
+
+ if backgrounds[s] and backgrounds[s] ~= self then
+ backgrounds[s]:remove_screen(s)
+ end
+
+ backgrounds[s] = self
+
+ self:repaint()
+end
+
+--- Detach the wallpaper from all screens.
+--
+-- Adding a new wallpaper to a screen will automatically
+-- detach the older one. However there is some case when
+-- it is useful to call this manually. For example, when
+-- adding a new panned wallpaper, it is possible that 2
+-- wallpaper will have an overlap.
+--
+-- @method detach
+-- @see remove_screen
+-- @see add_screen
+function module:detach()
+ local screens = gtable.clone(self.screens)
+
+ for _, s in ipairs(screens) do
+ self:remove_screen(s)
+ end
+end
+
+function module:_clear()
+ self._private.screens = setmetatable({}, {__mode = "v"})
+ update()
+end
+
+--- Repaint the wallpaper.
+--
+-- By default, even if the widget changes, the wallpaper will **NOT** be
+-- automatically repainted. Repainting the native X11 wallpaper is slow and
+-- it would be too easy to accidentally cause a performance problem. If you
+-- really need to repaint the wallpaper, call this method.
+--
+-- @method repaint
+function module:repaint()
+ for _, s in ipairs(self._private.screens) do
+ pending_repaint[s] = true
+ end
+
+ update()
+end
+
+--- Remove a screen.
+--
+-- Calling this will remove a screen, but will **not** repaint its area.
+-- In this example, the wallpaper was spanning all 3 screens and the
+-- first screen was removed:
+--
+-- @DOC_awful_wallpaper_remove_screen1_EXAMPLE@
+--
+-- As you can see, the content of screen 1 still looks like it is part of
+-- the 3 screen wallpaper. The only use case for calling this method is if
+-- you use a 3rd party tools to change the wallpaper.
+--
+-- If you wish to simply remove a screen and not have leftover content, it is
+-- simpler to just create a new wallpaper for that screen:
+--
+-- @DOC_awful_wallpaper_remove_screen2_EXAMPLE@
+--
+-- @method remove_screen
+-- @tparam screen screen The screen to remove.
+-- @see detach
+-- @see add_screen
+-- @see screens
+function module:remove_screen(s)
+ s = get_screen(s)
+
+ for k, s2 in ipairs(self._private.screens) do
+ if s == s2 then
+ table.remove(self._private.screens, k)
+ end
+ end
+
+ backgrounds[s] = nil
+
+ self:repaint()
+end
+
+--- Create a wallpaper.
+--
+-- Note that all parameters are not required. Please refer to the
+-- module description and examples to understand parameters usages.
+--
+-- @constructorfct awful.wallpaper
+-- @tparam table args
+-- @tparam[opt] wibox.widget args.widget The wallpaper widget.
+-- @tparam[opt] number args.dpi The wallpaper DPI (dots per inch).
+-- @tparam[opt] screen args.screen The wallpaper screen.
+-- @tparam[opt] table args.screens A list of screen for this wallpaper.
+-- Use this parameter as a remplacement for `args.screen` to manage multiscreen wallpaper.
+-- (Note: the expected table should be an array-like table `{screen1, screen2, ...}`)
+-- @tparam[opt] gears.color args.bg The background color.
+-- @tparam[opt] gears.color args.fg The foreground color.
+-- @tparam[opt] gears.color args.uncovered_areas_color The color for the uncovered areas.
+-- @tparam[opt] boolean args.honor_workarea Honor the workarea.
+-- @tparam[opt] boolean args.honor_padding Honor the screen padding.
+-- @tparam[opt] table args.uncovered_areas Returns the list of screen(s) area which won't be covered by the wallpaper.
+-- @tparam[opt] function|string args.panning_area Defines where the wallpaper is placed when there is multiple screens.
+
+local function new(_, args)
+ args = args or {}
+ local ret = gobject {
+ enable_auto_signals = true,
+ enable_properties = true,
+ }
+
+ rawset(ret, "_private", {})
+ ret._private.context = {}
+ ret._private.panning_area = "outer"
+
+ gtable.crush(ret, module, true)
+
+ ret:_clear()
+
+ -- Set the screen or screens first to avoid a race condition
+ -- with the other setters.
+ local args_screen, args_screens = args.screen, args.screens
+ if args_screen then
+ ret.screen = args_screen
+ elseif args_screens then
+ ret.screens = args_screens
+ end
+
+ -- Avoid crushing `screen` and `screens` twice.
+ args.screen, args.screens = nil, nil
+ gtable.crush(ret, args, false)
+ args.screen, args.screens = args_screen, args_screens
+
+ return ret
+end
+
+return setmetatable(module, {__call = new})
diff --git a/lib/gears/filesystem.lua b/lib/gears/filesystem.lua
index 42f94ec7..de58e1a7 100644
--- a/lib/gears/filesystem.lua
+++ b/lib/gears/filesystem.lua
@@ -172,28 +172,39 @@ end
-- @tparam string path The directory to search.
-- @tparam[opt] table exts Specific extensions to limit the search to. eg:`{ "jpg", "png" }`
-- If ommited, all files are considered.
+-- @tparam[opt=false] boolean absolute_path Return the absolute path instead of the filename.
-- @treturn string|nil A randomly selected filename from the specified path (with
--- a specified extension if required) or nil if no suitable file is found.
+-- a specified extension if required) or nil if no suitable file is found. If `absolute_path`
+-- is set, then a path is returned instead of a file name.
-- @staticfct gears.filesystem.get_random_file_from_dir
-function filesystem.get_random_file_from_dir(path, exts)
+function filesystem.get_random_file_from_dir(path, exts, absolute_path)
local files, valid_exts = {}, {}
-- Transforms { "jpg", ... } into { [jpg] = #, ... }
- if exts then for i, j in ipairs(exts) do valid_exts[j:lower()] = i end end
+ if exts then for i, j in ipairs(exts) do valid_exts[j:lower():gsub("^[.]", "")] = i end end
-- Build a table of files from the path with the required extensions
local file_list = Gio.File.new_for_path(path):enumerate_children("standard::*", 0)
+
+ -- This will happen when the directory doesn't exist.
+ if not file_list then return nil end
+
for file in function() return file_list:next_file() end do
if file:get_file_type() == "REGULAR" then
local file_name = file:get_display_name()
+
if not exts or valid_exts[file_name:lower():match(".+%.(.*)$") or ""] then
table.insert(files, file_name)
end
end
end
+ if #files == 0 then return nil end
+
-- Return a randomly selected filename from the file table
- return #files > 0 and files[math.random(#files)] or nil
+ local file = files[math.random(#files)]
+
+ return absolute_path and (path:gsub("[/]*$", "") .. "/" .. file) or file
end
return filesystem
diff --git a/lib/gears/wallpaper.lua b/lib/gears/wallpaper.lua
index 8b3f796a..3843a359 100644
--- a/lib/gears/wallpaper.lua
+++ b/lib/gears/wallpaper.lua
@@ -1,22 +1,7 @@
---------------------------------------------------------------------------
-- Functions for setting the wallpaper.
--
--- There are two levels of functionality provided by this module:
---
--- The low-level functionality consists of two functions.
--- @{set} an already-prepared wallpaper on all screens and @{prepare_context}
--- prepares things to draw a new wallpaper.
---
--- The low-level API can for example be used to set solid red as a wallpaper
--- (see @{gears.color} for details on the supported syntax):
---
--- gears.wallpaper.set("#ff0000")
---
--- Ontop of these low-level functions, the remaining functions implement more
--- useful functionality. For example, given a screen object `s`, an image can be
--- set as the wallpaper as follows:
---
--- gears.wallpaper.maximized("path/to/image.png", s)
+-- This module is deprecated, please use `awful.wallpaper`.
--
-- @author Uli Schlachter
-- @copyright 2012 Uli Schlachter
@@ -51,8 +36,10 @@ end
-- @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.
--- @staticfct gears.wallpaper.prepare_context
+-- @deprecated gears.wallpaper.prepare_context
function wallpaper.prepare_context(s)
+ debug.deprecate("Use `awful.wallpaper`", {deprecated_in=5})
+
s = get_screen(s)
local root_width, root_height = root.size()
@@ -110,8 +97,10 @@ end
-- @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
--- @staticfct gears.wallpaper.set
+-- @deprecated gears.wallpaper.set
function wallpaper.set(pattern)
+ debug.deprecate("Use `awful.wallpaper`", {deprecated_in=5})
+
if cairo.Surface:is_type_of(pattern) then
pattern = cairo.Pattern.create_for_surface(pattern)
end
@@ -121,7 +110,7 @@ function wallpaper.set(pattern)
if not cairo.Pattern:is_type_of(pattern) then
error("wallpaper.set() called with an invalid argument")
end
- root.wallpaper(pattern._native)
+ root.wallpaper(pattern)
end
--- Set a centered wallpaper.
@@ -132,8 +121,10 @@ end
-- gears.color. The default is black.
-- @param scale The scale factor for the wallpaper. Default is 1 (original size).
-- @see gears.color
--- @staticfct gears.wallpaper.centered
+-- @deprecated gears.wallpaper.centered
function wallpaper.centered(surf, s, background, scale)
+ debug.deprecate("Use `awful.wallpaper`", {deprecated_in=5})
+
local geom, cr = wallpaper.prepare_context(s)
local original_surf = surf
surf = surface.load_uncached(surf)
@@ -172,8 +163,10 @@ end
-- @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.
--- @staticfct gears.wallpaper.tiled
+-- @deprecated gears.wallpaper.tiled
function wallpaper.tiled(surf, s, offset)
+ debug.deprecate("Use `awful.wallpaper`", {deprecated_in=5})
+
local _, cr = wallpaper.prepare_context(s)
if offset then
@@ -202,8 +195,10 @@ end
-- @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.
--- @staticfct gears.wallpaper.maximized
+-- @deprecated gears.wallpaper.maximized
function wallpaper.maximized(surf, s, ignore_aspect, offset)
+ debug.deprecate("Use `awful.wallpaper`", {deprecated_in=5})
+
local geom, cr = wallpaper.prepare_context(s)
local original_surf = surf
surf = surface.load_uncached(surf)
@@ -243,8 +238,10 @@ end
-- @param background The background color that should be used. Gets handled via
-- gears.color. The default is black.
-- @see gears.color
--- @staticfct gears.wallpaper.fit
+-- @deprecated gears.wallpaper.fit
function wallpaper.fit(surf, s, background)
+ debug.deprecate("Use `awful.wallpaper`", {deprecated_in=5})
+
local geom, cr = wallpaper.prepare_context(s)
local original_surf = surf
surf = surface.load_uncached(surf)
diff --git a/lib/wibox/container/tile.lua b/lib/wibox/container/tile.lua
index e2175cd4..46fdd391 100644
--- a/lib/wibox/container/tile.lua
+++ b/lib/wibox/container/tile.lua
@@ -31,6 +31,7 @@ function module:draw(context, cr, width, height)
if not self._private.surface then
self._private.surface = cairo.ImageSurface(cairo.Format.ARGB32, w+hspace, h+vspace)
self._private.cr = cairo.Context(self._private.surface)
+ self._private.cr:set_source(cr:get_source())
self._private.pattern = cairo.Pattern.create_for_surface(self._private.surface)
self._private.pattern.extend = cairo.Extend.REPEAT
self._private.cr:translate(math.ceil(hspace), math.ceil(vspace))
diff --git a/lib/wibox/widget/imagebox.lua b/lib/wibox/widget/imagebox.lua
index 060d8c42..ace4eed7 100644
--- a/lib/wibox/widget/imagebox.lua
+++ b/lib/wibox/widget/imagebox.lua
@@ -48,28 +48,128 @@ end
local imagebox = { mt = {} }
-local rsvg_handle_cache = setmetatable({}, { __mode = 'v' })
+local rsvg_handle_cache = setmetatable({}, { __mode = 'k' })
---Load rsvg handle form image file
----@tparam string file Path to svg file.
----@return Rsvg handle
+-- @tparam string file Path to svg file.
+-- @return Rsvg handle
+-- @treturn table A table where cached data can be stored.
local function load_rsvg_handle(file)
if not Rsvg then return end
- local cache = rsvg_handle_cache[file]
+ local cache = (rsvg_handle_cache[file] or {})["handle"]
+
if cache then
- return cache
+ return cache, rsvg_handle_cache[file]
+ end
+
+ local handle, err
+
+ if file:match("<[?]?xml") or file:match("