diff --git a/awesomerc.lua b/awesomerc.lua index 11dcc93ea..7edb28278 100755 --- a/awesomerc.lua +++ b/awesomerc.lua @@ -165,8 +165,7 @@ mytasklist.buttons = awful.util.table.join( awful.client.focus.byidx(-1) end)) -awful.screen.connect_for_each_screen(function(s) - -- Wallpaper +local function set_wallpaper(s) if beautiful.wallpaper then local wallpaper = beautiful.wallpaper -- If wallpaper is a function, call it with the screen @@ -175,6 +174,14 @@ awful.screen.connect_for_each_screen(function(s) end gears.wallpaper.maximized(wallpaper, s, true) end +end + +-- Re-set wallpaper when a screen's geometry changes (e.g. different resolution) +screen.connect_signal("property::geometry", set_wallpaper) + +awful.screen.connect_for_each_screen(function(s) + -- Wallpaper + set_wallpaper(s) -- Each screen has its own tag table. awful.tag({ "1", "2", "3", "4", "5", "6", "7", "8", "9" }, s, awful.layout.layouts[1]) diff --git a/common/luaclass.c b/common/luaclass.c index 1771aaa2f..2f8f2b9cf 100644 --- a/common/luaclass.c +++ b/common/luaclass.c @@ -163,6 +163,32 @@ luaA_class_add_property(lua_class_t *lua_class, }); } +/** Newindex meta function for objects after they were GC'd. + * \param L The Lua VM state. + * \return The number of elements pushed on stack. + */ +static int +luaA_class_newindex_invalid(lua_State *L) +{ + return luaL_error(L, "attempt to index an object that was already garbage collected"); +} + +/** Index meta function for objects after they were GC'd. + * \param L The Lua VM state. + * \return The number of elements pushed on stack. + */ +static int +luaA_class_index_invalid(lua_State *L) +{ + const char *attr = luaL_checkstring(L, 2); + if (A_STREQ(attr, "valid")) + { + lua_pushboolean(L, false); + return 1; + } + return luaA_class_newindex_invalid(L); +} + /** Garbage collect a Lua object. * \param L The Lua VM state. * \return The number of elements pushed on stack. @@ -181,8 +207,13 @@ luaA_class_gc(lua_State *L) class->collector(item); /* Unset its metatable so that e.g. luaA_toudata() will no longer accept * this object. This is needed since other __gc methods can still use this. + * We also make sure that `item.valid == false`. */ lua_newtable(L); + lua_pushcfunction(L, luaA_class_index_invalid); + lua_setfield(L, -2, "__index"); + lua_pushcfunction(L, luaA_class_newindex_invalid); + lua_setfield(L, -2, "__newindex"); lua_setmetatable(L, 1); return 0; } diff --git a/lib/awful/layout/suit/magnifier.lua b/lib/awful/layout/suit/magnifier.lua index 089bbdd08..02226658b 100644 --- a/lib/awful/layout/suit/magnifier.lua +++ b/lib/awful/layout/suit/magnifier.lua @@ -62,13 +62,8 @@ function magnifier.arrange(p) -- Check that the focused window is on the right screen if focus and focus.screen ~= p.screen then focus = nil end - if not focus and #cls > 0 then - focus = cls[1] - fidx = 1 - end - - -- If focused window is not tiled, take the first one which is tiled. - if focus.floating then + -- If no window is focused or focused window is not tiled, take the first tiled one. + if (not focus or focus.floating) and #cls > 0 then focus = cls[1] fidx = 1 end diff --git a/lib/wibox/drawable.lua b/lib/wibox/drawable.lua index 0212efae9..e7df58217 100644 --- a/lib/wibox/drawable.lua +++ b/lib/wibox/drawable.lua @@ -24,7 +24,8 @@ local matrix = require("gears.matrix") local hierarchy = require("wibox.hierarchy") local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1) -local drawables = setmetatable({}, { __mode = 'k' }) +local drawables_draw = setmetatable({}, { __mode = 'k' }) +local drawables_force_complete_repaint = setmetatable({}, { __mode = 'k' }) -- Get the widget context. This should always return the same table (if -- possible), so that our draw and fit caches can work efficiently. @@ -46,20 +47,20 @@ local function get_widget_context(self) screen = s, dpi = dpi, drawable = self, - widget_at = function(_, ...) - self:widget_at(...) - end } for k, v in pairs(self._widget_context_skeleton) do context[k] = v end self._widget_context = context + + -- Give widgets a chance to react to the new context + self._need_complete_repaint = true end return context end local function do_redraw(self) - if type(self.drawable) ~= "drawable" then return end --FIXME See #1070 + if not self.drawable.valid then return end local surf = surface.load_silently(self.drawable.surface, false) -- The surface can be nil if the drawable's parent was already finalized @@ -357,9 +358,18 @@ function drawable.new(d, widget_context_skeleton, drawable_name) ret._need_complete_repaint = true ret:draw() end - drawables[ret._do_complete_repaint] = true + drawables_draw[ret.draw] = true + drawables_force_complete_repaint[ret._do_complete_repaint] = true + + -- Do a full redraw if the surface changes (the new surface has no content yet) d:connect_signal("property::surface", ret._do_complete_repaint) + -- Do a normal redraw when the drawable moves. This will likely do nothing + -- in most cases, but it makes us do a complete repaint when we are moved to + -- a different screen. + d:connect_signal("property::x", ret.draw) + d:connect_signal("property::y", ret.draw) + -- Currently we aren't redrawing on move (signals not connected). -- :set_bg() will later recompute this. ret._redraw_on_move = false @@ -390,6 +400,15 @@ function drawable.new(d, widget_context_skeleton, drawable_name) -- Set up our callbacks for repaints ret._redraw_callback = function(hierar, arg) + -- XXX: lgi will lead us into memory-corruption-land when we use an + -- object after it was GC'd. Try to detect this situation by checking if + -- the drawable is still valid. This is only a weak indication, but it + -- seems to be the best that we can do. The problem is that the drawable + -- could not yet be GC'd, but is pending finalisation, while the + -- cairo.Region below was already GC'd. This would still lead to corruption. + if not ret.drawable.valid then + return + end if ret._widget_hierarchy_callback_arg ~= arg then return end @@ -427,11 +446,21 @@ end -- Redraw all drawables when the wallpaper changes capi.awesome.connect_signal("wallpaper_changed", function() - for k in pairs(drawables) do + for k in pairs(drawables_force_complete_repaint) do k() end end) +-- Give drawables a chance to react to screen changes +local function draw_all() + for k in pairs(drawables_draw) do + k() + end +end +screen.connect_signal("property::geometry", draw_all) +screen.connect_signal("added", draw_all) +screen.connect_signal("removed", draw_all) + return setmetatable(drawable, { __call = function(_, ...) return drawable.new(...) end }) -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/lib/wibox/hierarchy.lua b/lib/wibox/hierarchy.lua index b21885055..ffdc9924a 100644 --- a/lib/wibox/hierarchy.lua +++ b/lib/wibox/hierarchy.lua @@ -23,6 +23,7 @@ local function hierarchy_new(redraw_callback, layout_callback, callback_arg) _matrix_to_device = matrix.identity, _need_update = true, _widget = nil, + _context = nil, _redraw_callback = redraw_callback, _layout_callback = layout_callback, _callback_arg = callback_arg, @@ -73,6 +74,7 @@ end local hierarchy_update function hierarchy_update(self, context, widget, width, height, region, matrix_to_parent, matrix_to_device) if (not self._need_update) and self._widget == widget and + self._context == context and self._size.width == width and self._size.height == height and matrix.equals(self._matrix, matrix_to_parent) and matrix.equals(self._matrix_to_device, matrix_to_device) then @@ -101,6 +103,7 @@ function hierarchy_update(self, context, widget, width, height, region, matrix_t -- Save the arguments we need to save self._widget = widget + self._context = context self._size.width = width self._size.height = height self._matrix = matrix_to_parent diff --git a/lib/wibox/init.lua b/lib/wibox/init.lua index 7d3735781..a078534e9 100644 --- a/lib/wibox/init.lua +++ b/lib/wibox/init.lua @@ -177,9 +177,6 @@ local function new(args) setup_signals(ret) ret.draw = ret._drawable.draw - ret.widget_at = function(_, widget, x, y, width, height) - return ret._drawable:widget_at(widget, x, y, width, height) - end -- Set the default background ret:set_bg(args.bg or beautiful.bg_normal) diff --git a/objects/client.c b/objects/client.c index e561aae2b..bdb372662 100644 --- a/objects/client.c +++ b/objects/client.c @@ -1138,7 +1138,7 @@ client_border_refresh(void) static void client_geometry_refresh(void) { - client_ignore_enterleave_events(); + bool ignored_enterleave = false; foreach(_c, globalconf.clients) { client_t *c = *_c; @@ -1164,6 +1164,11 @@ client_geometry_refresh(void) && AREA_EQUAL(real_geometry, c->x11_client_geometry)) continue; + if (!ignored_enterleave) { + client_ignore_enterleave_events(); + ignored_enterleave = true; + } + xcb_configure_window(globalconf.connection, c->frame_window, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, (uint32_t[]) { geometry.x, geometry.y, geometry.width, geometry.height }); @@ -1177,7 +1182,8 @@ client_geometry_refresh(void) /* ICCCM 4.2.3 says something else, but Java always needs this... */ client_send_configure(c); } - client_restore_enterleave_events(); + if (ignored_enterleave) + client_restore_enterleave_events(); } void diff --git a/tests/test-use-after-gc.lua b/tests/test-use-after-gc.lua new file mode 100644 index 000000000..29220568d --- /dev/null +++ b/tests/test-use-after-gc.lua @@ -0,0 +1,31 @@ +-- Test behaviour of C objects after they were finalized + +local runner = require("_runner") + +local done +do + local obj + local func = function() + assert(obj.valid == false) + assert(not pcall(function() + print(obj.visible) + end)) + assert(not pcall(function() + obj.visible = true + end)) + done = true + end + if _VERSION >= "Lua 5.2" then + setmetatable({}, { __gc = func }) + else + local newproxy = newproxy -- luacheck: globals newproxy + getmetatable(newproxy(true)).__gc = func + end + obj = drawin({}) +end +collectgarbage("collect") +assert(done) + +runner.run_steps({ function() return true end }) + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80