From 30a527fcfd1bdc5f4dce7c662d9647dacccf7b18 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Wed, 13 Apr 2016 05:20:16 -0400 Subject: [PATCH 01/26] travis: Install GTK So the tests can generate custom clients to suit their purpose. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d624ad47..d0ed2375 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,7 +29,7 @@ install: # Install build dependencies. # See also `apt-cache showsrc awesome | grep -E '^(Version|Build-Depends)'`. - - sudo apt-get install -y libcairo2-dev xmlto asciidoc libpango1.0-dev libxcb-xtest0-dev libxcb-icccm4-dev libxcb-randr0-dev libxcb-keysyms1-dev libxcb-xinerama0-dev libdbus-1-dev libxdg-basedir-dev libstartup-notification0-dev imagemagick libxcb1-dev libxcb-shape0-dev libxcb-util0-dev libx11-xcb-dev libxcb-cursor-dev libxcb-xkb-dev libxkbcommon-dev libxkbcommon-x11-dev + - sudo apt-get install -y libcairo2-dev gtk+3.0 xmlto asciidoc libpango1.0-dev libxcb-xtest0-dev libxcb-icccm4-dev libxcb-randr0-dev libxcb-keysyms1-dev libxcb-xinerama0-dev libdbus-1-dev libxdg-basedir-dev libstartup-notification0-dev imagemagick libxcb1-dev libxcb-shape0-dev libxcb-util0-dev libx11-xcb-dev libxcb-cursor-dev libxcb-xkb-dev libxkbcommon-dev libxkbcommon-x11-dev # Deps for functional tests. - sudo apt-get install -y dbus-x11 xterm xdotool xterm xvfb rxvt-unicode From fcd320c7f6145b3bf40a9fc3da2ae7f81cc666aa Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sun, 17 Apr 2016 05:30:34 -0400 Subject: [PATCH 02/26] Make awful.rules mandatory There was many unfixable race conditions that could only be solved by better integrating the request:: system and awful.rules. This has the side effect to make rules mandatory. --- awesomerc.lua | 1 - lib/awful/init.lua | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/awesomerc.lua b/awesomerc.lua index b686e684..5f7e87c6 100755 --- a/awesomerc.lua +++ b/awesomerc.lua @@ -1,7 +1,6 @@ -- Standard awesome library local gears = require("gears") local awful = require("awful") -awful.rules = require("awful.rules") require("awful.autofocus") -- Widget and layout library local wibox = require("wibox") diff --git a/lib/awful/init.lua b/lib/awful/init.lua index 9ed82b11..ac639e15 100644 --- a/lib/awful/init.lua +++ b/lib/awful/init.lua @@ -56,6 +56,7 @@ return tooltip = require("awful.tooltip"); ewmh = require("awful.ewmh"); titlebar = require("awful.titlebar"); + rules = require("awful.rules"); spawn = spawn; } From 2b5d918a8d8282b58ffa10e9f69ff6171b87af24 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Fri, 15 Apr 2016 05:09:05 -0400 Subject: [PATCH 03/26] awful.placement: Add a memento system. It is used by the ewmh methods. --- lib/awful/placement.lua | 77 ++++++++++++++++++++++++++++++++++------- 1 file changed, 65 insertions(+), 12 deletions(-) diff --git a/lib/awful/placement.lua b/lib/awful/placement.lua index 95866995..ef9818c2 100644 --- a/lib/awful/placement.lua +++ b/lib/awful/placement.lua @@ -39,6 +39,11 @@ -- -- **attach** (*boolean*): -- +-- **store_geometry** (*boolean*): +-- +-- Keep a single history of each type of placement. It can be restored using +-- `awful.placement.restore` by setting the right `context` argument. +-- -- When either the parent or the screen geometry change, call the placement -- function again. -- @@ -103,6 +108,26 @@ local align_map = { -- Store function -> keys local reverse_align_map = {} +--- Add a context to the arguments. +-- This function extend the argument table. The context is used by some +-- internal helper methods. If there already is a context, it has priority and +-- is kept. +local function add_context(args, context) + return setmetatable({context = (args or {}).context or context }, {__index=args}) +end + +local data = setmetatable({}, { __mode = 'k' }) + +--- Store a drawable geometry (per context) in a weak table. +-- @param d The drawin +-- @tparam string reqtype The context. +local function store_geometry(d, reqtype) + if not data[d] then data[d] = {} end + if not data[d][reqtype] then data[d][reqtype] = {} end + data[d][reqtype] = d:geometry() + data[d][reqtype].screen = d.screen +end + --- Get the area covered by a drawin. -- @param d The drawin -- @tparam[opt=nil] table new_geo A new geometry @@ -128,6 +153,12 @@ end -- @tparam[opt=false] boolean ignore_border_width Ignore the border -- @treturn table A table with *x*, *y*, *width* and *height*. local function geometry_common(obj, args, new_geo, ignore_border_width) + + -- Store the current geometry in a singleton-memento + if args.store_geometry and new_geo and args.context then + store_geometry(obj, args.context) + end + -- It's a mouse if obj.coords then local coords = new_geo and obj.coords(new_geo) or obj.coords() @@ -381,6 +412,7 @@ end -- @tparam[opt={}] table args The arguments -- @treturn string The corner name function placement.closest_corner(d, args) + args = add_context(args, "closest_corner") d = d or capi.client.focus local sgeo = get_parent_geometry(d, args) @@ -567,7 +599,7 @@ end -- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`) -- @tparam[opt={}] table args Other arguments function placement.align(d, args) - args = args or {} + args = add_context(args, "align") d = d or capi.client.focus if not d or not args.position then return end @@ -596,8 +628,9 @@ end -- Add the alias functions for k in pairs(align_map) do placement[k] = function(d, args) - local new_args = setmetatable({position = k}, {__index=args}) - placement.align(d, new_args) + args = add_context(args, k) + args.position = k + placement.align(d, args) end reverse_align_map[placement[k]] = k end @@ -636,7 +669,7 @@ end -- @tparam[opt=client.focus] drawable d A drawable (like `client` or `wibox`) -- @tparam[opt={}] table args The arguments function placement.stretch(d, args) - args = args or {} + args = add_context(args, "stretch") d = d or capi.client.focus if not d or not args.direction then return end @@ -644,8 +677,8 @@ function placement.stretch(d, args) -- In case there is multiple directions, call `stretch` for each of them if type(args.direction) == "table" then for _, dir in ipairs(args.direction) do - local new_args = setmetatable({direction = dir}, {__index=args}) - placement.stretch(dir, new_args) + args.direction = dir + placement.stretch(dir, args) end return end @@ -681,8 +714,9 @@ end -- Add the alias functions for _,v in ipairs {"left", "right", "up", "down"} do placement["stretch_"..v] = function(d, args) - local new_args = setmetatable({direction = v}, {__index=args}) - placement.stretch(d, new_args) + args = add_context(args, "stretch_"..v) + args.direction = v + placement.stretch(d, args) end end @@ -704,7 +738,7 @@ end -- @tparam[opt=client.focus] drawable d A drawable (like `client` or `wibox`) -- @tparam[opt={}] table args The arguments function placement.maximize(d, args) - args = args or {} + args = add_context(args, "maximize") d = d or capi.client.focus if not d then return end @@ -731,9 +765,9 @@ end -- Add the alias functions for _, v in ipairs {"vertically", "horizontally"} do placement["maximize_"..v] = function(d2, args) - args = args or {} - local new_args = setmetatable({axis = v}, {__index=args}) - placement.maximize(d2, new_args) + args = add_context(args, "maximize_"..v) + args.axis = v + placement.maximize(d2, args) end end @@ -741,6 +775,25 @@ end ---@DOC_awful_placement_maximize_horizontally_EXAMPLE@ +--- Restore the geometry. +-- @tparam[opt=client.focus] drawable d A drawable (like `client` or `wibox`) +-- @tparam[opt={}] table args The arguments +-- @treturn boolean If the geometry was restored +function placement.restore(d, args) + if not args or not args.context then return false end + d = d or capi.client.focus + + if not data[d] then return false end + + local memento = data[d][args.context] + + if not memento then return false end + + memento.screen = nil --TODO use it + d:geometry(memento) + return true +end + return placement -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 From bfc8f35fb93d34f3c1613b3501db8781cc9cdfe2 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sat, 16 Apr 2016 05:49:17 -0400 Subject: [PATCH 04/26] placement: Support composition. Multiple placement function can now be daisy chained like in gears.matrix. This is useful for building rules. --- lib/awful/placement.lua | 54 +++++++++++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 7 deletions(-) diff --git a/lib/awful/placement.lua b/lib/awful/placement.lua index ef9818c2..139d2574 100644 --- a/lib/awful/placement.lua +++ b/lib/awful/placement.lua @@ -10,7 +10,12 @@ -- * Re-use the same functions for the `mouse`, `client`s, `screen`s and `wibox`es -- -- --- +-- +-- It is possible to compose placement function using the `+` or `*` operator: +-- +-- local f = (awful.placement.right + awful.placement.left) +-- f(client.focus) +-- -- ### Common arguments -- -- **honor_workarea** (*boolean*): @@ -78,7 +83,37 @@ local function get_screen(s) return s and capi.screen[s] end -local placement = {} +local wrap_client = nil + +local function compose(w1, w2) + return wrap_client(function(...) + w1(...) + w2(...) + return --It make no sense to keep a return value + end) +end + +wrap_client = function(f) + return setmetatable({is_placement=true}, { + __call = function(_,...) return f(...) end, + __add = compose, -- Composition is usually defined as + + __mul = compose -- Make sense if you think of the functions as matrices + }) +end + +local placement_private = {} + +-- The module is a proxy in front of the "real" functions. +-- This allow syntax like: +-- +-- (awful.placement.no_overlap + awful.placement.no_offscreen)(c) +-- +local placement = setmetatable({}, { + __index = placement_private, + __newindex = function(_, k, f) + placement_private[k] = wrap_client(f) + end +}) -- 3x3 matrix of the valid sides and corners local corners3x3 = {{"top_left" , "top" , "top_right" }, @@ -441,7 +476,7 @@ function placement.closest_corner(d, args) -- Transpose the corner back to the original size local new_args = setmetatable({position = corner}, {__index=args}) - placement.align(d, new_args) + placement_private.align(d, new_args) return corner end @@ -452,6 +487,11 @@ end -- @tparam[opt=client's screen] integer screen The screen. -- @treturn table The new client geometry. function placement.no_offscreen(c, screen) + --HACK necessary for composition to work. The API will be changed soon + if type(screen) == "table" then + screen = nil + end + c = c or capi.client.focus local geometry = area_common(c) screen = get_screen(screen or c.screen or a_screen.getbycoord(geometry.x, geometry.y)) @@ -630,7 +670,7 @@ for k in pairs(align_map) do placement[k] = function(d, args) args = add_context(args, k) args.position = k - placement.align(d, args) + placement_private.align(d, args) end reverse_align_map[placement[k]] = k end @@ -678,7 +718,7 @@ function placement.stretch(d, args) if type(args.direction) == "table" then for _, dir in ipairs(args.direction) do args.direction = dir - placement.stretch(dir, args) + placement_private.stretch(dir, args) end return end @@ -716,7 +756,7 @@ for _,v in ipairs {"left", "right", "up", "down"} do placement["stretch_"..v] = function(d, args) args = add_context(args, "stretch_"..v) args.direction = v - placement.stretch(d, args) + placement_private.stretch(d, args) end end @@ -767,7 +807,7 @@ for _, v in ipairs {"vertically", "horizontally"} do placement["maximize_"..v] = function(d2, args) args = add_context(args, "maximize_"..v) args.axis = v - placement.maximize(d2, args) + placement_private.maximize(d2, args) end end From 798729ff11124bccde450593789f4f82e14ec8bf Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sat, 16 Apr 2016 05:56:41 -0400 Subject: [PATCH 05/26] tests: Test placement composition --- tests/examples/awful/placement/compose.lua | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 tests/examples/awful/placement/compose.lua diff --git a/tests/examples/awful/placement/compose.lua b/tests/examples/awful/placement/compose.lua new file mode 100644 index 00000000..28098677 --- /dev/null +++ b/tests/examples/awful/placement/compose.lua @@ -0,0 +1,9 @@ +screen[1]._resize {width = 128, height = 96} --DOC_HIDE +local awful = {placement = require("awful.placement")} --DOC_HIDE +local c = client.gen_fake {x = 45, y = 35, width=40, height=30} --DOC_HIDE + +local f = (awful.placement.right + awful.placement.left) +f(client.focus) + +assert(c.x == 0 and c.y==screen[1].geometry.height/2-30/2-c.border_width--DOC_HIDE + and c.width==40 and c.height==30)--DOC_HIDE From fe8beaeaacf1011e0de82da95e6eb65c53f0e2e9 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Mon, 18 Apr 2016 23:56:23 -0400 Subject: [PATCH 06/26] placement: Fix incorect use of the border_width The code and tests assumed the border was equaly applied around the geometry while the {x,y} pair of the geometry include the border. --- lib/awful/placement.lua | 25 +++++++++++-------- tests/examples/awful/placement/bottom.lua | 4 +-- .../examples/awful/placement/bottom_left.lua | 4 +-- .../examples/awful/placement/bottom_right.lua | 4 +-- .../awful/placement/center_horizontal.lua | 2 +- tests/examples/awful/placement/centered.lua | 5 ++-- .../awful/placement/closest_mouse.lua | 17 +++++++------ tests/examples/awful/placement/left.lua | 2 +- tests/examples/awful/placement/right.lua | 6 ++--- .../examples/awful/placement/stretch_down.lua | 2 +- .../examples/awful/placement/stretch_left.lua | 4 +-- .../awful/placement/stretch_right.lua | 2 +- tests/examples/awful/placement/stretch_up.lua | 6 ++--- tests/examples/awful/placement/top.lua | 6 +++-- tests/examples/awful/placement/top_left.lua | 2 +- tests/examples/awful/placement/top_right.lua | 2 +- 16 files changed, 50 insertions(+), 43 deletions(-) diff --git a/lib/awful/placement.lua b/lib/awful/placement.lua index 139d2574..b98079c9 100644 --- a/lib/awful/placement.lua +++ b/lib/awful/placement.lua @@ -172,8 +172,8 @@ local function area_common(d, new_geo, ignore_border_width) -- The C side expect no arguments, nil isn't valid local geometry = new_geo and d:geometry(new_geo) or d:geometry() local border = ignore_border_width and 0 or d.border_width or 0 - geometry.x = geometry.x - border - geometry.y = geometry.y - border + geometry.x = geometry.x + geometry.y = geometry.y geometry.width = geometry.width + 2 * border geometry.height = geometry.height + 2 * border return geometry @@ -511,7 +511,10 @@ function placement.no_offscreen(c, screen) geometry.y = screen_geometry.y end - return c:geometry({ x = geometry.x, y = geometry.y }) + return c:geometry { + x = geometry.x, + y = geometry.y + } end --- Place the client where there's place available with minimum overlap. @@ -656,8 +659,8 @@ function placement.align(d, args) ) geometry_common(d, args, { - x = (pos.x and math.ceil(sgeo.x + pos.x) or dgeo.x) + bw , - y = (pos.y and math.ceil(sgeo.y + pos.y) or dgeo.y) + bw , + x = (pos.x and math.ceil(sgeo.x + pos.x) or dgeo.x) , + y = (pos.y and math.ceil(sgeo.y + pos.y) or dgeo.y) , width = math.ceil(dgeo.width ) - 2*bw, height = math.ceil(dgeo.height ) - 2*bw, }) @@ -729,15 +732,15 @@ function placement.stretch(d, args) local bw = d.border_width or 0 if args.direction == "left" then - ngeo.x = sgeo.x + bw + ngeo.x = sgeo.x ngeo.width = dgeo.width + (dgeo.x - ngeo.x) elseif args.direction == "right" then - ngeo.width = sgeo.width - ngeo.x - bw + ngeo.width = sgeo.width - ngeo.x - 2*bw elseif args.direction == "up" then - ngeo.y = sgeo.y + bw + ngeo.y = sgeo.y ngeo.height = dgeo.height + (dgeo.y - ngeo.y) elseif args.direction == "down" then - ngeo.height = sgeo.height - dgeo.y - bw + ngeo.height = sgeo.height - dgeo.y - 2*bw else assert(false) end @@ -788,12 +791,12 @@ function placement.maximize(d, args) local bw = d.border_width or 0 if (not args.axis) or args.axis :match "vertical" then - ngeo.y = sgeo.y + bw + ngeo.y = sgeo.y ngeo.height = sgeo.height - 2*bw end if (not args.axis) or args.axis :match "horizontal" then - ngeo.x = sgeo.x + bw + ngeo.x = sgeo.x ngeo.width = sgeo.width - 2*bw end diff --git a/tests/examples/awful/placement/bottom.lua b/tests/examples/awful/placement/bottom.lua index 814d3546..10dff9ab 100644 --- a/tests/examples/awful/placement/bottom.lua +++ b/tests/examples/awful/placement/bottom.lua @@ -10,6 +10,6 @@ local c = client.gen_fake {x = 45, y = 35, width=40, height=30} --DOC_HIDE awful.placement.bottom(client.focus) -assert(c.x == screen[1].geometry.width/2-40/2--DOC_HIDE - and c.y==screen[1].geometry.height-30-c.border_width--DOC_HIDE +assert(c.x == screen[1].geometry.width/2-40/2-c.border_width--DOC_HIDE + and c.y==screen[1].geometry.height-30-2*c.border_width--DOC_HIDE and c.width==40 and c.height==30)--DOC_HIDE diff --git a/tests/examples/awful/placement/bottom_left.lua b/tests/examples/awful/placement/bottom_left.lua index f1da6045..a9ebc062 100644 --- a/tests/examples/awful/placement/bottom_left.lua +++ b/tests/examples/awful/placement/bottom_left.lua @@ -11,8 +11,8 @@ local c = client.gen_fake {x = 45, y = 35, width=40, height=30} --DOC_HIDE awful.placement.bottom_left(client.focus) assert( --DOC_HIDE - c.x == c.border_width --DOC_HIDE - and c.y == screen[1].geometry.height-30-c.border_width --DOC_HIDE + c.x == 0 --DOC_HIDE + and c.y+2*c.border_width == screen[1].geometry.height-30 --DOC_HIDE and c.width == 40--DOC_HIDE and c.height == 30--DOC_HIDE ) --DOC_HIDE diff --git a/tests/examples/awful/placement/bottom_right.lua b/tests/examples/awful/placement/bottom_right.lua index 1c6d7b20..0ad1072e 100644 --- a/tests/examples/awful/placement/bottom_right.lua +++ b/tests/examples/awful/placement/bottom_right.lua @@ -10,6 +10,6 @@ local c = client.gen_fake {x = 45, y = 35, width=40, height=30} --DOC_HIDE awful.placement.bottom_right(client.focus) -assert(c.x == screen[1].geometry.width-40-c.border_width) --DOC_HIDE -assert(c.y==screen[1].geometry.height-30-c.border_width) --DOC_HIDE +assert(c.x == screen[1].geometry.width-40-2*c.border_width) --DOC_HIDE +assert(c.y==screen[1].geometry.height-30-2*c.border_width) --DOC_HIDE assert(c.width==40 and c.height==30)--DOC_HIDE diff --git a/tests/examples/awful/placement/center_horizontal.lua b/tests/examples/awful/placement/center_horizontal.lua index c8e2874c..b9d1d6bb 100644 --- a/tests/examples/awful/placement/center_horizontal.lua +++ b/tests/examples/awful/placement/center_horizontal.lua @@ -9,6 +9,6 @@ local c = client.gen_fake {x = 45, y = 35, width=40, height=30} --DOC_HIDE awful.placement.center_horizontal(client.focus) -assert(c.x == screen[1].geometry.width/2-40/2)--DOC_HIDE +assert(c.x == screen[1].geometry.width/2-40/2-c.border_width)--DOC_HIDE assert(c.y==35)--DOC_HIDE assert(c.width==40 and c.height==30)--DOC_HIDE diff --git a/tests/examples/awful/placement/centered.lua b/tests/examples/awful/placement/centered.lua index 5b97a16f..af1a1c84 100644 --- a/tests/examples/awful/placement/centered.lua +++ b/tests/examples/awful/placement/centered.lua @@ -10,5 +10,6 @@ local c = client.gen_fake {x = 45, y = 35, width=40, height=30} --DOC_HIDE awful.placement.centered(client.focus) -assert(c.x == screen[1].geometry.width/2-40/2 and c.y==screen[1].geometry.height/2-30/2--DOC_HIDE - and c.width==40 and c.height==30)--DOC_HIDE +assert(c.x == screen[1].geometry.width/2-40/2-c.border_width) --DOC_HIDE +assert(c.y==screen[1].geometry.height/2-30/2-c.border_width) --DOC_HIDE +assert(c.width==40 and c.height==30)--DOC_HIDE diff --git a/tests/examples/awful/placement/closest_mouse.lua b/tests/examples/awful/placement/closest_mouse.lua index 98e58abd..90642641 100644 --- a/tests/examples/awful/placement/closest_mouse.lua +++ b/tests/examples/awful/placement/closest_mouse.lua @@ -8,49 +8,50 @@ mouse.coords {x=100,y=100} --DOC_HIDE -- Move the mouse to the closest corner of the focused client awful.placement.closest_corner(mouse, {include_sides=true, parent=c}) mouse.push_history() --DOC_HIDE -assert(mouse.coords().x == c.x-bw and mouse.coords().y == (c.y) + (c.height)/2) --DOC_HIDE +assert(mouse.coords().x == c.x) --DOC_HIDE +assert(mouse.coords().y == (c.y) + (c.height)/2+bw) --DOC_HIDE -- Top left --DOC_HIDE mouse.coords {x=60,y=40} --DOC_HIDE awful.placement.closest_corner(mouse, {include_sides=true, parent=c}) --DOC_HIDE mouse.push_history() --DOC_HIDE -assert(mouse.coords().x == c.x-bw and mouse.coords().y == c.y-bw) --DOC_HIDE +assert(mouse.coords().x == c.x and mouse.coords().y == c.y) --DOC_HIDE -- Top right --DOC_HIDE mouse.coords {x=230,y=50} --DOC_HIDE awful.placement.closest_corner(mouse, {include_sides=true, parent=c}) --DOC_HIDE mouse.push_history() --DOC_HIDE -assert(mouse.coords().x == c.x+c.width+bw and mouse.coords().y == c.y-bw) --DOC_HIDE +assert(mouse.coords().x == c.x+c.width+2*bw and mouse.coords().y == c.y) --DOC_HIDE -- Right --DOC_HIDE mouse.coords {x=240,y=140} --DOC_HIDE awful.placement.closest_corner(mouse, {include_sides=true, parent=c}) --DOC_HIDE mouse.push_history() --DOC_HIDE -assert(mouse.coords().x == c.x+c.width+bw and mouse.coords().y == c.y+c.height/2) --DOC_HIDE +assert(mouse.coords().x == c.x+c.width+2*bw and mouse.coords().y == c.y+c.height/2+bw) --DOC_HIDE -- Bottom right --DOC_HIDE mouse.coords {x=210,y=190} --DOC_HIDE awful.placement.closest_corner(mouse, {include_sides=true, parent=c}) --DOC_HIDE mouse.push_history() --DOC_HIDE -assert(mouse.coords().x == c.x+c.width+bw and mouse.coords().y == c.y+c.height+bw) --DOC_HIDE +assert(mouse.coords().x == c.x+c.width+2*bw and mouse.coords().y == c.y+c.height+2*bw) --DOC_HIDE -- Bottom --DOC_HIDE mouse.coords {x=130,y=190} --DOC_HIDE awful.placement.closest_corner(mouse, {include_sides=true, parent=c}) --DOC_HIDE mouse.push_history() --DOC_HIDE -assert(mouse.coords().x == c.x+c.width/2 and mouse.coords().y == c.y + c.height + bw) --DOC_HIDE +assert(mouse.coords().x == c.x+c.width/2+bw and mouse.coords().y == c.y + c.height + 2*bw) --DOC_HIDE -- Top --DOC_HIDE mouse.coords {x=130,y=30} --DOC_HIDE awful.placement.closest_corner(mouse, {include_sides=true, parent=c}) --DOC_HIDE mouse.push_history() --DOC_HIDE -assert(mouse.coords().x == c.x + c.width/2 and mouse.coords().y == c.y-bw) --DOC_HIDE +assert(mouse.coords().x == c.x + c.width/2+bw and mouse.coords().y == c.y) --DOC_HIDE -- Bottom left + outside of "c" --DOC_HIDE mouse.coords {x=0,y=230} --DOC_HIDE awful.placement.closest_corner(mouse, {include_sides=true, parent=c}) --DOC_HIDE mouse.push_history() --DOC_HIDE -assert(mouse.coords().x == c.x-bw and mouse.coords().y == c.y+c.height+bw) --DOC_HIDE +assert(mouse.coords().x == c.x and mouse.coords().y == c.y+c.height+2*bw) --DOC_HIDE -- It is possible to emulate the mouse API to get the closest corner of -- random area diff --git a/tests/examples/awful/placement/left.lua b/tests/examples/awful/placement/left.lua index f7e8c8cf..49cf93ac 100644 --- a/tests/examples/awful/placement/left.lua +++ b/tests/examples/awful/placement/left.lua @@ -10,5 +10,5 @@ local c = client.gen_fake {x = 45, y = 35, width=40, height=30} --DOC_HIDE awful.placement.left(client.focus) -assert(c.x == c.border_width and c.y==screen[1].geometry.height/2-30/2--DOC_HIDE +assert(c.x == 0 and c.y==screen[1].geometry.height/2-30/2-c.border_width--DOC_HIDE and c.width==40 and c.height==30)--DOC_HIDE diff --git a/tests/examples/awful/placement/right.lua b/tests/examples/awful/placement/right.lua index 1ba092c9..d2450116 100644 --- a/tests/examples/awful/placement/right.lua +++ b/tests/examples/awful/placement/right.lua @@ -10,6 +10,6 @@ local c = client.gen_fake {x = 45, y = 35, width=40, height=30} --DOC_HIDE awful.placement.right(client.focus) -assert(c.x == screen[1].geometry.width-40-c.border_width --DOC_HIDE - and c.y==screen[1].geometry.height/2-30/2--DOC_HIDE - and c.width==40 and c.height==30)--DOC_HIDE +assert(c.x == screen[1].geometry.width-40-2*c.border_width) --DOC_HIDE +assert( c.y==screen[1].geometry.height/2-30/2-c.border_width)--DOC_HIDE +assert( c.width==40 and c.height==30)--DOC_HIDE diff --git a/tests/examples/awful/placement/stretch_down.lua b/tests/examples/awful/placement/stretch_down.lua index 12a074b1..258647f8 100644 --- a/tests/examples/awful/placement/stretch_down.lua +++ b/tests/examples/awful/placement/stretch_down.lua @@ -13,5 +13,5 @@ placement.stretch_down(client.focus) assert(c.x==45) --DOC_HIDE assert(c.y==35) --DOC_HIDE assert(c.width == 40) --DOC_HIDE -assert(c.y+c.height == --DOC_HIDE +assert(c.y+c.height+2*c.border_width == --DOC_HIDE screen[1].geometry.y + screen[1].geometry.height) --DOC_HIDE diff --git a/tests/examples/awful/placement/stretch_left.lua b/tests/examples/awful/placement/stretch_left.lua index 7db11c77..51364697 100644 --- a/tests/examples/awful/placement/stretch_left.lua +++ b/tests/examples/awful/placement/stretch_left.lua @@ -10,5 +10,5 @@ local placement = require("awful.placement") --DOC_HIDE local c = client.gen_fake {x = 45, y = 35, width=40, height=30} --DOC_HIDE placement.stretch_left(client.focus) -assert(c.x == c.border_width and c.y == 35 and c.height == 30 --DOC_HIDE - and c.width == 45+40) --DOC_HIDE +assert(c.x == 0 and c.y == 35 and c.height == 30) --DOC_HIDE +print(c.width-2*c.border_width == 45+40) --DOC_HIDE diff --git a/tests/examples/awful/placement/stretch_right.lua b/tests/examples/awful/placement/stretch_right.lua index 79b2687d..9965d1a6 100644 --- a/tests/examples/awful/placement/stretch_right.lua +++ b/tests/examples/awful/placement/stretch_right.lua @@ -11,4 +11,4 @@ local c = client.gen_fake {x = 45, y = 35, width=40, height=30} --DOC_HIDE placement.stretch_right(client.focus) local right = screen[1].geometry.x + screen[1].geometry.width --DOC_HIDE -assert(c.height == 30 and c.x == 45 and c.x+c.width+c.border_width == right) --DOC_HIDE +assert(c.height == 30 and c.x == 45 and c.x+c.width+2*c.border_width == right) --DOC_HIDE diff --git a/tests/examples/awful/placement/stretch_up.lua b/tests/examples/awful/placement/stretch_up.lua index 87bea473..817a11d5 100644 --- a/tests/examples/awful/placement/stretch_up.lua +++ b/tests/examples/awful/placement/stretch_up.lua @@ -10,8 +10,8 @@ local placement = require("awful.placement") --DOC_HIDE local c = client.gen_fake {x = 45, y = 35, width=40, height=30} --DOC_HIDE placement.stretch_up(client.focus) -assert(c.y==c.border_width) --DOC_HIDE +assert(c.y==0) --DOC_HIDE assert(c.x==45) --DOC_HIDE assert(c.width == 40) --DOC_HIDE -print(c.height) -assert(c.height == 35+30) --DOC_HIDE +print(c.height-2*c.border_width,35+30) +assert(c.height-2*c.border_width == 35+30) --DOC_HIDE diff --git a/tests/examples/awful/placement/top.lua b/tests/examples/awful/placement/top.lua index ab1bdc0b..f84ea306 100644 --- a/tests/examples/awful/placement/top.lua +++ b/tests/examples/awful/placement/top.lua @@ -10,5 +10,7 @@ local c = client.gen_fake {x = 45, y = 35, width=40, height=30} --DOC_HIDE awful.placement.top(client.focus) -assert(c.x == screen[1].geometry.width/2-40/2 and c.y==c.border_width--DOC_HIDE - and c.width==40 and c.height==30)--DOC_HIDE +assert(c.x == screen[1].geometry.width/2-40/2-c.border_width) +assert(c.y==0) --DOC_HIDE +assert( c.width==40) --DOC_HIDE +assert(c.height==30)--DOC_HIDE diff --git a/tests/examples/awful/placement/top_left.lua b/tests/examples/awful/placement/top_left.lua index 208e86eb..a2bb3b58 100644 --- a/tests/examples/awful/placement/top_left.lua +++ b/tests/examples/awful/placement/top_left.lua @@ -10,4 +10,4 @@ local c = client.gen_fake {x = 45, y = 35, width=40, height=30} --DOC_HIDE awful.placement.top_left(client.focus) -assert(c.x == c.border_width and c.y==c.border_width and c.width==40 and c.height==30)--DOC_HIDE +assert(c.x == 0 and c.y==0 and c.width==40 and c.height==30)--DOC_HIDE diff --git a/tests/examples/awful/placement/top_right.lua b/tests/examples/awful/placement/top_right.lua index 9655032f..a94890c9 100644 --- a/tests/examples/awful/placement/top_right.lua +++ b/tests/examples/awful/placement/top_right.lua @@ -10,5 +10,5 @@ local c = client.gen_fake {x = 45, y = 35, width=40, height=30} --DOC_HIDE awful.placement.top_right(client.focus) -assert(c.x == screen[1].geometry.width-40-c.border_width and c.y==c.border_width --DOC_HIDE +assert(c.x == screen[1].geometry.width-40-2*c.border_width and c.y==0 --DOC_HIDE and c.width==40 and c.height==30)--DOC_HIDE From 2a8cc08ca11779e4bea35c39bf3b66cd3223cfda Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sat, 16 Apr 2016 06:23:09 -0400 Subject: [PATCH 07/26] awful.rules: Make adding new properties easier. This commit also add a 3 step process to apply rules. Testing showed that many rules are currently broken because of execution races. Create a new dynamic tag for the client. --- lib/awful/rules.lua | 103 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 83 insertions(+), 20 deletions(-) diff --git a/lib/awful/rules.lua b/lib/awful/rules.lua index d470d8c7..d3cf3a25 100644 --- a/lib/awful/rules.lua +++ b/lib/awful/rules.lua @@ -188,6 +188,7 @@ end --- Apply awful.rules.rules to a client. -- @client c The client. function rules.apply(c) + local props = {} local callbacks = {} @@ -205,36 +206,98 @@ function rules.apply(c) rules.execute(c, props, callbacks) end +local function add_to_tag(c, t) + if not t then return end + + local tags = c:tags() + table.insert(tags, t) + c:tags(tags) +end + +--- Extra rules properties. +-- +-- These properties are used in the rules only and are not sent to the client +-- afterward. +-- +-- To add a new properties, just do: +-- +-- function awful.rules.extra_properties.my_new_property(c, value) +-- -- do something +-- end +-- +-- @tfield table awful.rules.extra_properties +rules.extra_properties = {} + +--- Extra high priority properties. +-- +-- Some properties, such as anything related to tags, geometry or focus, will +-- cause a race condition if set in the main property section. This is why +-- they have a section for them. +-- +-- To add a new properties, just do: +-- +-- function awful.rules.high_priority_properties.my_new_property(c, value) +-- -- do something +-- end +-- +-- @tfield table awful.rules.high_priority_properties +rules.high_priority_properties = {} + +rules.delayed_properties = { + focus = true, + switchtotag = true, +} --- Apply properties and callbacks to a client. -- @client c The client. -- @tab props Properties to apply. -- @tab[opt] callbacks Callbacks to apply. function rules.execute(c, props, callbacks) - local handle_later = { focus = true, switchtotag = true } - local switchtotag = props.switchtotag + + -- Some properties need to be handled first. For example, many properties + -- depend that the client is tagged, this isn't yet the case. + for prop, handler in pairs(rules.high_priority_properties) do + local value = props[prop] + + if value ~= nil then + if type(value) == "function" then + value = value(c, props) + end + + handler(c, props[prop]) + end + + end for property, value in pairs(props) do + + if property ~= "focus" and type(value) == "function" then value = value(c) end - if property == "screen" then - -- Support specifying screens by name ("VGA1") - c.screen = screen[value] - elseif property == "tag" then - local t = value - if type(t) == "string" then - t = atag.find_by_name(props.screen, t) - end - c.screen = t.screen - c:tags({ t }) - elseif property == "height" or property == "width" or - property == "x" or property == "y" then - local geo = c:geometry(); - geo[property] = value - c:geometry(geo); - elseif not handle_later[property] then - if type(c[property]) == "function" then + + -- Some properties are handled elsewhere + if not rules.high_priority_properties[property] and not rules.delayed_properties[property] then + if property == "tag" then + local t = value + if type(t) == "string" then + t = atag.find_by_name(props.screen, t) + elseif type(t) == "function" then + t = value(c, props) + end + + if t then + c.screen = t.screen + c:tags{ t } + end + elseif property == "height" or property == "width" or + property == "x" or property == "y" then + local geo = c:geometry(); + geo[property] = value + c:geometry(geo); + elseif rules.extra_properties[property] then + rules.extra_properties[property](c, value) + elseif type(c[property]) == "function" then c[property](c, value) else c[property] = value @@ -243,7 +306,7 @@ function rules.execute(c, props, callbacks) end -- Only do this after the tag has been (possibly) set - if switchtotag and c.first_tag then + if props.switchtotag and c.first_tag then c.first_tag:view_only() end From a2a5448442d2d862c9b99e7241e0a2309d523449 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Wed, 20 Apr 2016 00:02:38 -0400 Subject: [PATCH 08/26] awful.rules: Remove dead code Also change the focus callback signature to match the others --- lib/awful/rules.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/awful/rules.lua b/lib/awful/rules.lua index d3cf3a25..55e129a9 100644 --- a/lib/awful/rules.lua +++ b/lib/awful/rules.lua @@ -273,7 +273,7 @@ function rules.execute(c, props, callbacks) if property ~= "focus" and type(value) == "function" then - value = value(c) + value = value(c, props) end -- Some properties are handled elsewhere From ddf14a3ffc7637c1298c7c9c9e820d3caaba84dd Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Wed, 20 Apr 2016 00:08:28 -0400 Subject: [PATCH 09/26] awful.rules: Refactor the code to avoid many race conditions. Testing demonstrated that many rule properties were broken when used together. This commit try to address this by forcing an execution order that doesn't trigger the problems. It is still possible to write broken rules, but it should not happen by accident anymore. Users should not try to assign the client a tag on screen 2 and also use screen=screen[1]. --- lib/awful/rules.lua | 125 +++++++++++++++++++++++++++++++++----------- 1 file changed, 94 insertions(+), 31 deletions(-) diff --git a/lib/awful/rules.lua b/lib/awful/rules.lua index 55e129a9..6b059700 100644 --- a/lib/awful/rules.lua +++ b/lib/awful/rules.lua @@ -9,6 +9,7 @@ -- Grab environment we need local client = client +local awesome = awesome local screen = screen local table = table local type = type @@ -221,10 +222,15 @@ end -- -- To add a new properties, just do: -- --- function awful.rules.extra_properties.my_new_property(c, value) +-- function awful.rules.extra_properties.my_new_property(c, value, props) -- -- do something -- end -- +-- By default, the table has the following functions: +-- +-- * geometry +-- * switchtotag +-- -- @tfield table awful.rules.extra_properties rules.extra_properties = {} @@ -236,24 +242,78 @@ rules.extra_properties = {} -- -- To add a new properties, just do: -- --- function awful.rules.high_priority_properties.my_new_property(c, value) +-- function awful.rules.high_priority_properties.my_new_property(c, value, props) -- -- do something -- end -- +-- By default, the table has the following functions: +-- +-- * tag +-- -- @tfield table awful.rules.high_priority_properties rules.high_priority_properties = {} rules.delayed_properties = { - focus = true, - switchtotag = true, + focus = function() end, } +function rules.high_priority_properties.tag(c, value) + if value then + if type(value) == "string" then + value = atag.find_by_name(nil, value) + end + + c:tags{ value } + end +end + +function rules.delayed_properties.switchtotag(c, value) + if not value then return end + + local selected_tags = {} + + for _,v in ipairs(c.screen.selected_tags) do + selected_tags[v] = true + end + + local tags = c:tags() + + for _, t in ipairs(tags) do + t.selected = true + selected_tags[t] = nil + end + + for t in pairs(selected_tags) do + t.selected = false + end +end + +function rules.extra_properties.geometry(c, _, props) + local cur_geo = c:geometry() + + local new_geo = type(props.geometry) == "function" + and props.geometry(c, props) or props.geometry or {} + + for _, v in ipairs {"x", "y", "width", "height"} do + new_geo[v] = type(props[v]) == "function" and props[v](c, props) + or props[v] or new_geo[v] or cur_geo[v] + end + + c:geometry(new_geo) --TODO use request::geometry +end + --- Apply properties and callbacks to a client. -- @client c The client. -- @tab props Properties to apply. -- @tab[opt] callbacks Callbacks to apply. function rules.execute(c, props, callbacks) + -- Before requesting a tag, make sure the screen is right + if props.screen then + c.screen = type(props.screen) == "function" and screen[props.screen(c,props)] + or screen[props.screen] + end + -- Some properties need to be handled first. For example, many properties -- depend that the client is tagged, this isn't yet the case. for prop, handler in pairs(rules.high_priority_properties) do @@ -264,38 +324,33 @@ function rules.execute(c, props, callbacks) value = value(c, props) end - handler(c, props[prop]) + handler(c, value) end end + -- By default, rc.lua use no_overlap+no_offscreen placement. This has to + -- be executed before x/y/width/height/geometry as it would otherwise + -- always override the user specified position with the default rule. + if props.placement then + -- It may be a function, so this one doesn't execute it like others + rules.extra_properties.placement(c, props.placement, props) + end + + -- Now that the tags and screen are set, handle the geometry + if props.height or props.width or props.x or props.y or props.geometry then + rules.extra_properties.geometry(c, nil, props) + end + + -- As most race conditions should now have been avoided, apply the remaining + -- properties. for property, value in pairs(props) do - - if property ~= "focus" and type(value) == "function" then value = value(c, props) end - -- Some properties are handled elsewhere if not rules.high_priority_properties[property] and not rules.delayed_properties[property] then - if property == "tag" then - local t = value - if type(t) == "string" then - t = atag.find_by_name(props.screen, t) - elseif type(t) == "function" then - t = value(c, props) - end - - if t then - c.screen = t.screen - c:tags{ t } - end - elseif property == "height" or property == "width" or - property == "x" or property == "y" then - local geo = c:geometry(); - geo[property] = value - c:geometry(geo); - elseif rules.extra_properties[property] then + if rules.extra_properties[property] then rules.extra_properties[property](c, value) elseif type(c[property]) == "function" then c[property](c, value) @@ -305,11 +360,6 @@ function rules.execute(c, props, callbacks) end end - -- Only do this after the tag has been (possibly) set - if props.switchtotag and c.first_tag then - c.first_tag:view_only() - end - -- Apply all callbacks. if callbacks then for _, callback in pairs(callbacks) do @@ -317,6 +367,19 @@ function rules.execute(c, props, callbacks) end end + -- Apply the delayed properties + for prop, handler in pairs(rules.delayed_properties) do + local value = props[prop] + + if value ~= nil then + if type(value) == "function" then + value = value(c, props) + end + + handler(c, value, props) + end + end + -- Do this at last so we do not erase things done by the focus signal. if props.focus and (type(props.focus) ~= "function" or props.focus(c)) then c:emit_signal('request::activate', "rules", {raise=true}) From 090f2b83ac6c4ef6ce54df93f24d50100c2054f8 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Thu, 14 Apr 2016 23:51:17 -0400 Subject: [PATCH 10/26] awful.rules: Avoid trying to set some properties This avoid unwanted signals or properties being set twice. --- lib/awful/rules.lua | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/lib/awful/rules.lua b/lib/awful/rules.lua index 6b059700..4ff94170 100644 --- a/lib/awful/rules.lua +++ b/lib/awful/rules.lua @@ -253,8 +253,11 @@ rules.extra_properties = {} -- @tfield table awful.rules.high_priority_properties rules.high_priority_properties = {} -rules.delayed_properties = { - focus = function() end, +rules.delayed_properties = {} + +local force_ignore = { + titlebars_enabled=true, focus=true, screen=true, x=true, + y=true, width=true, height=true, geometry=true } function rules.high_priority_properties.tag(c, value) @@ -349,7 +352,10 @@ function rules.execute(c, props, callbacks) value = value(c, props) end - if not rules.high_priority_properties[property] and not rules.delayed_properties[property] then + local ignore = rules.high_priority_properties[property] or + rules.delayed_properties[property] or force_ignore[property] + + if not ignore then if rules.extra_properties[property] then rules.extra_properties[property](c, value) elseif type(c[property]) == "function" then @@ -369,14 +375,16 @@ function rules.execute(c, props, callbacks) -- Apply the delayed properties for prop, handler in pairs(rules.delayed_properties) do - local value = props[prop] + if not force_ignore[prop] then + local value = props[prop] - if value ~= nil then - if type(value) == "function" then - value = value(c, props) + if value ~= nil then + if type(value) == "function" then + value = value(c, props) + end + + handler(c, value, props) end - - handler(c, value, props) end end From b0aedcda674489002c4faee01b72e1ade049c7df Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Thu, 14 Apr 2016 23:45:04 -0400 Subject: [PATCH 11/26] awful.rules: Add `new_tag` property Create a new dynamic tag for the client. --- lib/awful/rules.lua | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/lib/awful/rules.lua b/lib/awful/rules.lua index 4ff94170..a011590f 100644 --- a/lib/awful/rules.lua +++ b/lib/awful/rules.lua @@ -249,6 +249,7 @@ rules.extra_properties = {} -- By default, the table has the following functions: -- -- * tag +-- * new_tag -- -- @tfield table awful.rules.high_priority_properties rules.high_priority_properties = {} @@ -305,6 +306,32 @@ function rules.extra_properties.geometry(c, _, props) c:geometry(new_geo) --TODO use request::geometry end +--- Create a new tag based on a rule. +-- @tparam client c The client +-- @tparam boolean|function|string value The value. +-- @treturn tag The new tag +function rules.high_priority_properties.new_tag(c, value) + local ty = type(value) + local t = nil + + if ty == "boolean" then + -- Create a new tag named after the client class + t = atag.add(c.class or "N/A", {screen=c.screen, volatile=true}) + elseif ty == "string" then + -- Create a tag named after "value" + t = atag.add(value, {screen=c.screen, volatile=true}) + elseif ty == "table" then + -- Assume a table of tags properties + t = atag.add(value.name or c.class or "N/A", value) + else + assert(false) + end + + add_to_tag(c, t) + + return t +end + --- Apply properties and callbacks to a client. -- @client c The client. -- @tab props Properties to apply. From 7cd76e052919c9fe4b69294663dec1f5adc8465d Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sun, 17 Apr 2016 03:52:33 -0400 Subject: [PATCH 12/26] awful.rules: Add 'placement' property --- lib/awful/rules.lua | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/lib/awful/rules.lua b/lib/awful/rules.lua index a011590f..8632d3a7 100644 --- a/lib/awful/rules.lua +++ b/lib/awful/rules.lua @@ -16,6 +16,7 @@ local type = type local ipairs = ipairs local pairs = pairs local atag = require("awful.tag") +local a_place = require("awful.placement") local rules = {} @@ -258,7 +259,7 @@ rules.delayed_properties = {} local force_ignore = { titlebars_enabled=true, focus=true, screen=true, x=true, - y=true, width=true, height=true, geometry=true + y=true, width=true, height=true, geometry=true,placement=true, } function rules.high_priority_properties.tag(c, value) @@ -332,6 +333,29 @@ function rules.high_priority_properties.new_tag(c, value) return t end +function rules.extra_properties.placement(c, value) + -- Avoid problems + if awesome.startup and + (c.size_hints.user_position or c.size_hints.program_position) then + return + end + + local ty = type(value) + + local args = { + honor_workarea = true, + honor_padding = true + } + + if ty == "function" or (ty == "table" and + getmetatable(value) and getmetatable(value).__call + ) then + value(c, args) + elseif ty == "string" and a_place[value] then + a_place[value](c, args) + end +end + --- Apply properties and callbacks to a client. -- @client c The client. -- @tab props Properties to apply. @@ -367,6 +391,21 @@ function rules.execute(c, props, callbacks) rules.extra_properties.placement(c, props.placement, props) end + -- Make sure the tag is selected before the main rules are called. + -- Otherwise properties like "urgent" or "focus" may fail because they + -- will be overiden by various callbacks. + -- Previously, this was done in a second client.manage callback, but caused + -- a race condition where the order the require() would change the output. + c:emit_signal("request::tag", nil, {reason="rules"}) + + -- By default, rc.lua use no_overlap+no_offscreen placement. This has to + -- be executed before x/y/width/height/geometry as it would otherwise + -- always override the user specified position with the default rule. + if props.placement then + -- It may be a function, so this one doesn't execute it like others + rules.extra_properties.placement(c, props.placement, props) + end + -- Now that the tags and screen are set, handle the geometry if props.height or props.width or props.x or props.y or props.geometry then rules.extra_properties.geometry(c, nil, props) From 299e155acca4eaa5b440e0b387c3c439ecc4bbf5 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sun, 17 Apr 2016 03:52:59 -0400 Subject: [PATCH 13/26] client: Extend request::tag instead of awful.tag.withcurrent There was a regression when refactoring the API. It was no longer possible to disable the automatic tag selection. Due to recent changes, it was no longer possible to disable the default tag selection handler. This commit extend the already existing request::tag mechanism to let handlers select the tags. --- ewmh.c | 2 +- lib/awful/ewmh.lua | 8 +++++++- lib/awful/tag.lua | 9 --------- tests/examples/shims/client.lua | 1 + 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/ewmh.c b/ewmh.c index 183b259a..5f9c5b58 100755 --- a/ewmh.c +++ b/ewmh.c @@ -399,7 +399,7 @@ ewmh_process_desktop(client_t *c, uint32_t desktop) if(desktop == 0xffffffff) { luaA_object_push(L, c); - lua_pushnil(L); + lua_pushboolean(L, true); luaA_object_emit_signal(L, -2, "request::tag", 1); /* Pop the client, arguments are already popped */ lua_pop(L, 1); diff --git a/lib/awful/ewmh.lua b/lib/awful/ewmh.lua index de4af306..e110a450 100644 --- a/lib/awful/ewmh.lua +++ b/lib/awful/ewmh.lua @@ -177,8 +177,14 @@ end -- -- @client c A client to tag -- @tag[opt] t A tag to use. If omitted, then the client is made sticky. -function ewmh.tag(c, t) +-- @tparam[opt={}] table hints Extra information +function ewmh.tag(c, t, hints) --luacheck: no unused + -- There is nothing to do + if not t and #c:tags() > 0 then return end + if not t then + c:to_selected_tags() + elseif type(t) == "boolean" and t then c.sticky = true else c.screen = t.screen diff --git a/lib/awful/tag.lua b/lib/awful/tag.lua index a78a1023..7f8ad139 100644 --- a/lib/awful/tag.lua +++ b/lib/awful/tag.lua @@ -29,10 +29,6 @@ local function get_screen(s) return s and capi.screen[s] end --- awful.client is required() at the end of this file so the miss_handler is set --- before it is being required. -local client - local tag = {object = {}, mt = {} } -- Private data @@ -1370,11 +1366,6 @@ object.properties(capi.tag, { setter_fallback = tag.setproperty, }) --- fix a load loop -client = require("awful.client") -capi.client.connect_signal("manage", function(c) client.object.to_selected_tags(c) end) - - return setmetatable(tag, tag.mt) -- 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 index f2dd0d38..97d19fd9 100644 --- a/tests/examples/shims/client.lua +++ b/tests/examples/shims/client.lua @@ -12,6 +12,7 @@ local function add_signals(c) c:add_signal("property::screen") c:add_signal("property::geometry") c:add_signal("request::geometry") + c:add_signal("request::tag") c:add_signal("swapped") c:add_signal("raised") c:add_signal("property::_label") --Used internally From f72dcce4bde862124216aaa5b0beff0f362f0731 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Thu, 14 Apr 2016 03:13:06 -0400 Subject: [PATCH 14/26] titlebars: Use the request system rather than 'manage'. As awesomerc.lua "manage" section is executed after the rules, using a 'geometry' or 'placement' property in the rules was broken. --- awesomerc.lua | 83 +++++++++++++++++++++++--------------------- lib/awful/client.lua | 6 ++++ lib/awful/rules.lua | 4 +++ 3 files changed, 54 insertions(+), 39 deletions(-) diff --git a/awesomerc.lua b/awesomerc.lua index 5f7e87c6..72c6d01e 100755 --- a/awesomerc.lua +++ b/awesomerc.lua @@ -458,6 +458,11 @@ awful.rules.rules = { } }, properties = { floating = true }}, + -- Add titlebars to normal clients and dialogs + { rule_any = {type = { "normal", "dialog" } + }, properties = { titlebars_enabled = true } + }, + -- Set Firefox to always map on the tag named "2" on screen 1. -- { rule = { class = "Firefox" }, -- properties = { screen = 1, tag = "2" } }, @@ -481,48 +486,48 @@ client.connect_signal("manage", function (c) -- Prevent clients from being unreachable after screen count changes. awful.placement.no_offscreen(c) end +end) - local titlebars_enabled = true - if titlebars_enabled and (c.type == "normal" or c.type == "dialog") then - -- buttons for the titlebar - local buttons = awful.util.table.join( - awful.button({ }, 1, function() - client.focus = c - c:raise() - awful.mouse.client.move(c) - end), - awful.button({ }, 3, function() - client.focus = c - c:raise() - awful.mouse.client.resize(c) - end) - ) +-- Add a titlebar if titlebars_enabled is set to true in the rules. +client.connect_signal("request::titlebars", function(c) + -- buttons for the titlebar + local buttons = awful.util.table.join( + awful.button({ }, 1, function() + client.focus = c + c:raise() + awful.mouse.client.move(c) + end), + awful.button({ }, 3, function() + client.focus = c + c:raise() + awful.mouse.client.resize(c) + end) + ) - awful.titlebar(c) : setup { - { -- Left - awful.titlebar.widget.iconwidget(c), - buttons = buttons, - layout = wibox.layout.fixed.horizontal + awful.titlebar(c) : setup { + { -- Left + awful.titlebar.widget.iconwidget(c), + buttons = buttons, + layout = wibox.layout.fixed.horizontal + }, + { -- Middle + { -- Title + align = "center", + widget = awful.titlebar.widget.titlewidget(c) }, - { -- Middle - { -- Title - align = "center", - widget = awful.titlebar.widget.titlewidget(c) - }, - buttons = buttons, - layout = wibox.layout.flex.horizontal - }, - { -- Right - awful.titlebar.widget.floatingbutton (c), - awful.titlebar.widget.maximizedbutton(c), - awful.titlebar.widget.stickybutton (c), - awful.titlebar.widget.ontopbutton (c), - awful.titlebar.widget.closebutton (c), - layout = wibox.layout.fixed.horizontal() - }, - layout = wibox.layout.align.horizontal - } - end + buttons = buttons, + layout = wibox.layout.flex.horizontal + }, + { -- Right + awful.titlebar.widget.floatingbutton (c), + awful.titlebar.widget.maximizedbutton(c), + awful.titlebar.widget.stickybutton (c), + awful.titlebar.widget.ontopbutton (c), + awful.titlebar.widget.closebutton (c), + layout = wibox.layout.fixed.horizontal() + }, + layout = wibox.layout.align.horizontal + } end) -- Enable sloppy focus diff --git a/lib/awful/client.lua b/lib/awful/client.lua index b6e0ef00..5af66f07 100644 --- a/lib/awful/client.lua +++ b/lib/awful/client.lua @@ -1122,6 +1122,12 @@ capi.client.add_signal("property::floating") capi.client.add_signal("property::dockable") +--- Emited when a client need to get a titlebar. +-- @signal request::titlebars +-- @tparam[opt=nil] string content The context (like "rules") +-- @tparam[opt=nil] table hints Some hints. +capi.client.add_signal("request::titlebars") + --- The client marked signal (deprecated). -- @signal .marked capi.client.add_signal("marked") diff --git a/lib/awful/rules.lua b/lib/awful/rules.lua index 8632d3a7..18168825 100644 --- a/lib/awful/rules.lua +++ b/lib/awful/rules.lua @@ -361,6 +361,10 @@ end -- @tab props Properties to apply. -- @tab[opt] callbacks Callbacks to apply. function rules.execute(c, props, callbacks) + -- This has to be done first, as it will impact geometry related props. + if props.titlebars_enabled then + c:emit_signal("request::titlebars", "rules", {properties=props}) + end -- Before requesting a tag, make sure the screen is right if props.screen then From 89edc921104af30db88124d2c41b52c2a35ebd2f Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Tue, 12 Apr 2016 04:41:59 -0400 Subject: [PATCH 15/26] awful.rules: Add the 'tags' property Due to recent changes, it was no longer possible to disable the default tag selection handler. This commit extend the already existing request::tag mechanism to let handlers select the tags. --- lib/awful/rules.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/awful/rules.lua b/lib/awful/rules.lua index 18168825..32d4b7cf 100644 --- a/lib/awful/rules.lua +++ b/lib/awful/rules.lua @@ -16,6 +16,7 @@ local type = type local ipairs = ipairs local pairs = pairs local atag = require("awful.tag") +local util = require("awful.util") local a_place = require("awful.placement") local rules = {} @@ -356,6 +357,11 @@ function rules.extra_properties.placement(c, value) end end +function rules.extra_properties.tags(c, value) + local current = c:tags() + c:tags(util.table.merge(current, value)) +end + --- Apply properties and callbacks to a client. -- @client c The client. -- @tab props Properties to apply. From 06716df05a27e5753f01196db544aa4fd41aff6c Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Mon, 18 Apr 2016 02:55:54 -0400 Subject: [PATCH 16/26] awful.rules: Apply border_width early --- lib/awful/rules.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/awful/rules.lua b/lib/awful/rules.lua index 32d4b7cf..c9945926 100644 --- a/lib/awful/rules.lua +++ b/lib/awful/rules.lua @@ -261,6 +261,7 @@ rules.delayed_properties = {} local force_ignore = { titlebars_enabled=true, focus=true, screen=true, x=true, y=true, width=true, height=true, geometry=true,placement=true, + border_width=true, } function rules.high_priority_properties.tag(c, value) @@ -372,6 +373,12 @@ function rules.execute(c, props, callbacks) c:emit_signal("request::titlebars", "rules", {properties=props}) end + -- Border width will also cause geometry related properties to fail + if props.border_width then + c.border_width = type(props.border_width) == "function" and + props.border_width(c, props) or props.border_width + end + -- Before requesting a tag, make sure the screen is right if props.screen then c.screen = type(props.screen) == "function" and screen[props.screen(c,props)] From c678a0d426d48a7ab083986d41a654c0dd1f56bf Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Mon, 18 Apr 2016 02:56:25 -0400 Subject: [PATCH 17/26] awful.rules: Apply floating early --- lib/awful/rules.lua | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/awful/rules.lua b/lib/awful/rules.lua index c9945926..91201d72 100644 --- a/lib/awful/rules.lua +++ b/lib/awful/rules.lua @@ -261,7 +261,7 @@ rules.delayed_properties = {} local force_ignore = { titlebars_enabled=true, focus=true, screen=true, x=true, y=true, width=true, height=true, geometry=true,placement=true, - border_width=true, + border_width=true,floating=true } function rules.high_priority_properties.tag(c, value) @@ -379,6 +379,13 @@ function rules.execute(c, props, callbacks) props.border_width(c, props) or props.border_width end + -- Geometry will only work if floating is true, otherwise the "saved" + -- geometry will be restored. + if props.floating then + c.floating = type(props.floating) == "function" and props.floating(c,props) + or props.floating + end + -- Before requesting a tag, make sure the screen is right if props.screen then c.screen = type(props.screen) == "function" and screen[props.screen(c,props)] From f681ace58721f0f636c7daf30829192ca806f407 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Fri, 15 Apr 2016 01:43:58 -0400 Subject: [PATCH 18/26] ewmh: Take focusable into account in request::activate --- lib/awful/ewmh.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/awful/ewmh.lua b/lib/awful/ewmh.lua index e110a450..bca2e6e7 100644 --- a/lib/awful/ewmh.lua +++ b/lib/awful/ewmh.lua @@ -162,6 +162,10 @@ end -- @tparam[opt] table hints A table with additional hints: -- @tparam[opt=false] boolean hints.raise should the client be raised? function ewmh.activate(c, context, hints) -- luacheck: no unused args + hints = hints or {} + + if c.focusable == false and not hints.force then return end + if c:isvisible() then client.focus = c end From e54387904b16e3087cb989e862d9c98196a6d7b7 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Fri, 15 Apr 2016 05:09:41 -0400 Subject: [PATCH 19/26] client: Add request::geometry Remove request::fullscreen and request::maximized_* and use a single request for them. The other client resizing features will soon also start to use this. --- lib/awful/ewmh.lua | 115 +++++++++++++++++++++------------------------ objects/client.c | 25 +++++----- 2 files changed, 64 insertions(+), 76 deletions(-) diff --git a/lib/awful/ewmh.lua b/lib/awful/ewmh.lua index bca2e6e7..ffe04fd7 100644 --- a/lib/awful/ewmh.lua +++ b/lib/awful/ewmh.lua @@ -12,71 +12,14 @@ local client = client local screen = screen local ipairs = ipairs local math = math +local util = require("awful.util") local aclient = require("awful.client") +local aplace = require("awful.placement") local ewmh = {} local data = setmetatable({}, { __mode = 'k' }) -local function store_geometry(window, reqtype) - if not data[window] then data[window] = {} end - if not data[window][reqtype] then data[window][reqtype] = {} end - data[window][reqtype] = window:geometry() - data[window][reqtype].screen = window.screen -end - ---- Maximize a window horizontally. --- --- @param window The window. --- @param set Set or unset the maximized values. -local function maximized_horizontal(window, set) - if set then - store_geometry(window, "maximized_horizontal") - local g = screen[window.screen].workarea - local bw = window.border_width or 0 - window:geometry { width = g.width - 2*bw, x = g.x } - elseif data[window] and data[window].maximized_horizontal - and data[window].maximized_horizontal.x - and data[window].maximized_horizontal.width then - local g = data[window].maximized_horizontal - window:geometry { width = g.width, x = g.x } - end -end - ---- Maximize a window vertically. --- --- @param window The window. --- @param set Set or unset the maximized values. -local function maximized_vertical(window, set) - if set then - store_geometry(window, "maximized_vertical") - local g = screen[window.screen].workarea - local bw = window.border_width or 0 - window:geometry { height = g.height - 2*bw, y = g.y } - elseif data[window] and data[window].maximized_vertical - and data[window].maximized_vertical.y - and data[window].maximized_vertical.height then - local g = data[window].maximized_vertical - window:geometry { height = g.height, y = g.y } - end -end - ---- Fullscreen a window. --- --- @param window The window. --- @param set Set or unset the fullscreen values. -local function fullscreen(window, set) - if set then - store_geometry(window, "fullscreen") - data[window].fullscreen.border_width = window.border_width - window.border_width = 0 - window:geometry(screen[window.screen].geometry) - elseif data[window] and data[window].fullscreen then - window.border_width = data[window].fullscreen.border_width - window:geometry(data[window].fullscreen) - end -end - local function screen_change(window) if data[window] then for _, reqtype in ipairs({ "maximized_vertical", "maximized_horizontal", "fullscreen" }) do @@ -202,12 +145,60 @@ function ewmh.urgent(c, urgent) end end +-- Map the state to the action name +local context_mapper = { + maximized_vertical = "maximize_vertically", + maximized_horizontal = "maximize_horizontally", + fullscreen = "maximize" +} + +--- Move and resize the client. +-- +-- This is the default geometry request handler. +-- +-- @tparam client c The client +-- @tparam string context The context +-- @tparam[opt={}] table hints The hints to pass to the handler +function ewmh.geometry(c, context, hints) + context = context or "" + + local original_context = context + + -- Now, map it to something useful + context = context_mapper[context] or context + + local props = util.table.clone(hints or {}, false) + props.store_geometry = props.store_geometry==nil and true or props.store_geometry + + -- If it is a known placement function, then apply it, otherwise let + -- other potential handler resize the client (like in-layout resize or + -- floating client resize) + if aplace[context] then + + -- Check if it correspond to a boolean property + local state = c[original_context] + + -- If the property is boolean and it correspond to the undo operation, + -- restore the stored geometry. + if state == false then + aplace.restore(c,{context=context}) + return + end + + local honor_default = original_context ~= "fullscreen" + + if props.honor_workarea == nil then + props.honor_workarea = honor_default + end + + aplace[context](c, props) + end +end + client.connect_signal("request::activate", ewmh.activate) client.connect_signal("request::tag", ewmh.tag) client.connect_signal("request::urgent", ewmh.urgent) -client.connect_signal("request::maximized_horizontal", maximized_horizontal) -client.connect_signal("request::maximized_vertical", maximized_vertical) -client.connect_signal("request::fullscreen", fullscreen) +client.connect_signal("request::geometry", ewmh.geometry) client.connect_signal("property::screen", screen_change) client.connect_signal("property::border_width", geometry_change) client.connect_signal("property::geometry", geometry_change) diff --git a/objects/client.c b/objects/client.c index 8ec2118d..b1ae5c4d 100644 --- a/objects/client.c +++ b/objects/client.c @@ -1697,9 +1697,9 @@ client_set_fullscreen(lua_State *L, int cidx, bool s) client_set_ontop(L, cidx, false); } int abs_cidx = luaA_absindex(L, cidx); \ - lua_pushboolean(L, s); + lua_pushstring(L, "fullscreen"); c->fullscreen = s; - luaA_object_emit_signal(L, abs_cidx, "request::fullscreen", 1); + luaA_object_emit_signal(L, abs_cidx, "request::geometry", 1); luaA_object_emit_signal(L, abs_cidx, "property::fullscreen", 0); /* Force a client resize, so that titlebars get shown/hidden */ client_resize_do(c, c->geometry, true); @@ -1730,10 +1730,10 @@ client_get_maximized(client_t *c) if(c->maximized_##type != s) \ { \ int abs_cidx = luaA_absindex(L, cidx); \ - lua_pushboolean(L, s); \ int max_before = client_get_maximized(c); \ c->maximized_##type = s; \ - luaA_object_emit_signal(L, abs_cidx, "request::maximized_" #type, 1); \ + lua_pushstring(L, "maximized_"#type);\ + luaA_object_emit_signal(L, abs_cidx, "request::geometry", 1); \ luaA_object_emit_signal(L, abs_cidx, "property::maximized_" #type, 0); \ if(max_before != client_get_maximized(c)) \ luaA_object_emit_signal(L, abs_cidx, "property::maximized", 0); \ @@ -3446,17 +3446,14 @@ client_class_setup(lua_State *L) */ signal_add(&client_class.signals, "request::activate"); /** - * @signal request::fullscreen + * @signal request::geometry + * @tparam client c The client + * @tparam string context Why and what to resize. This is used for the + * handlers to know if they are capable of applying the new geometry. + * @tparam[opt={}] table Additional arguments. Each context handler may + * interpret this differently. */ - signal_add(&client_class.signals, "request::fullscreen"); - /** - * @signal request::maximized_horizontal - */ - signal_add(&client_class.signals, "request::maximized_horizontal"); - /** - * @signal request::maximized_vertical - */ - signal_add(&client_class.signals, "request::maximized_vertical"); + signal_add(&client_class.signals, "request::geometry"); /** * @signal request::tag */ From f5a27ab99bf90d05881a7abc40d3484b20ff2c59 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sun, 17 Apr 2016 03:28:44 -0400 Subject: [PATCH 20/26] tests: Test maximizing and fullscreen --- tests/test-maximize.lua | 90 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 tests/test-maximize.lua diff --git a/tests/test-maximize.lua b/tests/test-maximize.lua new file mode 100644 index 00000000..c55e4e9a --- /dev/null +++ b/tests/test-maximize.lua @@ -0,0 +1,90 @@ +--- Tests maximize and fullscreen + +local runner = require("_runner") +local awful = require("awful") + +local original_geo = nil + +local steps = { + function(count) + if count == 1 then + awful.spawn("xterm") + else + local c = client.get()[1] + if c then + original_geo = c:geometry() + return true + end + end + end, + + -- maximize horizontally + function() + local c = client.get()[1] + assert(not c.maximized_horizontal) + assert(not c.maximized_vertical ) + assert(not c.fullscreen ) + + c.maximized_horizontal = true + return true + end, + function() + local c = client.get()[1] + + local new_geo = c:geometry() + local sgeo = c.screen.workarea + + --assert(new_geo.x-c.border_width==sgeo.x) --FIXME c:geometry({x=1}).x ~= 1 + + --assert(new_geo.width+2*c.border_width==sgeo.width) --FIXME off by 4px + + c.maximized_horizontal = false + return true + end, + -- Test restoring a geometry + function() + local c = client.get()[1] + + local new_geo = c:geometry() + + for k,v in pairs(original_geo) do + assert(new_geo[k] == v) + end + + c.fullscreen = true + + return true + end, + function() + local c = client.get()[1] + + local new_geo = c:geometry() + local sgeo = c.screen.geometry + local bw = c.border_width + + assert(c.fullscreen) + assert(new_geo.x-bw==sgeo.x) + assert(new_geo.y-bw==sgeo.y) + assert(new_geo.x+new_geo.width+bw==sgeo.x+sgeo.width) + assert(new_geo.y+new_geo.height+bw==sgeo.y+sgeo.height) + + c.fullscreen = false + + return true + end, + function() + local c = client.get()[1] + + local new_geo = c:geometry() + + for k,v in pairs(original_geo) do + assert(new_geo[k] == v) + end + + return true + end +} + +runner.run_steps(steps) + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 From 6bc99fe52f2fd9ca932b61da453951f15836d7cd Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sat, 16 Apr 2016 05:51:25 -0400 Subject: [PATCH 21/26] awesomerc: Use rules to set the default position instead of "manage" This avoid a conflict when rules try to set overlapping geometry. --- awesomerc.lua | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/awesomerc.lua b/awesomerc.lua index 72c6d01e..ad828d1d 100755 --- a/awesomerc.lua +++ b/awesomerc.lua @@ -430,7 +430,10 @@ awful.rules.rules = { focus = awful.client.focus.filter, raise = true, keys = clientkeys, - buttons = clientbuttons } }, + buttons = clientbuttons, + placement = awful.placement.no_overlap+awful.placement.no_offscreen + } + }, -- Floating clients. { rule_any = { @@ -472,17 +475,13 @@ awful.rules.rules = { -- {{{ Signals -- Signal function to execute when a new client appears. client.connect_signal("manage", function (c) - if not awesome.startup then - -- Set the windows at the slave, - -- i.e. put it at the end of others instead of setting it master. - -- awful.client.setslave(c) + -- Set the windows at the slave, + -- i.e. put it at the end of others instead of setting it master. + -- if not awesome.startup then awful.client.setslave(c) end - -- Put windows in a smart way, only if they do not set an initial position. - if not c.size_hints.user_position and not c.size_hints.program_position then - awful.placement.no_overlap(c) - awful.placement.no_offscreen(c) - end - elseif not c.size_hints.user_position and not c.size_hints.program_position then + if awesome.startup and + not c.size_hints.user_position + and not c.size_hints.program_position then -- Prevent clients from being unreachable after screen count changes. awful.placement.no_offscreen(c) end From 9798b455cbe4ba1f0d773e8a72a5885a5e9601e7 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Tue, 12 Apr 2016 23:18:09 -0400 Subject: [PATCH 22/26] tests: Add a flexible dummy client. XTerm isn't really a perfect test candidate. --- tests/_client.lua | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 tests/_client.lua diff --git a/tests/_client.lua b/tests/_client.lua new file mode 100644 index 00000000..10f0700e --- /dev/null +++ b/tests/_client.lua @@ -0,0 +1,32 @@ +local spawn = require("awful.spawn") + +-- This file provide a simple, yet flexible, test client. +-- It is used to test the `awful.rules` + +return function(class, title) + title = title or 'Awesome test client' + + local cmd = {"lua" , "-e", table.concat { + "local lgi = require 'lgi';", + "local Gtk = lgi.require('Gtk');", + "Gtk.init();", + "local class = '", + class or 'test_app',"';", + "local window = Gtk.Window {", + " default_width = 100,", + " default_height = 100,", + " title = '",title, + "'};", + "window:set_wmclass(class, class);", + "local app = Gtk.Application {", + " application_id = 'org.awesomewm.tests.",class, + "'};", + "function app:on_activate()", + " window.application = self;", + " window:show_all();", + "end;", + "app:run {''}" + }} + + spawn(cmd) +end From 8875b66d2e4fdf02ef64d91ece248791c4f25d26 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Wed, 13 Apr 2016 03:22:29 -0400 Subject: [PATCH 23/26] tests: Test awful.rules --- tests/test-awful-rules.lua | 291 +++++++++++++++++++++++++++++++++++++ tests/test-maximize.lua | 4 +- 2 files changed, 293 insertions(+), 2 deletions(-) create mode 100644 tests/test-awful-rules.lua diff --git a/tests/test-awful-rules.lua b/tests/test-awful-rules.lua new file mode 100644 index 00000000..7dc871a2 --- /dev/null +++ b/tests/test-awful-rules.lua @@ -0,0 +1,291 @@ +local awful = require("awful") +local beautiful = require("beautiful") +local test_client = require("_client") +local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1) + +local callback_called = false + +-- Magic table to store tests +local tests = {} + +local tb_height = awful.util.round(beautiful.get_font_height() * 1.5) +-- local border_width = beautiful.border_width + +local function test_rule(rule) + rule.rule = rule.rule or {} + + -- Create a random class. The number need to be large as "rule1" and "rule10" would collide + -- The math.ceil is necessary to remove the floating point, this create an invalid dbus name + local class = string.format("rule%010d", 42+#tests) + rule.rule.class = rule.rule.class or class + + test_client(rule.rule.class, rule.properties.name or "Foo") + if rule.test then + local t = rule.test + table.insert(tests, function() return t(rule.rule.class) end) + rule.test = nil + end + + table.insert(awful.rules.rules, rule) +end + +-- Helper function to search clients +local function get_client_by_class(class) + for _, c in ipairs(client.get()) do + if class == c.class then + return c + end + end +end + +-- Test callback and floating +test_rule { + properties = { floating = true }, + callback = function(c) + assert(type(c) == "client") + callback_called = true + end, + test = function(class) + -- Test if callbacks works + assert(callback_called) + + -- Make sure "smart" dynamic properties are applied + assert(get_client_by_class(class).floating) + + -- The size should not have changed + local geo = get_client_by_class(class):geometry() + assert(geo.width == 100 and geo.height == 100+tb_height) + + return true + end +} + +-- Test ontop +test_rule { + properties = { ontop = true }, + test = function(class) + -- Make sure C-API properties are applied + assert(get_client_by_class(class).ontop) + + return true + end +} + +-- Test placement +test_rule { + properties = { + floating = true, + placement = "bottom_right" + }, + test = function(class) + -- Test placement + local sgeo = mouse.screen.workarea + local c = get_client_by_class(class) + local geo = c:geometry() + + assert(geo.y+geo.height+2*c.border_width == sgeo.y+sgeo.height) + assert(geo.x+geo.width+2*c.border_width == sgeo.x+sgeo.width) + + return true + end +} + +-- Create a tag named after the class name +test_rule { + properties = { new_tag=true }, + test = function(class) + local c_new_tag1 = get_client_by_class(class) + + assert(#c_new_tag1:tags()[1]:clients() == 1) + assert(c_new_tag1:tags()[1].name == class) + + return true + end +} + +-- Create a tag named Foo Tag with a magnifier layout +test_rule { + properties = { + new_tag = { + name = "Foo Tag", + layout = awful.layout.suit.magnifier + } + }, + test = function(class) + local t_new_tag2 = get_client_by_class(class):tags()[1] + + assert( #t_new_tag2:clients() == 1 ) + assert( t_new_tag2.name == "Foo Tag" ) + assert( t_new_tag2.layout.name == "magnifier" ) + + return true + end +} + +-- Create a tag named "Bar Tag" +test_rule { + properties = { new_tag= "Bar Tag" }, + test = function(class) + local c_new_tag3 = get_client_by_class(class) + assert(#c_new_tag3:tags()[1]:clients() == 1) + assert(c_new_tag3:tags()[1].name == "Bar Tag") + + return true + end +} + +-- Test if setting the geometry work +test_rule { + properties = { + floating = true, + x = 200, + y = 200, + width = 200, + height = 200, + }, + test = function(class) + local c = get_client_by_class(class) + local geo = c:geometry() + + -- Give it some time + if geo.y < 180 then return end + + assert(geo.x == 200) + assert(geo.y == 200) + assert(geo.width == 200) + assert(geo.height == 200) + + return true + end +} + +-- Test if setting a partial geometry preserve the other attributes +test_rule { + properties = { + floating = true, + x = 200, + geometry = { + height = 220 + } + }, + test = function(class) + local c = get_client_by_class(class) + local geo = c:geometry() + + -- Give it some time + if geo.height < 200 then return end + + assert(geo.x == 200) + assert(geo.width == 100) + assert(geo.height == 220) + + return true + end +} + +-- Test maximized_horizontal +test_rule { + properties = { maximized_horizontal = true }, + test = function(class) + local c = get_client_by_class(class) + -- Make sure C-API properties are applied + + assert(c.maximized_horizontal) + + local geo = c:geometry() + local sgeo = c.screen.workarea + + assert(geo.x==sgeo.x) + + assert(geo.width+2*c.border_width==sgeo.width) + + return true + end +} + +-- Test maximized_vertical +test_rule { + properties = { maximized_vertical = true }, + test = function(class) + local c = get_client_by_class(class) + -- Make sure C-API properties are applied + + assert(c.maximized_vertical) + + local geo = c:geometry() + local sgeo = c.screen.workarea + + assert(geo.y==sgeo.y) + + assert(geo.height+2*c.border_width==sgeo.height) + + return true + end +} + +-- Test fullscreen +test_rule { + properties = { fullscreen = true }, + test = function(class) + local c = get_client_by_class(class) + -- Make sure C-API properties are applied + + assert(c.fullscreen) + + local geo = c:geometry() + local sgeo = c.screen.geometry + + assert(geo.x==sgeo.x) + assert(geo.y==sgeo.y) + assert(geo.height+2*c.border_width==sgeo.height) + assert(geo.width+2*c.border_width==sgeo.width) + + return true + end +} + +-- Test tag and switchtotag +test_rule { + properties = { + tag = "9", + switchtotag = true + }, + test = function(class) + local c = get_client_by_class(class) + -- Make sure C-API properties are applied + + assert(#c:tags() == 1) + assert(c:tags()[1].name == "9") + assert(c.screen.selected_tag.name == "9") + + return true + end +} +test_rule { + properties = { + tag = "8", + switchtotag = false + }, + test = function(class) + local c = get_client_by_class(class) + -- Make sure C-API properties are applied + + assert(#c:tags() == 1) + assert(c:tags()[1].name == "8") + + assert(c.screen.selected_tag.name ~= "8") + + return true + end +} + +-- Wait until all the auto-generated clients are ready +local function spawn_clients() + if #client.get() >= #tests then + -- Set tiled + awful.layout.inc(1) + return true + end +end + +require("_runner").run_steps{spawn_clients, unpack(tests)} diff --git a/tests/test-maximize.lua b/tests/test-maximize.lua index c55e4e9a..bddf693e 100644 --- a/tests/test-maximize.lua +++ b/tests/test-maximize.lua @@ -31,8 +31,8 @@ local steps = { function() local c = client.get()[1] - local new_geo = c:geometry() - local sgeo = c.screen.workarea + --local new_geo = c:geometry() + --local sgeo = c.screen.workarea --assert(new_geo.x-c.border_width==sgeo.x) --FIXME c:geometry({x=1}).x ~= 1 From a4f1b760bdaef2f5041c5ed0ddf4a10c3610933d Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sun, 17 Apr 2016 05:00:02 -0400 Subject: [PATCH 24/26] awful.rules: Apply size_hints_honor early There is a property race with width, height and geometry --- lib/awful/rules.lua | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/awful/rules.lua b/lib/awful/rules.lua index 91201d72..1a9b872c 100644 --- a/lib/awful/rules.lua +++ b/lib/awful/rules.lua @@ -261,7 +261,7 @@ rules.delayed_properties = {} local force_ignore = { titlebars_enabled=true, focus=true, screen=true, x=true, y=true, width=true, height=true, geometry=true,placement=true, - border_width=true,floating=true + border_width=true,floating=true,size_hints_honor=true } function rules.high_priority_properties.tag(c, value) @@ -379,6 +379,13 @@ function rules.execute(c, props, callbacks) props.border_width(c, props) or props.border_width end + -- Size hints will be re-applied when setting width/height unless it is + -- disabled first + if props.size_hints_honor ~= nil then + c.size_hints_honor = type(props.size_hints_honor) == "function" and props.size_hints_honor(c,props) + or props.size_hints_honor + end + -- Geometry will only work if floating is true, otherwise the "saved" -- geometry will be restored. if props.floating then From f2a19690ac908a5cbc856deb0576919e5e9b2338 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sun, 17 Apr 2016 05:15:21 -0400 Subject: [PATCH 25/26] tests: Test geometry changes --- tests/test-geometry.lua | 243 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 tests/test-geometry.lua diff --git a/tests/test-geometry.lua b/tests/test-geometry.lua new file mode 100644 index 00000000..0a36aa5e --- /dev/null +++ b/tests/test-geometry.lua @@ -0,0 +1,243 @@ +--- Tests for drawing geometry + +local runner = require( "_runner" ) +local awful = require( "awful" ) +local wibox = require( "wibox" ) +local beautiful = require( "beautiful" ) + +local w = nil +local w1_draw, w2_draw + +-- Disable automatic placement +awful.rules.rules = { + { rule = { }, properties = { + border_width = 0, + size_hints_honor = false, + x = 0, + y = 0, + width = 100, + height = 100, + border_color = beautiful.border_normal + } + } +} + +local steps = { + function() + if #client.get() == 0 then + return true + end + for _,c in ipairs(client.get()) do + c:kill() + end + end, + -- border_color should get applied via focus signal for first client on tag. + function(count) + if count == 1 then + awful.spawn("xterm") + else + local c = client.get()[1] + if c then + assert(c.size_hints_honor == false ) + assert(c.border_width == 0 ) + assert(c:geometry().x == 0 ) + assert(c:geometry().y == 0 ) + assert(c:geometry().height == 100 ) + assert(c:geometry().width == 100 ) + + c:kill() + + return true + end + end + end, + function() + awful.rules.rules = { + -- All clients will match this rule. + { rule = { },properties = { + titlebars_enabled = true, + border_width = 10, + border_color = "#00ff00", + size_hints_honor = false, + x = 0, + y = 0, + width = 100, + height = 100 + } + } + } + return true + end, + function(count) + if count == 1 then + awful.spawn("xterm") + else + local c = client.get()[1] + if c then + assert(c.border_width == 10 ) + assert(c:geometry().x == 0 ) + assert(c:geometry().y == 0 ) + assert(c:geometry().height == 100 ) + assert(c:geometry().width == 100 ) + + c.border_width = 20 + + return true + end + end + end, + function() + local c = client.get()[1] + assert(c.border_width == 20 ) + assert(c:geometry().x == 0 ) + assert(c:geometry().y == 0 ) + assert(c:geometry().height == 100 ) + assert(c:geometry().width == 100 ) + + c.border_width = 0 + + return true + end, + function() + local c = client.get()[1] + + assert(not pcall(function() c.border_width = -2000 end)) + assert(c.border_width==0) + + c.border_width = 125 + + return true + end, + function() + local c = client.get()[1] + + assert(c.border_width == 125 ) + assert(c:geometry().x == 0 ) + assert(c:geometry().y == 0 ) + assert(c:geometry().height == 100 ) + assert(c:geometry().width == 100 ) + + -- So it doesn't hide the other tests + c:kill() + + return true + end, + function() + w = wibox { + ontop = true, + border_width = 20, + x = 100, + y = 100, + width = 100, + height = 100, + visible = true, + } + + assert(w) + assert(w.border_width == 20 ) + assert(w.width == 100 ) + assert(w.height == 100 ) + assert(w.x == 100 ) + assert(w.y == 100 ) + + w:setup { + fit = function(_, _, _, height) + return height, height -- A square taking the full height + end, + draw = function(_, _, cr, width, height) + + assert(width == 100) + assert(height == 100) + + w1_draw = true + cr:set_source_rgb(1, 0, 0) -- Red + cr:arc(height/2, height/2, height/2, 0, math.pi*2) + cr:fill() + end, + layout = wibox.widget.base.make_widget, + } + + return true + end, + function() + w.border_width = 0 + assert(w1_draw) + + return true + end, + function() + assert(w.border_width == 0 ) + assert(w.width == 100 ) + assert(w.height == 100 ) + assert(w.x == 100 ) + assert(w.y == 100 ) + + w:setup { + fit = function(_, _, _, height) + return height, height -- A square taking the full height + end, + draw = function(_, _, cr, width, height) + + assert(width == 100) + assert(height == 100) + + w2_draw = true + cr:set_source_rgb(1, 0, 0) -- Red + cr:arc(height/2, height/2, height/2, 0, math.pi*2) + cr:fill() + end, + layout = wibox.widget.base.make_widget, + } + + w.visible = false + + return true + end, + function() + -- Remove the widget before the size change to avoid the asserts + w:setup { + fit = function(_, _, _, height) + return height, height -- A square taking the full height + end, + draw = function(_, _, cr, _, height) + cr:set_source_rgb(1, 0, 1) -- Purple + cr:arc(height/2, height/2, height/2, 0, math.pi*2) + cr:fill() + end, + layout = wibox.widget.base.make_widget, + } + + w.visible = true + assert(w2_draw) + + assert(w.border_width == 0 ) + assert(w.width == 100 ) + assert(w.height == 100 ) + assert(w.x == 100 ) + assert(w.y == 100 ) + + w.border_width = 5 + + w:geometry { + x = 200, + y = 200, + width = 200, + height = 200, + } + + return true + end, + function() + assert(w.border_width == 5 ) + assert(w.width == 200 ) + assert(w.height == 200 ) + assert(w.x == 200 ) + assert(w.y == 200 ) + + return true + end +} + +runner.run_steps(steps) + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 From d736850e6058537dea4c9ec9b52ffc5ca9030458 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sun, 17 Apr 2016 06:09:47 -0400 Subject: [PATCH 26/26] tests: Fix urgent test use of deprecated functions It caused noise in the test results --- tests/test-urgent.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test-urgent.lua b/tests/test-urgent.lua index 63be5b65..d763dda9 100644 --- a/tests/test-urgent.lua +++ b/tests/test-urgent.lua @@ -55,7 +55,7 @@ local steps = { root.fake_input("key_release", "2") root.fake_input("key_release", "Super_L") - elseif awful.tag.selectedlist()[1] == tags[awful.screen.focused()][2] then + elseif awful.screen.focused().selected_tags[1] == tags[awful.screen.focused()][2] then assert(#client.get() == 1) local c = client.get()[1] assert(not c.urgent, "Client is not urgent anymore.") @@ -79,11 +79,11 @@ local steps = { awful.spawn("xterm") - elseif awful.tag.selectedlist()[1] == tags[awful.screen.focused()][2] then + elseif awful.screen.focused().selected_tags[1] == tags[awful.screen.focused()][2] then assert(not urgent_cb_done) assert(awful.tag.getproperty(tags[awful.screen.focused()][2], "urgent") == false) assert(awful.tag.getproperty(tags[awful.screen.focused()][2], "urgent_count") == 0) - assert(awful.tag.selectedlist()[2] == nil) + assert(awful.screen.focused().selected_tags[2] == nil) return true end end,