--------------------------------------------------------------------------- -- @author Uli Schlachter -- @copyright 2012 Uli Schlachter -- @module gears.wallpaper --------------------------------------------------------------------------- local cairo = require("lgi").cairo local color = require("gears.color") local surface = require("gears.surface") local timer = require("gears.timer") local debug = require("gears.debug") local root = root local wallpaper = { mt = {} } local function root_geometry() local width, height = root.size() return { x = 0, y = 0, width = width, height = height } end -- Information about a pending wallpaper change, see prepare_context() local pending_wallpaper = nil local function get_screen(s) return s and screen[s] end --- Prepare the needed state for setting a wallpaper. -- This function returns a cairo context through which a wallpaper can be drawn. -- The context is only valid for a short time and should not be saved in a -- global variable. -- @param s The screen to set the wallpaper on or nil for all screens -- @return[1] The available geometry (table with entries width and height) -- @return[1] A cairo context that the wallpaper should be drawn to function wallpaper.prepare_context(s) s = get_screen(s) local root_width, root_height = root.size() local geom = s and s.geometry or root_geometry() local source, target, cr if not pending_wallpaper then -- Prepare a pending wallpaper source = surface(root.wallpaper()) target = source:create_similar(cairo.Content.COLOR, root_width, root_height) -- Set the wallpaper (delayed) timer.delayed_call(function() local paper = pending_wallpaper pending_wallpaper = nil wallpaper.set(paper.surface) paper.surface:finish() end) elseif root_width > pending_wallpaper.width or root_height > pending_wallpaper.height then -- The root window was resized while a wallpaper is pending source = pending_wallpaper.surface target = source:create_similar(cairo.Content.COLOR, root_width, root_height) else -- Draw to the already-pending wallpaper source = nil target = pending_wallpaper.surface end cr = cairo.Context(target) if source then -- Copy the old wallpaper to the new one cr:save() cr.operator = cairo.Operator.SOURCE cr:set_source_surface(source, 0, 0) cr:paint() cr:restore() end pending_wallpaper = { surface = target, width = root_width, height = root_height } -- Only draw to the selected area cr:translate(geom.x, geom.y) cr:rectangle(0, 0, geom.width, geom.height) cr:clip() return geom, cr end --- Set the current wallpaper. -- @param pattern The wallpaper that should be set. This can be a cairo surface, -- a description for gears.color or a cairo pattern. -- @see gears.color function wallpaper.set(pattern) if cairo.Surface:is_type_of(pattern) then pattern = cairo.Pattern.create_for_surface(pattern) end if type(pattern) == "string" or type(pattern) == "table" then pattern = color(pattern) end if not cairo.Pattern:is_type_of(pattern) then error("wallpaper.set() called with an invalid argument") end root.wallpaper(pattern._native) end --- Set a centered wallpaper. -- @param surf The wallpaper to center. Either a cairo surface or a file name. -- @param s The screen whose wallpaper should be set. Can be nil, in which case -- all screens are set. -- @param background The background color that should be used. Gets handled via -- gears.color. The default is black. -- @param scale The scale factor for the wallpaper. Default is 1 (original size). -- @see gears.color function wallpaper.centered(surf, s, background, scale) local geom, cr = wallpaper.prepare_context(s) local original_surf = surf surf = surface.load_uncached(surf) background = color(background) -- Set default scale if unset if not scale or scale <= 0 then scale = 1 end -- Fill the area with the background cr.operator = cairo.Operator.SOURCE cr.source = background cr:paint() -- Now center the surface local w, h = surface.get_size(surf) cr:translate((geom.width - (w * scale)) / 2, (geom.height - (h * scale)) / 2) cr:rectangle(0, 0, (w * scale), (h * scale)) cr:clip() cr:scale(scale, scale) cr:set_source_surface(surf, 0, 0) cr:paint() if surf ~= original_surf then surf:finish() end if cr.status ~= "SUCCESS" then debug.print_warning("Cairo context entered error state: " .. cr.status) end end --- Set a tiled wallpaper. -- @param surf The wallpaper to tile. Either a cairo surface or a file name. -- @param s The screen whose wallpaper should be set. Can be nil, in which case -- all screens are set. -- @param offset This can be set to a table with entries x and y. function wallpaper.tiled(surf, s, offset) local _, cr = wallpaper.prepare_context(s) if offset then cr:translate(offset.x, offset.y) end local original_surf = surf surf = surface.load_uncached(surf) local pattern = cairo.Pattern.create_for_surface(surf) pattern.extend = cairo.Extend.REPEAT cr.source = pattern cr.operator = cairo.Operator.SOURCE cr:paint() if surf ~= original_surf then surf:finish() end if cr.status ~= "SUCCESS" then debug.print_warning("Cairo context entered error state: " .. cr.status) end end --- Set a maximized wallpaper. -- @param surf The wallpaper to set. Either a cairo surface or a file name. -- @param s The screen whose wallpaper should be set. Can be nil, in which case -- all screens are set. -- @param ignore_aspect If this is true, the image's aspect ratio is ignored. -- The default is to honor the aspect ratio. -- @param offset This can be set to a table with entries x and y. function wallpaper.maximized(surf, s, ignore_aspect, offset) local geom, cr = wallpaper.prepare_context(s) local original_surf = surf surf = surface.load_uncached(surf) local w, h = surface.get_size(surf) local aspect_w = geom.width / w local aspect_h = geom.height / h if not ignore_aspect then aspect_h = math.max(aspect_w, aspect_h) aspect_w = math.max(aspect_w, aspect_h) end cr:scale(aspect_w, aspect_h) if offset then cr:translate(offset.x, offset.y) elseif not ignore_aspect then local scaled_width = geom.width / aspect_w local scaled_height = geom.height / aspect_h cr:translate((scaled_width - w) / 2, (scaled_height - h) / 2) end cr:set_source_surface(surf, 0, 0) cr.operator = cairo.Operator.SOURCE cr:paint() if surf ~= original_surf then surf:finish() end if cr.status ~= "SUCCESS" then debug.print_warning("Cairo context entered error state: " .. cr.status) end end --- Set a fitting wallpaper. -- @param surf The wallpaper to set. Either a cairo surface or a file name. -- @param s The screen whose wallpaper should be set. Can be nil, in which case -- all screens are set. -- @param background The background color that should be used. Gets handled via -- gears.color. The default is black. -- @see gears.color function wallpaper.fit(surf, s, background) local geom, cr = wallpaper.prepare_context(s) local original_surf = surf surf = surface.load_uncached(surf) background = color(background) -- Fill the area with the background cr.operator = cairo.Operator.SOURCE cr.source = background cr:paint() -- Now fit the surface local w, h = surface.get_size(surf) local scale = geom.width / w if h * scale > geom.height then scale = geom.height / h end cr:translate((geom.width - (w * scale)) / 2, (geom.height - (h * scale)) / 2) cr:rectangle(0, 0, w * scale, h * scale) cr:clip() cr:scale(scale, scale) cr:set_source_surface(surf, 0, 0) cr:paint() if surf ~= original_surf then surf:finish() end if cr.status ~= "SUCCESS" then debug.print_warning("Cairo context entered error state: " .. cr.status) end end return wallpaper -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80