Merge pull request #773 from Elv13/add_testing_framework
tests: Add a new GUI testing framework
This commit is contained in:
commit
b1a93e4540
|
@ -299,6 +299,11 @@ set(AWESOME_THEMES_PATH ${AWESOME_DATA_PATH}/themes)
|
|||
if(GENERATE_DOC)
|
||||
# Generate some images and examples
|
||||
include(docs/generate_examples.cmake)
|
||||
|
||||
# Use `include`, rather than `add_subdirectory`, to keep the variables
|
||||
# The file is a valid CMakeLists.txt and can be executed directly if only
|
||||
# the image artefacts are needed.
|
||||
include(tests/examples/CMakeLists.txt)
|
||||
endif()
|
||||
|
||||
# {{{ Configure files
|
||||
|
|
|
@ -11,6 +11,7 @@ local capi = { awesome = awesome }
|
|||
local cairo = require("lgi").cairo
|
||||
local color = nil
|
||||
local gdebug = require("gears.debug")
|
||||
local hierarchy = require("wibox.hierarchy")
|
||||
|
||||
-- Keep this in sync with build-utils/lgi-check.sh!
|
||||
local ver_major, ver_minor, ver_patch = string.match(require('lgi.version'), '(%d)%.(%d)%.(%d)')
|
||||
|
@ -205,6 +206,53 @@ function surface.apply_shape_bounding(draw, shape, ...)
|
|||
draw.shape_bounding = img._native
|
||||
end
|
||||
|
||||
local function no_op() end
|
||||
|
||||
local function run_in_hierarchy(self, cr, width, height)
|
||||
|
||||
local function redraw(h)
|
||||
h:draw({dpi=96}, cr)
|
||||
end
|
||||
|
||||
local h = hierarchy.new({dpi=96}, self, width, height, redraw, no_op, {})
|
||||
|
||||
redraw(h)
|
||||
|
||||
return h
|
||||
end
|
||||
|
||||
--- Create an SVG file with this widget content.
|
||||
-- This is dynamic, so the SVG will be updated along with the widget content.
|
||||
-- because of this, the painting may happen hover multiple event loop cycles.
|
||||
-- @tparam widget widget A widget
|
||||
-- @tparam string path The output file path
|
||||
-- @tparam number width The surface width
|
||||
-- @tparam number height The surface height
|
||||
-- @return The cairo surface
|
||||
-- @return The hierarchy
|
||||
function surface.widget_to_svg(widget, path, width, height)
|
||||
local img = cairo.SvgSurface.create(path, width, height)
|
||||
local cr = cairo.Context(img)
|
||||
|
||||
return img, run_in_hierarchy(widget, cr, width, height)
|
||||
end
|
||||
|
||||
--- Create a cairo surface with this widget content.
|
||||
-- This is dynamic, so the SVG will be updated along with the widget content.
|
||||
-- because of this, the painting may happen hover multiple event loop cycles.
|
||||
-- @tparam widget widget A widget
|
||||
-- @tparam number width The surface width
|
||||
-- @tparam number height The surface height
|
||||
-- @param[opt=cairo.Format.ARGB32] format The surface format
|
||||
-- @return The cairo surface
|
||||
-- @return The hierarchy
|
||||
function surface.widget_to_surface(widget, width, height, format)
|
||||
local img = cairo.ImageSurface(format or cairo.Format.ARGB32, width, height)
|
||||
local cr = cairo.Context(img)
|
||||
|
||||
return img, run_in_hierarchy(widget, cr, width, height)
|
||||
end
|
||||
|
||||
return setmetatable(surface, surface.mt)
|
||||
|
||||
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
---------------------------------------------------------------------------
|
||||
-- A container capable of changing the background color, foreground color
|
||||
-- widget shape.
|
||||
-- @author Uli Schlachter
|
||||
-- @copyright 2010 Uli Schlachter
|
||||
-- @release @AWESOME_VERSION@
|
||||
|
@ -17,7 +19,7 @@ local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility
|
|||
|
||||
local background = { mt = {} }
|
||||
|
||||
--- Draw this widget
|
||||
-- Draw this widget
|
||||
function background:draw(context, cr, width, height)
|
||||
if not self.widget or not self.widget.visible then
|
||||
return
|
||||
|
@ -67,7 +69,7 @@ function background:after_draw_children(_, cr)
|
|||
end
|
||||
end
|
||||
|
||||
--- Prepare drawing the children of this widget
|
||||
-- Prepare drawing the children of this widget
|
||||
function background:before_draw_children(_, cr)
|
||||
if self.foreground then
|
||||
cr:set_source(self.foreground)
|
||||
|
@ -80,14 +82,14 @@ function background:before_draw_children(_, cr)
|
|||
end
|
||||
end
|
||||
|
||||
--- Layout this widget
|
||||
-- Layout this widget
|
||||
function background:layout(_, width, height)
|
||||
if self.widget then
|
||||
return { base.place_widget_at(self.widget, 0, 0, width, height) }
|
||||
end
|
||||
end
|
||||
|
||||
--- Fit this widget into the given area
|
||||
-- Fit this widget into the given area
|
||||
function background:fit(context, width, height)
|
||||
if not self.widget then
|
||||
return 0, 0
|
||||
|
@ -97,6 +99,8 @@ function background:fit(context, width, height)
|
|||
end
|
||||
|
||||
--- Set the widget that is drawn on top of the background
|
||||
-- @tparam widget widget The widget to be disaplayed inside of the background
|
||||
-- area
|
||||
function background:set_widget(widget)
|
||||
if widget then
|
||||
base.check_widget(widget)
|
||||
|
@ -105,20 +109,22 @@ function background:set_widget(widget)
|
|||
self:emit_signal("widget::layout_changed")
|
||||
end
|
||||
|
||||
--- Get the number of children element
|
||||
-- Get children element
|
||||
-- @treturn table The children
|
||||
function background:get_children()
|
||||
return {self.widget}
|
||||
end
|
||||
|
||||
--- Replace the layout children
|
||||
-- Replace the layout children
|
||||
-- This layout only accept one children, all others will be ignored
|
||||
-- @tparam table children A table composed of valid widgets
|
||||
function background:set_children(children)
|
||||
self:set_widget(children[1])
|
||||
end
|
||||
|
||||
--- Set the background to use
|
||||
--- Set the background to use.
|
||||
--@DOC_wibox_widget_background_bg_EXAMPLE@
|
||||
-- @param bg A color string, pattern or gradient (see `gears.color`)
|
||||
function background:set_bg(bg)
|
||||
if bg then
|
||||
self.background = color(bg)
|
||||
|
@ -128,7 +134,9 @@ function background:set_bg(bg)
|
|||
self:emit_signal("widget::redraw_needed")
|
||||
end
|
||||
|
||||
--- Set the foreground to use
|
||||
--- Set the foreground to use.
|
||||
--@DOC_wibox_widget_background_fg_EXAMPLE@
|
||||
-- @param fg A color string, pattern or gradient (see `gears.color`)
|
||||
function background:set_fg(fg)
|
||||
if fg then
|
||||
self.foreground = color(fg)
|
||||
|
@ -138,23 +146,29 @@ function background:set_fg(fg)
|
|||
self:emit_signal("widget::redraw_needed")
|
||||
end
|
||||
|
||||
--- Set the background shape
|
||||
-- @param shape A function taking a context, width and height as arguments
|
||||
--- Set the background shape.
|
||||
--
|
||||
-- Any other arguments will be passed to the shape function
|
||||
--@DOC_wibox_widget_background_shape_EXAMPLE@
|
||||
-- @param shape A function taking a context, width and height as arguments
|
||||
function background:set_shape(shape, ...)
|
||||
self._shape = shape
|
||||
self._shape_args = {...}
|
||||
self:emit_signal("widget::redraw_needed")
|
||||
end
|
||||
|
||||
--- When a `shape` is set, also draw a border
|
||||
--- When a `shape` is set, also draw a border.
|
||||
--
|
||||
-- See `wibox.widget.background.set_shape` for an usage example.
|
||||
-- @tparam number width The border width
|
||||
function background:set_shape_border_width(width)
|
||||
self._shape_border_width = width
|
||||
self:emit_signal("widget::redraw_needed")
|
||||
end
|
||||
|
||||
--- When a `shape` is set, also draw a border
|
||||
--- When a `shape` is set, also draw a border.
|
||||
--
|
||||
-- See `wibox.widget.background.set_shape` for an usage example.
|
||||
-- @param[opt=self.foreground] fg The border color, pattern or gradient
|
||||
function background:set_shape_border_color(fg)
|
||||
self._shape_border_color = fg
|
||||
|
@ -162,6 +176,7 @@ function background:set_shape_border_color(fg)
|
|||
end
|
||||
|
||||
--- When a `shape` is set, make sure nothing is drawn outside of it.
|
||||
--@DOC_wibox_widget_background_clip_EXAMPLE@
|
||||
-- @tparam boolean value If the shape clip is enable
|
||||
function background:set_shape_clip(value)
|
||||
self._shape_clip = value
|
||||
|
|
|
@ -182,6 +182,7 @@ function clear_caches(widget)
|
|||
clear_caches(w)
|
||||
end
|
||||
end
|
||||
|
||||
-- }}}
|
||||
|
||||
--- Figure out the geometry in device coordinate space. This gives only tight
|
||||
|
|
|
@ -0,0 +1,251 @@
|
|||
# This module is designed to allow the Awesome documentation examples to be
|
||||
# tested.
|
||||
#
|
||||
# It shims enough of the Awesome C API to allow code to be executed without an
|
||||
# actual X server or running Awesome process. These tests are not genuine
|
||||
# integration tests, but they are the next best thing.
|
||||
#
|
||||
# # As secondary goals, this module also generates images of the test result where
|
||||
# relevant. Those images are used by the documentation and help the developers
|
||||
# track user interface regressions and glitches. Finally, it also helps to find
|
||||
# broken code.
|
||||
cmake_minimum_required(VERSION 3.0.0)
|
||||
|
||||
# Get and update the LUA_PATH so the scripts can be executed without Awesome.
|
||||
execute_process(COMMAND lua -e print\(package.path\) OUTPUT_VARIABLE "LUA_PATH_")
|
||||
|
||||
# Add the main awesome lua libraries.
|
||||
set(LUA_PATH2_ "\
|
||||
${CMAKE_SOURCE_DIR}/lib/?.lua;\
|
||||
${CMAKE_SOURCE_DIR}/lib/?/init.lua;\
|
||||
${CMAKE_SOURCE_DIR}/lib/?;"
|
||||
)
|
||||
|
||||
# Add the C API shims.
|
||||
set(LUA_PATH3_ "\
|
||||
${CMAKE_SOURCE_DIR}/tests/examples/shims/?.lua;\
|
||||
${CMAKE_SOURCE_DIR}/tests/examples/shims/?/init.lua;\
|
||||
${CMAKE_SOURCE_DIR}/tests/examples/shims/?;"
|
||||
)
|
||||
|
||||
# Done in 3 variables to avoid CMake from implicitly converting into a list.
|
||||
set(ENV{LUA_PATH} "${LUA_PATH3_}${LUA_PATH2_}${LUA_PATH_}")
|
||||
|
||||
# The documentation images directory
|
||||
set(IMAGE_DIR "${CMAKE_BINARY_DIR}/doc/images")
|
||||
file(MAKE_DIRECTORY "${IMAGE_DIR}")
|
||||
|
||||
# Escape potentially multiline strings to be part of the API doc.
|
||||
# * add "--" in front of each lines
|
||||
# * add a custom prefix in front of each lines
|
||||
# * drop empty lines
|
||||
# * convert " " lines into empty lines
|
||||
# * drop lines ending with "--DOC_SOMETHING", they are handled elsewhere
|
||||
function(escape_string variable content escaped_content line_prefix)
|
||||
# If DOC_HIDE_ALL is present, do nothing
|
||||
if(variable MATCHES "--DOC_HIDE_ALL")
|
||||
return()
|
||||
endif()
|
||||
|
||||
string(REGEX REPLACE "\n" ";" var_lines "${variable}")
|
||||
|
||||
set(tmp_output ${content})
|
||||
foreach (LINE ${var_lines})
|
||||
if(NOT LINE MATCHES "^.+--DOC_[A-Z]+$")
|
||||
set(tmp_output ${tmp_output}\n--${line_prefix}${LINE})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
set(${escaped_content} ${tmp_output} PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Extract lines with the --DOC_HEADER marker
|
||||
function(extract_header variable pre_output post_output)
|
||||
string(REGEX REPLACE "\n" ";" var_lines "${variable}")
|
||||
|
||||
set(IS_POST 0)
|
||||
|
||||
foreach (LINE ${var_lines})
|
||||
# This function doesn't escape the lines, so make sure they are
|
||||
# already comments.
|
||||
if(LINE MATCHES "^--.*--DOC_HEADER")
|
||||
# Remove the header tag
|
||||
string(REGEX REPLACE "[ ]*--DOC_HEADER" "" LINE "${LINE}")
|
||||
|
||||
# ldoc is picky about what happen after the first --@, so split
|
||||
# the output between all that come before and all that come after.
|
||||
if (NOT IS_POST AND LINE MATCHES "^--[ ]*@")
|
||||
set(IS_POST 1)
|
||||
endif()
|
||||
|
||||
if (IS_POST)
|
||||
set(tmp_post_output ${tmp_post_output}\n${line_prefix}${LINE})
|
||||
else()
|
||||
set(tmp_pre_output ${tmp_pre_output}\n${line_prefix}${LINE})
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
set(${post_output} "${tmp_post_output}\n--" PARENT_SCOPE)
|
||||
set(${pre_output} "${tmp_pre_output}\n--" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Read a code file and convert it to ldoc usage example.
|
||||
# * add "--" in front of each lines
|
||||
# * drop empty lines
|
||||
# * convert " " lines into empty lines
|
||||
# * drop lines ending with "--DOC_HIDE"
|
||||
function(escape_code path escaped_content pre_header post_header)
|
||||
file(READ ${path} path)
|
||||
|
||||
escape_string("${path}\n" "" escaped_code "")
|
||||
|
||||
extract_header("${path}" example_pre_header example_post_header)
|
||||
|
||||
set(${escaped_content} ${escaped_code} PARENT_SCOPE)
|
||||
set(${pre_header} ${example_pre_header} PARENT_SCOPE)
|
||||
set(${post_header} ${example_post_header} PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Execute a lua file.
|
||||
function(run_test test_path namespace template escaped_content)
|
||||
|
||||
# A template is required to know how to handle the output.
|
||||
if (template STREQUAL " ")
|
||||
message(FATAL_ERROR "No template found for " ${test_path} ", bye")
|
||||
endif()
|
||||
|
||||
# Get the file name without the extension
|
||||
get_filename_component(${test_path} TEST_FILE_NAME NAME)
|
||||
set(IMAGE_PATH "${IMAGE_DIR}/${namespace}_${TEST_FILE_NAME}")
|
||||
|
||||
# Execute the script, leave the image extension decision to the test
|
||||
# SVG is preferred, but PNG is better suited for some tests, like bitmap
|
||||
# patterns.
|
||||
execute_process(
|
||||
COMMAND lua ${template} ${test_path} ${IMAGE_PATH} ${SOURCE_DIR}/.luacov
|
||||
OUTPUT_VARIABLE TEST_OUTPUT
|
||||
ERROR_VARIABLE TEST_ERROR
|
||||
)
|
||||
|
||||
# If there is something on stderr, exit
|
||||
if (NOT TEST_ERROR STREQUAL "")
|
||||
message("${TEST_OUTPUT}")
|
||||
message("${TEST_ERROR}")
|
||||
message(FATAL_ERROR ${test_path} " A test failed, bye")
|
||||
endif()
|
||||
|
||||
# Read the code and turn it into an usage example.
|
||||
escape_code(${test_path} TEST_CODE TEST_PRE_HEADER TEST_POST_HEADER)
|
||||
|
||||
# Build the documentation.
|
||||
set(TEST_DOC_CONTENT "${TEST_PRE_HEADER}")
|
||||
|
||||
# If the image has been created, then add it.
|
||||
if(EXISTS "${IMAGE_PATH}.svg")
|
||||
escape_string(
|
||||
"![Usage example](../images/${namespace}_${TEST_FILE_NAME}.svg)\n"
|
||||
"${TEST_DOC_CONTENT}" TEST_DOC_CONTENT ""
|
||||
)
|
||||
elseif(EXISTS "${IMAGE_PATH}.png")
|
||||
escape_string(
|
||||
"![Usage example](../images/${namespace}_${TEST_FILE_NAME}.png)\n"
|
||||
"${TEST_DOC_CONTENT}" TEST_DOC_CONTENT ""
|
||||
)
|
||||
endif()
|
||||
|
||||
# If there is an output, assume it is relevant and add it to the
|
||||
# documentation under the image.
|
||||
if(NOT ${TEST_OUTPUT} STREQUAL "")
|
||||
set(TEST_DOC_CONTENT
|
||||
"${TEST_DOC_CONTENT}\n--\n--**Usage example output**:\n--"
|
||||
)
|
||||
|
||||
# Markdown require an empty line before and after + 4 spaces.
|
||||
escape_string(
|
||||
"\n${TEST_OUTPUT}"
|
||||
"${TEST_DOC_CONTENT}" TEST_DOC_CONTENT " "
|
||||
)
|
||||
set(TEST_DOC_CONTENT "${TEST_DOC_CONTENT}\n--")
|
||||
endif()
|
||||
|
||||
# If there is some @* content, append it.
|
||||
set(TEST_DOC_CONTENT "${TEST_DOC_CONTENT}${TEST_POST_HEADER}")
|
||||
|
||||
# Only add it if there is something to display.
|
||||
if(NOT ${TEST_CODE} STREQUAL "\n--")
|
||||
escape_string(
|
||||
" @usage"
|
||||
"${TEST_DOC_CONTENT}" TEST_DOC_CONTENT ""
|
||||
)
|
||||
set(TEST_DOC_CONTENT "${TEST_DOC_CONTENT}${TEST_CODE}")
|
||||
endif()
|
||||
|
||||
# Export the outout to the parent scope
|
||||
set(${escaped_content} "${TEST_DOC_CONTENT}" PARENT_SCOPE)
|
||||
|
||||
endfunction()
|
||||
|
||||
# Recursive helper function to avoid adding CMakeLists.txt and add_subdirectory
|
||||
# in every sub-directories.
|
||||
function(digg path namespace template)
|
||||
|
||||
# Check if there is a template for this directory, else use the
|
||||
# last known one.
|
||||
if(EXISTS ${path}/template.lua)
|
||||
message(STATUS "Testing code based on ${namespace}")
|
||||
set(template ${path}/template.lua)
|
||||
endif()
|
||||
|
||||
# Get the directory content
|
||||
file(GLOB ex_files RELATIVE "${path}"
|
||||
"${path}/*")
|
||||
|
||||
foreach(ex_file_name ${ex_files})
|
||||
if(IS_DIRECTORY ${path}/${ex_file_name}
|
||||
AND (NOT ${ex_file_name} STREQUAL "shims"))
|
||||
|
||||
digg("${path}/${ex_file_name}" "${namespace}_${ex_file_name}" ${template})
|
||||
|
||||
elseif(${ex_file_name} MATCHES ".lua"
|
||||
AND NOT ${ex_file_name} MATCHES "template.lua")
|
||||
|
||||
# Get the file name without the extension
|
||||
string(REGEX REPLACE "\\.lua" "" TEST_FILE_NAME ${ex_file_name})
|
||||
|
||||
run_test("${path}/${ex_file_name}" "${namespace}" ${template} ESCAPED_CODE_EXAMPLE)
|
||||
|
||||
# Set the test name
|
||||
set(TEST_NAME DOC${namespace}_${TEST_FILE_NAME}_EXAMPLE)
|
||||
|
||||
# Anything called @DOC_`namespace`_EXAMPLE@
|
||||
# in the Lua or C sources will be replaced by the content if that
|
||||
# variable during the pre-processing
|
||||
set(ENV{${TEST_NAME}} "${ESCAPED_CODE_EXAMPLE}" CACHE INTERNAL FORCE)
|
||||
|
||||
# Update the test list
|
||||
set(ENV{EXAMPLE_LIST} "$ENV{EXAMPLE_LIST};${TEST_NAME}")
|
||||
|
||||
endif()
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
# Start at the top level then recursively explore the sub-directories to locate
|
||||
# the test. In parallel, build a namespace for the global variables. Those
|
||||
# variables will be inserted into the lua source code itself once the examples
|
||||
# are validated.
|
||||
digg("${SOURCE_DIR}/tests/examples" "" " ")
|
||||
|
||||
# This is ugly, but CMake variable scope system totally ignore 50 years of
|
||||
# computer science evolution and only support function local variables.
|
||||
# PARENT_SCOPE is useless in recursive methods and the CMake pre-processor
|
||||
# can't access ENV variables. So the only (insane) way is to set tons of ENV
|
||||
# variables, keep track of them in yet another one and set them in the global
|
||||
# scope once in the "top level" CMakeLists section (it cannot be done from
|
||||
# functions).
|
||||
foreach(vari $ENV{EXAMPLE_LIST})
|
||||
# While at it, replace \" created by CMake by ', " wont work in <code>
|
||||
string(REGEX REPLACE "\\\"" "'" ${vari} $ENV{${vari}})
|
||||
endforeach()
|
||||
|
||||
message(STATUS "All test passed!")
|
|
@ -0,0 +1,154 @@
|
|||
local file_path, image_path, luacovpath = ...
|
||||
|
||||
local cairo = require("lgi").cairo
|
||||
local pango = require("lgi").Pango
|
||||
local pangocairo = require("lgi").PangoCairo
|
||||
|
||||
-- Set the global shims
|
||||
-- luacheck: globals awesome root tag screen client mouse
|
||||
awesome = require( "awesome" )
|
||||
root = require( "root" )
|
||||
tag = require( "tag" )
|
||||
screen = require( "screen" )
|
||||
client = require( "client" )
|
||||
mouse = require( "mouse" )
|
||||
|
||||
-- Force luacheck to be silent about setting those as unused globals
|
||||
assert(awesome and root and tag and screen and client and mouse)
|
||||
|
||||
-- If luacov is available, use it. Else, do nothing.
|
||||
pcall(function()
|
||||
require("luacov.runner")(luacovpath)
|
||||
end)
|
||||
|
||||
local color = require( "gears.color" )
|
||||
local shape = require( "gears.shape" )
|
||||
local beautiful = require( "beautiful" )
|
||||
|
||||
-- Run the test
|
||||
local args = loadfile(file_path)() or {}
|
||||
|
||||
-- Draw the result
|
||||
local img = cairo.SvgSurface.create(image_path..".svg", screen._get_extents() )
|
||||
|
||||
local cr = cairo.Context(img)
|
||||
|
||||
local pango_crx = pangocairo.font_map_get_default():create_context()
|
||||
local pl = pango.Layout.new(pango_crx)
|
||||
|
||||
-- Draw some text inside of the geometry
|
||||
local function draw_label(geo, text)
|
||||
cr:save()
|
||||
cr:set_source(color(beautiful.fg_normal))
|
||||
cr:translate(geo.x, geo.y)
|
||||
pl.text = text
|
||||
cr:show_layout(pl)
|
||||
cr:restore()
|
||||
end
|
||||
|
||||
-- Draw a mouse cursor at [x,y]
|
||||
local function draw_mouse(x, y)
|
||||
cr:move_to(x, y)
|
||||
cr:rel_line_to( 0, 10)
|
||||
cr:rel_line_to( 3, -2)
|
||||
cr:rel_line_to( 3, 4)
|
||||
cr:rel_line_to( 2, 0)
|
||||
cr:rel_line_to(-3, -4)
|
||||
cr:rel_line_to( 4, 0)
|
||||
cr:close_path()
|
||||
cr:fill()
|
||||
end
|
||||
|
||||
-- Print an outline for the screens
|
||||
for _, s in ipairs(screen) do
|
||||
cr:save()
|
||||
-- Draw the screen outline
|
||||
cr:set_source(color("#00000044"))
|
||||
cr:set_line_width(1.5)
|
||||
cr:set_dash({10,4},1)
|
||||
cr:rectangle(s.geometry.x+0.75,s.geometry.y+0.75,s.geometry.width-1.5,s.geometry.height-1.5)
|
||||
cr:stroke()
|
||||
|
||||
-- Draw the workarea outline
|
||||
cr:set_source(color("#00000033"))
|
||||
cr:rectangle(s.workarea.x,s.workarea.y,s.workarea.width,s.workarea.height)
|
||||
cr:stroke()
|
||||
|
||||
-- Draw the padding outline
|
||||
--TODO
|
||||
cr:restore()
|
||||
end
|
||||
|
||||
cr:set_line_width(beautiful.border_width)
|
||||
cr:set_source(color(beautiful.border_color))
|
||||
|
||||
-- Loop each clients geometry history and paint it
|
||||
for _, c in ipairs(client.get()) do
|
||||
local pgeo = nil
|
||||
for _, geo in ipairs(c._old_geo) do
|
||||
if not geo._hide then
|
||||
cr:save()
|
||||
cr:translate(geo.x, geo.y)
|
||||
shape.rounded_rect(cr, geo.width, geo.height, args.radius or 5)
|
||||
cr:stroke_preserve()
|
||||
cr:set_source(color(c.color or geo._color or beautiful.bg_normal))
|
||||
cr:fill()
|
||||
cr:restore()
|
||||
|
||||
if geo._label then
|
||||
draw_label(geo, geo._label)
|
||||
end
|
||||
end
|
||||
|
||||
-- Draw lines between the old and new corners
|
||||
if pgeo and not args.hide_lines then
|
||||
cr:save()
|
||||
cr:set_source_rgba(0,0,0,.1)
|
||||
|
||||
-- Top left
|
||||
cr:move_to(pgeo.x, pgeo.y)
|
||||
cr:line_to(geo.x, geo.y)
|
||||
cr:stroke()
|
||||
|
||||
-- Top right
|
||||
cr:move_to(pgeo.x+pgeo.width, pgeo.y)
|
||||
cr:line_to(geo.x+pgeo.width, geo.y)
|
||||
|
||||
-- Bottom left
|
||||
cr:move_to(pgeo.x, pgeo.y+pgeo.height)
|
||||
cr:line_to(geo.x, geo.y+geo.height)
|
||||
cr:stroke()
|
||||
|
||||
-- Bottom right
|
||||
cr:move_to(pgeo.x+pgeo.width, pgeo.y+pgeo.height)
|
||||
cr:line_to(geo.x+pgeo.width, geo.y+geo.height)
|
||||
cr:stroke()
|
||||
|
||||
cr:restore()
|
||||
end
|
||||
|
||||
pgeo = geo
|
||||
end
|
||||
end
|
||||
|
||||
cr:set_source_rgba(1,0,0,1)
|
||||
cr:set_dash({1,1},1)
|
||||
|
||||
-- Paint the mouse cursor position history
|
||||
for _, h in ipairs(mouse.old_histories) do
|
||||
local pos = nil
|
||||
for _, coords in ipairs(h) do
|
||||
draw_mouse(coords.x, coords.y)
|
||||
cr:fill()
|
||||
|
||||
if pos then
|
||||
cr:move_to(pos.x, pos.y)
|
||||
cr:line_to(coords.x, coords.y)
|
||||
cr:stroke()
|
||||
end
|
||||
|
||||
pos = coords
|
||||
end
|
||||
end
|
||||
|
||||
img:finish()
|
|
@ -0,0 +1,60 @@
|
|||
local gears_obj = require("gears.object")
|
||||
|
||||
-- Emulate the C API classes. They differ from C API objects as connect_signal
|
||||
-- doesn't take an object as first argument and they support fallback properties
|
||||
-- handlers.
|
||||
local function _shim_fake_class()
|
||||
local obj = gears_obj()
|
||||
|
||||
local meta = {
|
||||
__index = function()end,
|
||||
__new_index = function()end,
|
||||
}
|
||||
|
||||
obj._connect_signal = obj.connect_signal
|
||||
|
||||
function obj.connect_signal(name, func)
|
||||
return obj._connect_signal(obj, name, func)
|
||||
end
|
||||
|
||||
obj._add_signal = obj.add_signal
|
||||
|
||||
function obj.add_signal(name)
|
||||
return obj._add_signal(obj, name)
|
||||
end
|
||||
|
||||
function obj.set_index_miss_handler(handler)
|
||||
meta.__index = handler
|
||||
end
|
||||
|
||||
function obj.set_newindex_miss_handler(handler)
|
||||
meta.__new_index = handler
|
||||
end
|
||||
|
||||
function obj.emit_signal(name, c, ...)
|
||||
local conns = obj._signals[name] or {strong={}}
|
||||
for func in pairs(conns.strong) do
|
||||
func(c, ...)
|
||||
end
|
||||
end
|
||||
|
||||
return obj, meta
|
||||
end
|
||||
|
||||
local awesome = _shim_fake_class()
|
||||
awesome._shim_fake_class = _shim_fake_class
|
||||
|
||||
-- Avoid c.screen = acreen.focused() to be called, all tests will fail
|
||||
awesome.startup = true
|
||||
|
||||
function awesome.register_xproperty()
|
||||
end
|
||||
|
||||
awesome.add_signal("refresh")
|
||||
awesome.add_signal("wallpaper_changed")
|
||||
awesome.add_signal("spawn::canceled")
|
||||
awesome.add_signal("spawn::timeout")
|
||||
|
||||
return awesome
|
||||
|
||||
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
|
|
@ -0,0 +1,24 @@
|
|||
local lgi = require("lgi")
|
||||
local Pango = lgi.Pango
|
||||
|
||||
-- Default theme for the documentation examples
|
||||
local module = {
|
||||
fg_normal = "#000000" ,
|
||||
bg_normal = "#6181FF7D",
|
||||
bg_highlight = "#AA00FF7D",
|
||||
border_color = "#6181FF" ,
|
||||
border_width = 1.5 ,
|
||||
|
||||
-- Fake resources handling
|
||||
xresources = require("beautiful.xresources")
|
||||
}
|
||||
|
||||
local f = Pango.FontDescription.from_string("sans 8")
|
||||
|
||||
function module.get_font()
|
||||
return f
|
||||
end
|
||||
|
||||
return module
|
||||
|
||||
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
|
|
@ -0,0 +1,8 @@
|
|||
return {
|
||||
apply_dpi = function(size, _)
|
||||
return size
|
||||
end,
|
||||
get_dpi = function() return 96 end,
|
||||
}
|
||||
|
||||
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
|
|
@ -0,0 +1,171 @@
|
|||
local gears_obj = require("gears.object")
|
||||
|
||||
local clients = {}
|
||||
|
||||
local client = awesome._shim_fake_class()
|
||||
|
||||
local function add_signals(c)
|
||||
c:add_signal("property::width")
|
||||
c:add_signal("property::height")
|
||||
c:add_signal("property::x")
|
||||
c:add_signal("property::y")
|
||||
c:add_signal("property::screen")
|
||||
c:add_signal("property::geometry")
|
||||
c:add_signal("request::geometry")
|
||||
c:add_signal("swapped")
|
||||
c:add_signal("raised")
|
||||
c:add_signal("property::_label") --Used internally
|
||||
end
|
||||
|
||||
-- Keep an history of the geometry for validation and images
|
||||
local function push_geometry(c)
|
||||
table.insert(c._old_geo, c:geometry())
|
||||
end
|
||||
|
||||
-- Undo the last move, but keep it in history
|
||||
-- local function pop_geometry(c)
|
||||
-- CURRENTLY UNUSED
|
||||
-- end
|
||||
|
||||
-- Create fake clients to move around
|
||||
function client.gen_fake(args)
|
||||
local ret = gears_obj()
|
||||
ret.type = "normal"
|
||||
ret.valid = true
|
||||
ret.size_hints = {}
|
||||
ret.border_width = 1
|
||||
|
||||
-- Apply all properties
|
||||
for k,v in pairs(args or {}) do
|
||||
ret[k] = v
|
||||
end
|
||||
|
||||
-- Tests should always set a geometry, but just in case
|
||||
for _, v in ipairs{"x","y","width","height"} do
|
||||
ret[v] = ret[v] or 1
|
||||
end
|
||||
|
||||
|
||||
add_signals(ret)
|
||||
|
||||
-- Emulate capi.client.geometry
|
||||
function ret:geometry(new)
|
||||
if new then
|
||||
for k,v in pairs(new) do
|
||||
ret[k] = v
|
||||
ret:emit_signal("property::"..k, v)
|
||||
end
|
||||
ret:emit_signal("property::geometry", ret:geometry())
|
||||
push_geometry(ret)
|
||||
end
|
||||
|
||||
return {
|
||||
x = ret.x,
|
||||
y = ret.y,
|
||||
width = ret.width,
|
||||
height = ret.height,
|
||||
label = ret._label,
|
||||
}
|
||||
end
|
||||
|
||||
function ret:isvisible()
|
||||
return true
|
||||
end
|
||||
|
||||
-- Used for screenshots
|
||||
function ret:set_label(text)
|
||||
ret._old_geo[#ret._old_geo]._label = text
|
||||
end
|
||||
|
||||
-- Used for screenshots, hide the current client position
|
||||
function ret:_hide()
|
||||
ret._old_geo[#ret._old_geo]._hide = true
|
||||
end
|
||||
|
||||
function ret:get_xproperty()
|
||||
return nil
|
||||
end
|
||||
|
||||
function ret:tags(new) --FIXME
|
||||
if new then
|
||||
ret._tags = new
|
||||
end
|
||||
|
||||
if ret._tags then
|
||||
return ret._tags
|
||||
end
|
||||
|
||||
for _, t in ipairs(root._tags) do
|
||||
if t.screen == ret.screen then
|
||||
return {t}
|
||||
end
|
||||
end
|
||||
|
||||
return {}
|
||||
end
|
||||
|
||||
-- Record the geometry
|
||||
ret._old_geo = {}
|
||||
push_geometry(ret)
|
||||
|
||||
-- Set the attributes
|
||||
ret.screen = args.screen or screen[1]
|
||||
|
||||
-- Add to the client list
|
||||
table.insert(clients, ret)
|
||||
|
||||
client.focus = ret
|
||||
|
||||
client.emit_signal("manage", ret)
|
||||
assert(not args.screen or (args.screen == ret.screen))
|
||||
|
||||
return ret
|
||||
end
|
||||
|
||||
function client.get(s)
|
||||
if not s then return clients end
|
||||
|
||||
local s2 = screen[s]
|
||||
|
||||
local ret = {}
|
||||
|
||||
for _,c in ipairs(clients) do
|
||||
if c.screen == s2 then
|
||||
table.insert(ret, c)
|
||||
end
|
||||
end
|
||||
|
||||
return ret
|
||||
end
|
||||
|
||||
client:_add_signal("manage")
|
||||
client:_add_signal("unmanage")
|
||||
client:_add_signal("property::urgent")
|
||||
client:_add_signal("untagged")
|
||||
client:_add_signal("tagged")
|
||||
client:_add_signal("property::shape_client_bounding")
|
||||
client:_add_signal("property::shape_client_clip")
|
||||
client:_add_signal("property::width")
|
||||
client:_add_signal("property::height")
|
||||
client:_add_signal("property::x")
|
||||
client:_add_signal("property::y")
|
||||
client:_add_signal("property::geometry")
|
||||
client:_add_signal("focus")
|
||||
client:_add_signal("new")
|
||||
client:_add_signal("property::size_hints_honor")
|
||||
client:_add_signal("property::struts")
|
||||
client:_add_signal("property::minimized")
|
||||
client:_add_signal("property::maximized_horizontal")
|
||||
client:_add_signal("property::maximized_vertical")
|
||||
client:_add_signal("property::sticky")
|
||||
client:_add_signal("property::fullscreen")
|
||||
client:_add_signal("property::border_width")
|
||||
client:_add_signal("property::hidden")
|
||||
client:_add_signal("property::screen")
|
||||
client:_add_signal("raised")
|
||||
client:_add_signal("lowered")
|
||||
client:_add_signal("list")
|
||||
|
||||
return client
|
||||
|
||||
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
|
|
@ -0,0 +1,27 @@
|
|||
local screen = require("screen")
|
||||
|
||||
local coords = {x=100,y=100}
|
||||
|
||||
local mouse = {
|
||||
screen = screen[1],
|
||||
old_histories = {},
|
||||
history = {},
|
||||
}
|
||||
|
||||
function mouse.coords(args)
|
||||
if args then
|
||||
coords.x, coords.y = args.x, args.y
|
||||
table.insert(mouse.history, {x=coords.x, y=coords.y})
|
||||
end
|
||||
|
||||
return coords
|
||||
end
|
||||
|
||||
function mouse.push_history()
|
||||
table.insert(mouse.old_histories, mouse.history)
|
||||
mouse.history = {}
|
||||
end
|
||||
|
||||
return mouse
|
||||
|
||||
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
|
|
@ -0,0 +1,9 @@
|
|||
local root = {_tags={}}
|
||||
|
||||
function root:tags()
|
||||
return root._tags
|
||||
end
|
||||
|
||||
return root
|
||||
|
||||
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
|
|
@ -0,0 +1,101 @@
|
|||
local gears_obj = require("gears.object")
|
||||
|
||||
local screen = awesome._shim_fake_class()
|
||||
|
||||
screen.count = 1
|
||||
|
||||
local function create_screen(args)
|
||||
local s = gears_obj()
|
||||
|
||||
s:add_signal("property::workarea")
|
||||
s:add_signal("padding")
|
||||
|
||||
-- Copy the geo in case the args are mutated
|
||||
local geo = {
|
||||
x = args.x ,
|
||||
y = args.y ,
|
||||
width = args.width ,
|
||||
height = args.height,
|
||||
}
|
||||
|
||||
function s._resize(args2)
|
||||
geo.x = args2.x or geo.x
|
||||
geo.y = args2.y or geo.y
|
||||
geo.width = args2.width or geo.width
|
||||
geo.height = args2.height or geo.height
|
||||
end
|
||||
|
||||
local wa = args.workarea_sides or 10
|
||||
|
||||
return setmetatable(s,{ __index = function(_, key)
|
||||
if key == "geometry" then
|
||||
return {
|
||||
x = geo.x or 0,
|
||||
y = geo.y or 0,
|
||||
width = geo.width ,
|
||||
height = geo.height,
|
||||
}
|
||||
elseif key == "workarea" then
|
||||
return {
|
||||
x = (geo.x or 0) + wa ,
|
||||
y = (geo.y or 0) + wa ,
|
||||
width = geo.width - 2*wa,
|
||||
height = geo.height - 2*wa,
|
||||
}
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
local screens = {}
|
||||
|
||||
function screen._add_screen(args)
|
||||
local s = create_screen(args)
|
||||
table.insert(screens, s)
|
||||
s.index = #screens
|
||||
screen[#screen+1] = s
|
||||
screen[s] = s
|
||||
end
|
||||
|
||||
function screen._get_extents()
|
||||
local xmax, ymax
|
||||
for _, v in ipairs(screen) do
|
||||
if not xmax or v.geometry.x+v.geometry.width > xmax.geometry.x+xmax.geometry.width then
|
||||
xmax = v
|
||||
end
|
||||
if not ymax or v.geometry.y+v.geometry.height > ymax.geometry.y+ymax.geometry.height then
|
||||
ymax = v
|
||||
end
|
||||
end
|
||||
|
||||
return xmax.geometry.x+xmax.geometry.width, ymax.geometry.y+ymax.geometry.height
|
||||
end
|
||||
|
||||
function screen._clear()
|
||||
for i=1, #screen do
|
||||
screen[screen[i]] = nil
|
||||
screen[i] = nil
|
||||
end
|
||||
screens = {}
|
||||
end
|
||||
|
||||
function screen._setup_grid(w, h, rows, args)
|
||||
args = args or {}
|
||||
screen._clear()
|
||||
for i, row in ipairs(rows) do
|
||||
for j=1, row do
|
||||
args.x = (j-1)*w + (j-1)*10
|
||||
args.y = (i-1)*h + (i-1)*10
|
||||
args.width = w
|
||||
args.height = h
|
||||
screen._add_screen(args)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
screen._add_screen {width=320, height=240}
|
||||
|
||||
return screen
|
||||
|
||||
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
local gears_obj = require("gears.object")
|
||||
|
||||
local tag, meta = awesome._shim_fake_class()
|
||||
|
||||
local function new_tag(_, args)
|
||||
local ret = gears_obj()
|
||||
|
||||
ret:add_signal("property::layout")
|
||||
ret:add_signal("property::name")
|
||||
ret:add_signal("property::geometry")
|
||||
ret:add_signal("property::screen")
|
||||
ret:add_signal("property::mwfact")
|
||||
ret:add_signal("property::ncol")
|
||||
ret:add_signal("property::nmaster")
|
||||
ret:add_signal("property::index")
|
||||
ret:add_signal("property::useless_gap")
|
||||
ret:add_signal("property::_wa_tracker")
|
||||
|
||||
ret.name = args.name or "test"
|
||||
ret.activated = true
|
||||
ret.selected = true
|
||||
|
||||
function ret:clients(_) --TODO handle new
|
||||
local list = {}
|
||||
for _, c in ipairs(client.get()) do
|
||||
if c.screen == (ret.screen or screen[1]) then
|
||||
table.insert(list, c)
|
||||
end
|
||||
end
|
||||
|
||||
return list
|
||||
end
|
||||
|
||||
table.insert(root._tags, ret)
|
||||
|
||||
return setmetatable(ret, {
|
||||
__index = function(...) return meta.__index(...) end,
|
||||
__new_index = function(...) return meta.__new_index(...) end
|
||||
})
|
||||
end
|
||||
|
||||
tag:_add_signal("request::select")
|
||||
tag:_add_signal("property::selected")
|
||||
tag:_add_signal("property::activated")
|
||||
tag:_add_signal("tagged")
|
||||
tag:_add_signal("untagged")
|
||||
|
||||
return setmetatable(tag, {
|
||||
__call = new_tag,
|
||||
})
|
||||
|
||||
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
local file_path, image_path, luacovpath = ...
|
||||
|
||||
-- Set the global shims
|
||||
-- luacheck: globals awesome client tag
|
||||
awesome = require( "awesome" )
|
||||
client = require( "client" )
|
||||
tag = require( "tag" )
|
||||
|
||||
-- Force luacheck to be silent about setting those as unused globals
|
||||
assert(awesome and client and tag)
|
||||
|
||||
local wibox = require( "wibox" )
|
||||
local surface = require( "gears.surface" )
|
||||
|
||||
-- If luacov is available, use it. Else, do nothing.
|
||||
pcall(function()
|
||||
require("luacov.runner")(luacovpath)
|
||||
end)
|
||||
|
||||
-- This is the main widget the tests will use as top level
|
||||
local container = wibox.layout.fixed.vertical()
|
||||
|
||||
-- Let the test request a size and file format
|
||||
local w, h, image_type = loadfile(file_path)(container)
|
||||
image_type = image_type or "svg"
|
||||
|
||||
-- Emulate the event loop for 10 iterations
|
||||
for _ = 1, 10 do
|
||||
awesome:emit_signal("refresh")
|
||||
end
|
||||
|
||||
-- Get the example fallback size (the tests can return a size if the want)
|
||||
local f_w, f_h = container:fit({dpi=96}, 9999, 9999)
|
||||
|
||||
-- There is an overhead that cause testbox "...", add 10 until someone
|
||||
-- figures out the real equation
|
||||
f_w, f_h = f_w+10, f_h+10
|
||||
|
||||
-- Save to the output file
|
||||
local img = surface["widget_to_"..image_type](container, image_path.."."..image_type, w or f_w, h or f_h)
|
||||
img:finish()
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
local parent = ... --DOC_HIDE
|
||||
local wibox = require("wibox") --DOC_HIDE
|
||||
|
||||
local text_widget = {
|
||||
text = "Hello world!",
|
||||
widget = wibox.widget.textbox
|
||||
}
|
||||
|
||||
parent : setup {
|
||||
{
|
||||
text_widget,
|
||||
bg = '#ff0000',
|
||||
widget = wibox.widget.background
|
||||
},
|
||||
{
|
||||
text_widget,
|
||||
bg = '#00ff00',
|
||||
widget = wibox.widget.background
|
||||
},
|
||||
{
|
||||
text_widget,
|
||||
bg = '#0000ff',
|
||||
widget = wibox.widget.background
|
||||
},
|
||||
spacing = 10,
|
||||
layout = wibox.layout.fixed.vertical
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
local parent = ... --DOC_HIDE
|
||||
local wibox = require("wibox") --DOC_HIDE
|
||||
local gears = {shape = require("gears.shape")} --DOC_HIDE
|
||||
local beautiful = require("beautiful") --DOC_HIDE
|
||||
|
||||
parent : setup {
|
||||
{
|
||||
-- Some content may be outside of the shape
|
||||
{
|
||||
text = "Hello\nworld!",
|
||||
widget = wibox.widget.textbox
|
||||
},
|
||||
shape = gears.shape.circle,
|
||||
bg = beautiful.bg_normal,
|
||||
shape_border_color = beautiful.border_color,
|
||||
widget = wibox.widget.background
|
||||
},
|
||||
{
|
||||
-- To solve this, clip the content
|
||||
{
|
||||
text = "Hello\nworld!",
|
||||
widget = wibox.widget.textbox
|
||||
},
|
||||
shape_clip = true,
|
||||
shape = gears.shape.circle,
|
||||
bg = beautiful.bg_normal,
|
||||
shape_border_color = beautiful.border_color,
|
||||
widget = wibox.widget.background
|
||||
},
|
||||
spacing = 10,
|
||||
layout = wibox.layout.fixed.vertical
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
local parent = ... --DOC_HIDE
|
||||
local wibox = require("wibox") --DOC_HIDE
|
||||
|
||||
local text_widget = {
|
||||
text = "Hello world!",
|
||||
widget = wibox.widget.textbox
|
||||
}
|
||||
|
||||
parent : setup {
|
||||
{
|
||||
text_widget,
|
||||
fg = '#ff0000',
|
||||
widget = wibox.widget.background
|
||||
},
|
||||
{
|
||||
text_widget,
|
||||
fg = '#00ff00',
|
||||
widget = wibox.widget.background
|
||||
},
|
||||
{
|
||||
text_widget,
|
||||
fg = '#0000ff',
|
||||
widget = wibox.widget.background
|
||||
},
|
||||
spacing = 10,
|
||||
layout = wibox.layout.fixed.vertical
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
local parent = ... --DOC_HIDE
|
||||
local wibox = require("wibox") --DOC_HIDE
|
||||
local gears = {shape = require("gears.shape")} --DOC_HIDE
|
||||
local beautiful = require("beautiful") --DOC_HIDE
|
||||
|
||||
parent : setup {
|
||||
{
|
||||
-- Adding a shape without margin may result in cropped output
|
||||
{
|
||||
text = "Hello world!",
|
||||
widget = wibox.widget.textbox
|
||||
},
|
||||
shape = gears.shape.hexagon,
|
||||
bg = beautiful.bg_normal,
|
||||
shape_border_color = beautiful.border_color,
|
||||
shape_border_width = beautiful.border_width,
|
||||
widget = wibox.widget.background
|
||||
},
|
||||
{
|
||||
-- To solve this, use a margin
|
||||
{
|
||||
{
|
||||
text = "Hello world!",
|
||||
widget = wibox.widget.textbox
|
||||
},
|
||||
left = 10,
|
||||
right = 10,
|
||||
top = 3,
|
||||
bottom = 3,
|
||||
widget = wibox.layout.margin
|
||||
},
|
||||
shape = gears.shape.hexagon,
|
||||
bg = beautiful.bg_normal,
|
||||
shape_border_color = beautiful.border_color,
|
||||
shape_border_width = beautiful.border_width,
|
||||
widget = wibox.widget.background
|
||||
},
|
||||
spacing = 10,
|
||||
layout = wibox.layout.fixed.vertical
|
||||
}
|
Loading…
Reference in New Issue