From 9da40d010cbecc025c1d8f699d44dda9c4201a5d Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Tue, 29 Mar 2016 00:10:37 -0400 Subject: [PATCH 1/7] gears.surface: Add methods to convert widgets to surfaces It can be saved directly to a PNG or SVG file or used as a cairo surface or pattern. --- lib/gears/surface.lua | 48 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/lib/gears/surface.lua b/lib/gears/surface.lua index 678a0fb0..3cb18565 100644 --- a/lib/gears/surface.lua +++ b/lib/gears/surface.lua @@ -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 From e7652a053d25ca7b2164a8ff8edc3f8ce77234ba Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sat, 19 Mar 2016 04:59:06 -0400 Subject: [PATCH 2/7] tests: Add a new testing framework --- awesomeConfig.cmake | 5 + lib/wibox/widget/base.lua | 1 + tests/examples/CMakeLists.txt | 251 ++++++++++++++++++++++++++++++++++ 3 files changed, 257 insertions(+) create mode 100644 tests/examples/CMakeLists.txt diff --git a/awesomeConfig.cmake b/awesomeConfig.cmake index 33d09ff0..09fe93db 100644 --- a/awesomeConfig.cmake +++ b/awesomeConfig.cmake @@ -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 diff --git a/lib/wibox/widget/base.lua b/lib/wibox/widget/base.lua index 07222fac..900eb1f5 100644 --- a/lib/wibox/widget/base.lua +++ b/lib/wibox/widget/base.lua @@ -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 diff --git a/tests/examples/CMakeLists.txt b/tests/examples/CMakeLists.txt new file mode 100644 index 00000000..c3c54f76 --- /dev/null +++ b/tests/examples/CMakeLists.txt @@ -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 + string(REGEX REPLACE "\\\"" "'" ${vari} $ENV{${vari}}) +endforeach() + +message(STATUS "All test passed!") From c9f8690a60afa345840f5cb4fe5800469731aba2 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sat, 19 Mar 2016 04:57:37 -0400 Subject: [PATCH 3/7] tests: Add some shims to emulate CAPI without an X server --- tests/examples/shims/awesome.lua | 60 ++++++ tests/examples/shims/beautiful.lua | 24 +++ tests/examples/shims/beautiful/xresources.lua | 8 + tests/examples/shims/client.lua | 171 ++++++++++++++++++ tests/examples/shims/mouse.lua | 27 +++ tests/examples/shims/root.lua | 9 + tests/examples/shims/screen.lua | 101 +++++++++++ tests/examples/shims/tag.lua | 53 ++++++ 8 files changed, 453 insertions(+) create mode 100644 tests/examples/shims/awesome.lua create mode 100644 tests/examples/shims/beautiful.lua create mode 100644 tests/examples/shims/beautiful/xresources.lua create mode 100644 tests/examples/shims/client.lua create mode 100644 tests/examples/shims/mouse.lua create mode 100644 tests/examples/shims/root.lua create mode 100644 tests/examples/shims/screen.lua create mode 100644 tests/examples/shims/tag.lua diff --git a/tests/examples/shims/awesome.lua b/tests/examples/shims/awesome.lua new file mode 100644 index 00000000..30c2c39b --- /dev/null +++ b/tests/examples/shims/awesome.lua @@ -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 diff --git a/tests/examples/shims/beautiful.lua b/tests/examples/shims/beautiful.lua new file mode 100644 index 00000000..26349e97 --- /dev/null +++ b/tests/examples/shims/beautiful.lua @@ -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 diff --git a/tests/examples/shims/beautiful/xresources.lua b/tests/examples/shims/beautiful/xresources.lua new file mode 100644 index 00000000..70e130b7 --- /dev/null +++ b/tests/examples/shims/beautiful/xresources.lua @@ -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 diff --git a/tests/examples/shims/client.lua b/tests/examples/shims/client.lua new file mode 100644 index 00000000..f4f78d1d --- /dev/null +++ b/tests/examples/shims/client.lua @@ -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 diff --git a/tests/examples/shims/mouse.lua b/tests/examples/shims/mouse.lua new file mode 100644 index 00000000..01d7bb40 --- /dev/null +++ b/tests/examples/shims/mouse.lua @@ -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 diff --git a/tests/examples/shims/root.lua b/tests/examples/shims/root.lua new file mode 100644 index 00000000..dbe8754f --- /dev/null +++ b/tests/examples/shims/root.lua @@ -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 diff --git a/tests/examples/shims/screen.lua b/tests/examples/shims/screen.lua new file mode 100644 index 00000000..d7f25860 --- /dev/null +++ b/tests/examples/shims/screen.lua @@ -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 + diff --git a/tests/examples/shims/tag.lua b/tests/examples/shims/tag.lua new file mode 100644 index 00000000..fe10da12 --- /dev/null +++ b/tests/examples/shims/tag.lua @@ -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 + From 9f1565c7982977f48bffd30aaa25d9763dfaafa6 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Mon, 21 Mar 2016 04:45:40 -0400 Subject: [PATCH 4/7] tests: Add a template to test clients, screens, tags and mouse --- tests/examples/awful/template.lua | 154 ++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 tests/examples/awful/template.lua diff --git a/tests/examples/awful/template.lua b/tests/examples/awful/template.lua new file mode 100644 index 00000000..42c0a897 --- /dev/null +++ b/tests/examples/awful/template.lua @@ -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() From 25c76322a093af45b2a64beee86d4608404dcc12 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sat, 19 Mar 2016 04:58:02 -0400 Subject: [PATCH 5/7] tests: Add a template to take screenshots of widgets --- tests/examples/wibox/template.lua | 42 +++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 tests/examples/wibox/template.lua diff --git a/tests/examples/wibox/template.lua b/tests/examples/wibox/template.lua new file mode 100644 index 00000000..3e8d0e0c --- /dev/null +++ b/tests/examples/wibox/template.lua @@ -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() + From c62116f505a6f90942637c61aa329373de5a525e Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sat, 19 Mar 2016 04:58:43 -0400 Subject: [PATCH 6/7] tests: Add wibox.widget.background tests --- tests/examples/wibox/widget/background/bg.lua | 27 +++++++++++++ .../examples/wibox/widget/background/clip.lua | 32 +++++++++++++++ tests/examples/wibox/widget/background/fg.lua | 27 +++++++++++++ .../wibox/widget/background/shape.lua | 40 +++++++++++++++++++ 4 files changed, 126 insertions(+) create mode 100644 tests/examples/wibox/widget/background/bg.lua create mode 100644 tests/examples/wibox/widget/background/clip.lua create mode 100644 tests/examples/wibox/widget/background/fg.lua create mode 100644 tests/examples/wibox/widget/background/shape.lua diff --git a/tests/examples/wibox/widget/background/bg.lua b/tests/examples/wibox/widget/background/bg.lua new file mode 100644 index 00000000..f6562a6c --- /dev/null +++ b/tests/examples/wibox/widget/background/bg.lua @@ -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 +} diff --git a/tests/examples/wibox/widget/background/clip.lua b/tests/examples/wibox/widget/background/clip.lua new file mode 100644 index 00000000..15bdb15e --- /dev/null +++ b/tests/examples/wibox/widget/background/clip.lua @@ -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 +} diff --git a/tests/examples/wibox/widget/background/fg.lua b/tests/examples/wibox/widget/background/fg.lua new file mode 100644 index 00000000..98a889c5 --- /dev/null +++ b/tests/examples/wibox/widget/background/fg.lua @@ -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 +} diff --git a/tests/examples/wibox/widget/background/shape.lua b/tests/examples/wibox/widget/background/shape.lua new file mode 100644 index 00000000..e8c16361 --- /dev/null +++ b/tests/examples/wibox/widget/background/shape.lua @@ -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 +} From c64c223edec8b6f5eddbb4ebfde8f310fc11a9d2 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Tue, 29 Mar 2016 00:42:31 -0400 Subject: [PATCH 7/7] tests: Improve wibox.widget.background documentation --- lib/wibox/widget/background.lua | 39 +++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/lib/wibox/widget/background.lua b/lib/wibox/widget/background.lua index db5a42f7..de8e88c5 100644 --- a/lib/wibox/widget/background.lua +++ b/lib/wibox/widget/background.lua @@ -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