awful: Add a wallpaper module.
`gears.wallpaper` is a flat API (that doesn't even belong in gears) and is neither well integrated with the other AwesomeWM concepts, nor well documented or easy to understand for newcomers. This module adds an object oriented, declarative, module with properties for the most common wallpaper types. It also integrates with `awful.placement` and the `wibox` module. The design attempts to make the wallpaper a "wibox like" object like the titlebars. It is non-interactive, but still allows the widgets. Note that this is slow and should be avoided for dynamic content. It is why the widgets are never updated unless manually reloaded. The objects also attempt to be disposable rather than persistent. Thus they are immutable by default to prevent accidental abuse. Fix #3428 #2596
This commit is contained in:
parent
aa998db626
commit
98dd78b777
|
@ -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");
|
||||
|
|
|
@ -0,0 +1,857 @@
|
|||
---------------------------------------------------------------------------
|
||||
--- 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 ned 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.
|
||||
--
|
||||
-- @constructorfct awful.wallpaper
|
||||
-- @tparam table args
|
||||
-- @tparam wibox.widget args.widget The wallpaper widget.
|
||||
-- @tparam number args.dpi The wallpaper DPI (dots per inch).
|
||||
-- @tparam screen args.screen The wallpaper screen.
|
||||
-- @tparam table args.screens A list of screen for this wallpaper.
|
||||
-- @tparam gears.color args.bg The background color.
|
||||
-- @tparam gears.color args.fg The foreground color.
|
||||
-- @tparam gears.color args.uncovered_areas_color The color for the uncovered areas.
|
||||
-- @tparam boolean args.honor_workarea Honor the workarea.
|
||||
-- @tparam boolean args.honor_padding Honor the screen padding.
|
||||
-- @tparam table args.uncovered_areas Returns the list of screen(s) area which won't be covered by the wallpaper.
|
||||
-- @tparam 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})
|
|
@ -68,7 +68,10 @@ local function add_client(args)
|
|||
local c = data.c
|
||||
if not c then
|
||||
c = client.get()[1]
|
||||
assert(c.name == name)
|
||||
assert(c.name == name,
|
||||
"Expected "..name.." got "..c.name.." there is "
|
||||
..#client.get().." clients"
|
||||
)
|
||||
data.c = c
|
||||
end
|
||||
local test = args.test or default_test
|
||||
|
|
Loading…
Reference in New Issue