diff --git a/lib/awful/client.lua b/lib/awful/client.lua index cda5dd061..68de1b38e 100644 --- a/lib/awful/client.lua +++ b/lib/awful/client.lua @@ -984,6 +984,7 @@ capi.client.connect_signal("property::fullscreen", update_implicitly_floating) capi.client.connect_signal("property::maximized_vertical", update_implicitly_floating) capi.client.connect_signal("property::maximized_horizontal", update_implicitly_floating) capi.client.connect_signal("property::maximized", update_implicitly_floating) +capi.client.connect_signal("property::fullscreen", update_implicitly_floating) capi.client.connect_signal("property::size_hints", update_implicitly_floating) capi.client.connect_signal("request::manage", update_implicitly_floating) diff --git a/lib/awful/permissions/init.lua b/lib/awful/permissions/init.lua index 909544293..64673d3f7 100644 --- a/lib/awful/permissions/init.lua +++ b/lib/awful/permissions/init.lua @@ -608,32 +608,39 @@ end, "mouse_enter") function permissions.update_border(c, context) if not pcommon.check(c, "client", "border", context) then return end - local suffix, fallback = "", "" + local suffix, fallback1, fallback2 = "", "" -- Add the sub-namespace. if c.fullscreen then - suffix, fallback = "_fullscreen", "_fullscreen" + suffix, fallback1 = "_fullscreen", "_fullscreen" elseif c.maximized then - suffix, fallback = "_maximized", "_maximized" + suffix, fallback1 = "_maximized", "_maximized" elseif c.floating then - suffix, fallback = "_floating", "_floating" + suffix, fallback1 = "_floating", "_floating" end -- Add the state suffix. if c.urgent then - suffix = suffix .. "_urgent" + suffix, fallback2 = suffix .. "_urgent", "_urgent" elseif c.active then - suffix = suffix .. "_active" + suffix, fallback2 = suffix .. "_active", "_active" elseif context == "added" then - suffix = suffix .. "_new" + suffix, fallback2 = suffix .. "_new", "_new" else - suffix = suffix .. "_normal" + suffix, fallback2 = suffix .. "_normal", "_normal" end if not c._private._user_border_width then - c._border_width = beautiful["border_width"..suffix] - or beautiful["border_width"..fallback] - or beautiful.border_width + local bw = beautiful["border_width"..suffix] + or beautiful["border_width"..fallback1] + or beautiful["border_width"..fallback2] + + -- The default `awful.permissions.geometry` handler removes the border. + if (not bw) and (c.fullscreen or c.maximized) then + bw = 0 + end + + c._border_width = bw or beautiful.border_width end if not c._private._user_border_color then @@ -657,8 +664,12 @@ function permissions.update_border(c, context) local tv = beautiful["border_color"..suffix] - if fallback ~= "" and not tv then - tv = beautiful["border_color"..fallback] + if (not tv) and fallback1 ~= "" then + tv = beautiful["border_color"..fallback1] + end + + if (not tv) and (fallback2 ~= "") then + tv = beautiful["border_color"..fallback2] end -- The old theme variable did not have "color" in its name. @@ -688,8 +699,12 @@ function permissions.update_border(c, context) if not c._private._user_opacity then local tv = beautiful["opacity"..suffix] - if fallback ~= "" and not tv then - tv = beautiful["opacity"..fallback] + if fallback1 ~= "" and not tv then + tv = beautiful["opacity"..fallback1] + end + + if fallback2 ~= "" and not tv then + tv = beautiful["opacity"..fallback2] end if tv then diff --git a/lib/awful/placement.lua b/lib/awful/placement.lua index d57b817e9..f25569a69 100644 --- a/lib/awful/placement.lua +++ b/lib/awful/placement.lua @@ -383,7 +383,7 @@ end area_common = function(d, new_geo, ignore_border_width, args) -- The C side expect no arguments, nil isn't valid if new_geo and args.zap_border_width then - d.border_width = 0 + d._border_width = 0 end 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 @@ -1633,7 +1633,7 @@ function placement.restore(d, args) end - d.border_width = memento.border_width + d._border_width = memento.border_width -- Don't use the memento as it would be "destructive", since `x`, `y` -- and `screen` have to be modified. diff --git a/lib/awful/titlebar.lua b/lib/awful/titlebar.lua index fe16719a7..9952a743a 100644 --- a/lib/awful/titlebar.lua +++ b/lib/awful/titlebar.lua @@ -583,6 +583,7 @@ local function new(c, args) bars[position] = { args = args, drawable = ret, + font = args.font or beautiful.titlebar_font, update_colors = update_colors } @@ -683,6 +684,20 @@ local function update_on_signal(c, signal, widget) table.insert(widgets, widget) end +--- Honor the font. +local function draw_title(self, ctx, cr, width, height) + if ctx.position and ctx.client then + local bars = all_titlebars[ctx.client] + local data = bars and bars[ctx.position] + + if data and data.font then + self:set_font(data.font) + end + end + + textbox.draw(self, ctx, cr, width, height) +end + --- Create a new title widget. -- -- A title widget displays the name of a client. @@ -694,6 +709,9 @@ end -- @constructorfct awful.titlebar.widget.titlewidget function titlebar.widget.titlewidget(c) local ret = textbox() + + rawset(ret, "draw", draw_title) + local function update() ret:set_text(c.name or titlebar.fallback_name) end diff --git a/lib/awful/widget/layoutlist.lua b/lib/awful/widget/layoutlist.lua index a9c8ccaca..5fac6cd86 100644 --- a/lib/awful/widget/layoutlist.lua +++ b/lib/awful/widget/layoutlist.lua @@ -245,7 +245,7 @@ local layoutlist = {} --- The space between the layouts. -- @beautiful beautiful.layoutlist_spacing --- @tparam[opt=0] number spacing The spacing between tasks. +-- @tparam[opt=0] number spacing The spacing between layouts. --- The default layoutlist elements shape. -- @beautiful beautiful.layoutlist_shape @@ -314,10 +314,10 @@ function layoutlist:set_base_layout(layout) layout or wibox.layout.fixed.horizontal ) - if self._private.layout.set_spacing then - self._private.layout:set_spacing( - self._private.style.spacing or beautiful.tasklist_spacing or 0 - ) + local spacing = self._private.style.spacing or beautiful.tasklist_spacing + + if self._private.layout.set_spacing and spacing then + self._private.layout:set_spacing(spacing) end assert(self._private.layout.is_widget) @@ -363,7 +363,7 @@ end --- Create a layout list. -- -- @tparam table args --- @tparam widget args.layout The widget layout (not to be confused with client +-- @tparam widget args.base_layout The widget layout (not to be confused with client -- layout). -- @tparam table args.buttons A table with buttons binding to set. -- @tparam[opt=awful.widget.layoutlist.source.for_screen] function args.source A @@ -400,6 +400,8 @@ local function update_common() end local function new(_, args) + args = args or {} + local ret = wibox.widget.base.make_widget(nil, nil, { enable_properties = true, }) @@ -413,11 +415,15 @@ local function new(_, args) reload_cache(ret) - -- Apply all args properties - gtable.crush(ret, args) + -- Apply all args properties. Make sure "set_layout" doesn't override + -- the widget `layout` method. + local l = args.layout + args.layout = nil + gtable.crush(ret, args, false) + args.layout = l if not ret._private.layout then - ret:set_base_layout() + ret:set_base_layout(args.layout) end assert(ret._private.layout) diff --git a/lib/wibox/widget/textbox.lua b/lib/wibox/widget/textbox.lua index 26f6ea2f0..b733f4343 100644 --- a/lib/wibox/widget/textbox.lua +++ b/lib/wibox/widget/textbox.lua @@ -332,12 +332,47 @@ end --- Set a textbox font. -- +-- There is multiple valid font string representation. The most precise is +-- [XFT](https://wiki.archlinux.org/title/X_Logical_Font_Description). It +-- is also possible to use the family name, followed by the face and size +-- such as `Monospace Bold 10`. This script lists the fonts present +-- on your system: +-- +-- #!/usr/bin/env lua +-- +-- local lgi = require("lgi") +-- local pangocairo = lgi.PangoCairo +-- +-- local font_map = pangocairo.font_map_get_default() +-- +-- for k, v in pairs(font_map:list_families()) do +-- print(v:get_name(), "monospace?: "..tostring(v:is_monospace())) +-- for k2, v2 in ipairs(v:list_faces()) do +-- print(" ".. v2:get_face_name()) +-- end +-- end +-- +-- Save this script somewhere on your system, `chmod +x` it and run it. It +-- will list something like: +-- +-- Sans monospace?: false +-- Regular +-- Bold +-- Italic +-- Bold Italic +-- +-- In this case, the font could be `Sans 10` or `Sans Bold Italic 10`. +-- -- @property font -- @tparam string font The font description as string. -- @propemits true false -- @propbeautiful function textbox:set_font(font) + if font == self._private.font then return end + + self._private.font = font + self._private.layout:set_font_description(beautiful.get_font(font)) self:emit_signal("widget::redraw_needed") self:emit_signal("widget::layout_changed") diff --git a/tests/test-client-borders.lua b/tests/test-client-borders.lua new file mode 100644 index 000000000..d9d044e7c --- /dev/null +++ b/tests/test-client-borders.lua @@ -0,0 +1,190 @@ +-- Brute force every possible states and see what blows up. +-- +-- The goal is to detect "other" code paths within the core +-- which have the side effect of accidentally marking the border +-- to be user-specified. When this happens, all theme variables +-- stop working. +-- +-- For example, fullscreen caused an issue because the default +-- request::geometry modified the border_width. +local beautiful = require("beautiful") +local aclient = require("awful.client") +local test_client = require("_client") + +local steps = {} +local used_colors = {} +local state_colors, state_widths = {}, {} +local state_setter = {} +local c = nil + +-- Make sure it uses `beautiful.border_width` (default) again. +local function reset() + client.focus = nil + c.urgent = false + c.maximized = false + c.fullscreen = false + + -- Use the low level API because `floating` has an implicit/explicit + -- mode just like the border. + aclient.property.set(c, "floating", nil) +end + +local function gen_random_color(state) + while true do + local str = "#" + for _=1, 3 do + local part = string.format("%x", math.ceil(math.random() * 255)) + part = #part == 1 and ("0"..part) or part + str = str .. part + end + + if not used_colors[str] then + used_colors[str] = state + return str + end + end +end + +local function check_state(state) + reset() + + if state_setter[state] then + local col = "border_color" .. state + local w = "border_width" .. state + state_setter[state](client.get()[1]) + + assert(c.border_color ~= nil) + assert(c.border_width ~= nil) + assert( + c.border_color == state_colors[col], + "Expected "..state_colors[col].." for "..state.. " but got "..c.border_color.. + " for "..(used_colors[c.border_color] or "nil") + ) + + if not state:find("full") then + assert(c.border_width == state_widths[w]) + else + assert(c.border_width == 0) + end + + assert(not c._private._user_border_width) + assert(not c._private._user_border_color) + end + + return true +end + +local function clear_theme() + for k in pairs(beautiful) do + if k:find("border_") then + beautiful[k] = nil + end + end + + return true +end + +for _, state1 in ipairs {"_fullscreen", "_maximized", "_floating" } do + local color = "border_color" .. state1 + local width = "border_width" .. state1 + + state_widths[width] = math.floor(math.random() * 10) + state_colors[color] = gen_random_color(color) + + for _, state2 in ipairs {"_urgent", "_active", "_new", "_normal" } do + color = "border_color" .. state1 .. state2 + width = "border_width" .. state1 .. state2 + + state_widths[width] = math.floor(math.random() * 10) + state_colors[color] = gen_random_color(color) + end +end + +for _, state in ipairs {"_urgent", "_active", "_new", "_normal" } do + local color = "border_color" .. state + local width = "border_width" .. state + + state_widths[width] = math.floor(math.random() * 10) + state_colors[color] = gen_random_color(color) + beautiful[width] = state_widths[width] + beautiful[color] = state_colors[color] +end + +function state_setter._urgent() + c.urgent = true +end + +function state_setter._fullscreen() + c.fullscreen = true +end + +function state_setter._floating() + c.floating = true +end + +function state_setter._active() + client.focus = c +end + +function state_setter._maximized() + c.maximized = true +end + +function state_setter._normal() + -- no-op +end + +test_client() + +table.insert(steps, function() + c = client.get()[1] + return c and true or nil +end) + +for _, state in ipairs {"_urgent", "_active", "_new", "_normal" } do + table.insert(steps, function() + return check_state(state) + end) +end + +table.insert(steps, clear_theme) + +table.insert(steps, function() + for _, state in ipairs {"_fullscreen", "_maximized", "_floating" } do + local color = "border_color" .. state + local width = "border_width" .. state + + beautiful[width] = state_widths[width] + beautiful[color] = state_colors[color] + end + + return true +end) + +for _, state in ipairs {"_fullscreen", "_maximized", "_floating" } do + table.insert(steps, function() + return check_state(state) + end) +end + +table.insert(steps, clear_theme) + +table.insert(steps, function() + -- Add everything to the theme. + for _, mode in ipairs {state_colors, state_widths} do + for key, value in pairs(mode) do + beautiful[key] = value + end + end + return true +end) + +for _, state1 in ipairs {"_fullscreen", "_maximized", "_floating" } do + for _, state2 in ipairs {"_urgent", "_active", "_new", "_normal" } do + table.insert(steps, function() + return check_state(state1 .. state2) + end) + end +end + +require("_runner").run_steps(steps) diff --git a/tests/test-selection-getter.lua b/tests/test-selection-getter.lua index 9d9193556..afac47034 100644 --- a/tests/test-selection-getter.lua +++ b/tests/test-selection-getter.lua @@ -8,6 +8,8 @@ local lgi = require("lgi") local Gio = lgi.Gio local GdkPixbuf = lgi.GdkPixbuf +local pids = {} + local lua_executable = os.getenv("LUA") if lua_executable == nil or lua_executable == "" then lua_executable = "lua" @@ -53,8 +55,9 @@ runner.run_steps{ -- Clear the clipboard to get to a known state function() - spawn.with_line_callback({ lua_executable, "-e", acquire_and_clear_clipboard }, + local pid = spawn.with_line_callback({ lua_executable, "-e", acquire_and_clear_clipboard }, { exit = function() continue = true end }) + table.insert(pids, pid) return true end, @@ -84,13 +87,15 @@ runner.run_steps{ -- Now set the clipboard to some text continue = false - spawn.with_line_callback({ lua_executable, "-e", acquire_clipboard_text }, + local pid = spawn.with_line_callback({ lua_executable, "-e", acquire_clipboard_text }, { stdout = function(line) assert(line == "initialisation done", "Unexpected line: " .. line) continue = true end }) + table.insert(pids, pid) + return true end, @@ -148,13 +153,15 @@ runner.run_steps{ -- Now set the clipboard to an image continue = false - spawn.with_line_callback({ lua_executable, "-e", acquire_clipboard_pixbuf }, + local pid = spawn.with_line_callback({ lua_executable, "-e", acquire_clipboard_pixbuf }, { stdout = function(line) assert(line == "initialisation done", "Unexpected line: " .. line) continue = true end }) + table.insert(pids, pid) + return true end, @@ -194,6 +201,12 @@ runner.run_steps{ return end + -- There are now "windows", so they have no "clients", however, + -- they talk to X11 and wont exit by themselves and must be killed. + for _, pid in ipairs(pids) do + awesome.kill(pid, 9) + end + return true end, } diff --git a/tests/test-selection-watcher.lua b/tests/test-selection-watcher.lua index 3721a67f8..2e9d6a647 100644 --- a/tests/test-selection-watcher.lua +++ b/tests/test-selection-watcher.lua @@ -3,6 +3,8 @@ local runner = require("_runner") local spawn = require("awful.spawn") +local pids = {} + local lua_executable = os.getenv("LUA") if lua_executable == nil or lua_executable == "" then lua_executable = "lua" @@ -67,8 +69,11 @@ runner.run_steps{ -- Clear the clipboard to get to a known state function() check_state(0, 0) - spawn.with_line_callback({ lua_executable, "-e", acquire_and_clear_clipboard }, + local pid = spawn.with_line_callback({ lua_executable, "-e", acquire_and_clear_clipboard }, { exit = function() continue = true end }) + + table.insert(pids, pid) + return true end, @@ -86,13 +91,15 @@ runner.run_steps{ -- Set the clipboard continue = false - spawn.with_line_callback({ lua_executable, "-e", acquire_clipboard }, + local pid = spawn.with_line_callback({ lua_executable, "-e", acquire_clipboard }, { stdout = function(line) assert(line == "initialisation done", "Unexpected line: " .. line) continue = true end }) + table.insert(pids, pid) + return true end, @@ -105,9 +112,11 @@ runner.run_steps{ -- Now clear the clipboard again continue = false - spawn.with_line_callback({ lua_executable, "-e", acquire_and_clear_clipboard }, + local pid = spawn.with_line_callback({ lua_executable, "-e", acquire_and_clear_clipboard }, { exit = function() continue = true end }) + table.insert(pids, pid) + return true end, @@ -118,6 +127,13 @@ runner.run_steps{ end check_state(2, 1) + + -- There are now "windows", so they have no "clients", however, + -- they talk to X11 and wont exit by themselves and must be killed. + for _, pid in ipairs(pids) do + awesome.kill(pid, 9) + end + return true end } diff --git a/tests/test-titlebar.lua b/tests/test-titlebar.lua index d80f19371..4b0b8d4b5 100644 --- a/tests/test-titlebar.lua +++ b/tests/test-titlebar.lua @@ -3,6 +3,7 @@ local titlebar = require("awful.titlebar") local rules = require("ruled.client") local spawn = require("awful.spawn") local gdebug = require("gears.debug") +local textbox = require("wibox.widget.textbox") local lua_executable = os.getenv("LUA") if lua_executable == nil or lua_executable == "" then @@ -17,10 +18,8 @@ window = Gtk.Window {default_width=100, default_height=100, title='title'} %s window:set_wmclass(class, class) local app = Gtk.Application {} -function app:on_activate() - window.application = self - window:show_all() -end +window:show_all() +Gtk:main{...} app:run {''} ]] local tiny_client = { lua_executable, "-e", string.format(tiny_client_code_template, "") } @@ -30,12 +29,21 @@ window.decorated = false ]]) } +local found_font = nil + -- Use the test client props local dep = gdebug.deprecate gdebug.deprecate = function() end rules.rules = {} gdebug.deprecate = dep +local function kill_client(c) + -- Make sure the process finishes. Just `c:kill()` only + -- closes the window. Adding some handlers to the GTK "app" + -- created some unwanted side effects in the CI. + awesome.kill(c.pid, 9) +end + -- Too bad there's no way to disconnect the rc.lua request::titlebars function local steps = { @@ -69,7 +77,7 @@ local steps = { titlebar.toggle(c, "top") assert(c.height == 100) - c:kill() + kill_client(c) return true end, @@ -100,7 +108,7 @@ local steps = { assert(c.width == 100 and c.height == h) - c:kill() + kill_client(c) return true end, @@ -119,7 +127,7 @@ local steps = { assert(c.width == 100 and c.height > 100) assert(c._request_titlebars_called) - c:kill() + kill_client(c) return true end, @@ -140,7 +148,7 @@ local steps = { assert(c.width == 100 and c.height > 100) assert(c._request_titlebars_called) - c:kill() + kill_client(c) return true end, @@ -161,7 +169,26 @@ local steps = { assert(not c._request_titlebars_called) assert(c.width == 100 and c.height == 100) - c:kill() + function textbox:set_font(value) + found_font = value + end + + local args = {size = 40, font = "sans 10", position = "bottom"} + titlebar(c, args).widget = titlebar.widget.titlewidget(c) + + return true + end, + function() + local c = client.get()[1] + + assert(found_font == "sans 10") + + kill_client(c) + + return true + end, + function() + if #client.get() > 0 then return end return true end,