2019-04-29 05:49:47 +02:00
|
|
|
---------------------------------------------------------------------------
|
|
|
|
--- 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
|
2021-12-21 06:54:15 +01:00
|
|
|
-- @usebeautiful beautiful.wallpaper_bg
|
2019-04-29 05:49:47 +02:00
|
|
|
-- @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
|
2021-12-21 06:54:15 +01:00
|
|
|
-- @usebeautiful beautiful.wallpaper_fg
|
2019-04-29 05:49:47 +02:00
|
|
|
-- @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
|
2021-09-23 05:51:54 +02:00
|
|
|
-- adding a new panned wallpaper, it is possible that 2
|
2019-04-29 05:49:47 +02:00
|
|
|
-- 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.
|
|
|
|
--
|
2021-09-25 21:49:19 +02:00
|
|
|
-- Note that all parameters are not required. Please refer to the
|
|
|
|
-- module description and examples to understand parameters usages.
|
|
|
|
--
|
2019-04-29 05:49:47 +02:00
|
|
|
-- @constructorfct awful.wallpaper
|
|
|
|
-- @tparam table args
|
2021-09-25 21:49:19 +02:00
|
|
|
-- @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.
|
2019-04-29 05:49:47 +02:00
|
|
|
|
|
|
|
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})
|