diff --git a/lib/awful/autofocus.lua b/lib/awful/autofocus.lua index f1117f15..878896c9 100644 --- a/lib/awful/autofocus.lua +++ b/lib/awful/autofocus.lua @@ -18,6 +18,7 @@ local timer = require("gears.timer") -- -- @param obj An object that should have a .screen property. local function check_focus(obj) + if not obj.screen.valid then return end -- When no visible client has the focus... if not client.focus or not client.focus:isvisible() then local c = aclient.focus.history.get(screen[obj.screen], 0, aclient.focus.filter) @@ -39,7 +40,7 @@ end -- @param tag A tag object local function check_focus_tag(t) local s = t.screen - if not s then return end + if (not s) or (not s.valid) then return end s = screen[s] check_focus({ screen = s }) if client.focus and screen[client.focus.screen] ~= s then diff --git a/lib/awful/tag.lua b/lib/awful/tag.lua index 4777ea6a..64e95d01 100644 --- a/lib/awful/tag.lua +++ b/lib/awful/tag.lua @@ -250,12 +250,14 @@ end -- @see awful.tag.find_fallback -- @tparam[opt=awful.tag.find_fallback()] tag fallback_tag Tag to assign -- stickied tags to. +-- @tparam[opt=false] boolean force Move even non-sticky clients to the fallback +-- tag. -- @return Returns true if the tag is successfully deleted, nil otherwise. -- If there are no clients exclusively on this tag then delete it. Any -- stickied clients are assigned to the optional 'fallback_tag'. -- If after deleting the tag there is no selected tag, try and restore from -- history or select the first tag on the screen. -function tag.object.delete(self, fallback_tag) +function tag.object.delete(self, fallback_tag, force) -- abort if the taf isn't currently activated if not self.activated then return end @@ -283,8 +285,7 @@ function tag.object.delete(self, fallback_tag) -- If a client has only this tag, or stickied clients with -- nowhere to go, abort. - if (not c.sticky and nb_tags == 1) or - (c.sticky and fallback_tag == nil) then + if (not c.sticky and nb_tags == 1 and not force) then return -- If a client has multiple tags, then do not move it to fallback elseif nb_tags < 2 then @@ -294,6 +295,7 @@ function tag.object.delete(self, fallback_tag) -- delete the tag data.tags[self].screen = nil + data.tags[self] = nil self.activated = false -- Update all indexes @@ -1350,11 +1352,45 @@ capi.tag.add_signal("property::urgent") capi.tag.add_signal("property::urgent_count") capi.tag.add_signal("property::volatile") +capi.tag.add_signal("request::screen") +capi.tag.add_signal("removal-pending") capi.screen.add_signal("tag::history::update") capi.screen.connect_signal("tag::history::update", tag.history.update) +capi.screen.connect_signal("removed", function(s) + -- First give other code a chance to move the tag to another screen + for _, t in pairs(s.tags) do + t:emit_signal("request::screen") + end + -- Everything that's left: Tell everyone that these tags go away (other code + -- could e.g. save clients) + for _, t in pairs(s.tags) do + t:emit_signal("removal-pending") + end + -- Give other code yet another change to save clients + for _, c in pairs(capi.client.get(s)) do + c:emit_signal("request::tag", nil, { reason = "screen-removed" }) + end + -- Then force all clients left to go somewhere random + local fallback = nil + for other_screen in capi.screen do + if #other_screen.tags > 0 then + fallback = other_screen.tags[1] + break + end + end + for _, t in pairs(s.tags) do + t:delete(fallback, true) + end + -- If any tag survived until now, forcefully get rid of it + for _, t in pairs(s.tags) do + t.activated = false + data.tags[t] = nil + end +end) + function tag.mt:__call(...) return tag.new(...) end diff --git a/lib/awful/widget/taglist.lua b/lib/awful/widget/taglist.lua index 97ec5215..df2b4eae 100644 --- a/lib/awful/widget/taglist.lua +++ b/lib/awful/widget/taglist.lua @@ -168,7 +168,9 @@ function taglist.new(screen, filter, buttons, style, update_function, base_widge -- Add a delayed callback for the first update. if not queued_update[screen] then timer.delayed_call(function() - taglist_update(screen, w, buttons, filter, data, style, uf) + if screen.valid then + taglist_update(screen, w, buttons, filter, data, style, uf) + end queued_update[screen] = false end) queued_update[screen] = true @@ -203,6 +205,9 @@ function taglist.new(screen, filter, buttons, style, update_function, base_widge capi.client.connect_signal("tagged", uc) capi.client.connect_signal("untagged", uc) capi.client.connect_signal("unmanage", uc) + capi.screen.connect_signal("removed", function(s) + instances[get_screen(s)] = nil + end) end w._do_taglist_update() local list = instances[screen] diff --git a/lib/awful/widget/tasklist.lua b/lib/awful/widget/tasklist.lua index 70a49207..09ec420b 100644 --- a/lib/awful/widget/tasklist.lua +++ b/lib/awful/widget/tasklist.lua @@ -181,7 +181,9 @@ function tasklist.new(screen, filter, buttons, style, update_function, base_widg if not queued_update then timer.delayed_call(function() queued_update = false - tasklist_update(screen, w, buttons, filter, data, style, uf) + if screen.valid then + tasklist_update(screen, w, buttons, filter, data, style, uf) + end end) queued_update = true end @@ -201,7 +203,9 @@ function tasklist.new(screen, filter, buttons, style, update_function, base_widg end local function u() for s in pairs(instances) do - us(s) + if s.valid then + us(s) + end end end @@ -238,6 +242,9 @@ function tasklist.new(screen, filter, buttons, style, update_function, base_widg capi.client.connect_signal("list", u) capi.client.connect_signal("focus", u) capi.client.connect_signal("unfocus", u) + capi.screen.connect_signal("removed", function(s) + instances[get_screen(s)] = nil + end) end w._do_tasklist_update() local list = instances[screen] diff --git a/lib/naughty/core.lua b/lib/naughty/core.lua index 4f04f67f..be2f46ae 100644 --- a/lib/naughty/core.lua +++ b/lib/naughty/core.lua @@ -154,6 +154,16 @@ screen.connect_for_each_screen(function(s) } end) +capi.screen.connect_signal("removed", function(scr) + -- Destroy all notifications on this screen + for _, list in pairs(naughty.notifications[scr]) do + while #list > 0 do + naughty.destroy(list[1]) + end + end + naughty.notifications[scr] = nil +end) + --- Notification state function naughty.is_suspended() return suspended diff --git a/objects/screen.c b/objects/screen.c index f8fdfa01..f42bb640 100644 --- a/objects/screen.c +++ b/objects/screen.c @@ -1020,6 +1020,57 @@ luaA_screen_count(lua_State *L) return 1; } +/** Add a fake screen. + * @tparam integer x X-coordinate for screen. + * @tparam integer y Y-coordinate for screen. + * @tparam integer width width for screen. + * @tparam integer height height for screen. + * @return The new screen. + * @function fake_add + */ +static int +luaA_screen_fake_add(lua_State *L) +{ + int x = luaL_checkinteger(L, 1); + int y = luaL_checkinteger(L, 2); + int width = luaL_checkinteger(L, 3); + int height = luaL_checkinteger(L, 4); + screen_t *s; + + s = screen_add(L, &globalconf.screens); + s->geometry.x = x; + s->geometry.y = y; + s->geometry.width = width; + s->geometry.height = height; + s->valid = true; + luaA_object_push(L, s); + luaA_object_emit_signal(L, -1, "added", 0); + + return 1; +} + +/** Remove a screen. + * @function fake_remove. + */ +static int +luaA_screen_fake_remove(lua_State *L) +{ + screen_t *s = luaA_checkudata(L, 1, &screen_class); + int idx = screen_get_index(s) - 1; + if (idx < 0) + /* WTF? */ + return 0; + + screen_array_take(&globalconf.screens, idx); + luaA_object_push(L, s); + screen_removed(L, -1); + lua_pop(L, 1); + luaA_object_unref(L, s); + s->valid = false; + + return 0; +} + void screen_class_setup(lua_State *L) { @@ -1030,6 +1081,7 @@ screen_class_setup(lua_State *L) { "__index", luaA_screen_module_index }, { "__newindex", luaA_default_newindex }, { "__call", luaA_screen_module_call }, + { "fake_add", luaA_screen_fake_add }, { NULL, NULL } }; @@ -1037,6 +1089,7 @@ screen_class_setup(lua_State *L) { LUA_OBJECT_META(screen) LUA_CLASS_META + { "fake_remove", luaA_screen_fake_remove }, { NULL, NULL }, }; diff --git a/tests/_client.lua b/tests/_client.lua index 1275584b..9a94fd4b 100644 --- a/tests/_client.lua +++ b/tests/_client.lua @@ -4,29 +4,26 @@ local spawn = require("awful.spawn") -- It is used to test the `awful.rules` return function(class, title, use_sn) + class = class or 'test_app' 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 class = '",class,"';", "local window = Gtk.Window {", " default_width = 100,", " default_height = 100,", + " on_destroy = Gtk.main_quit,", " 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 {''}" + "window:show_all();", + "Gtk:main{...}" }} return spawn(cmd, use_sn) end + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/tests/examples/shims/screen.lua b/tests/examples/shims/screen.lua index a0d4eb6b..ca36a063 100644 --- a/tests/examples/shims/screen.lua +++ b/tests/examples/shims/screen.lua @@ -100,6 +100,8 @@ end screen._add_screen {width=320, height=240} screen.add_signal("property::workarea") +screen.add_signal("added") +screen.add_signal("removed") return screen diff --git a/tests/test-screen-changes.lua b/tests/test-screen-changes.lua new file mode 100644 index 00000000..efc687c3 --- /dev/null +++ b/tests/test-screen-changes.lua @@ -0,0 +1,64 @@ +-- Tests for screen additions & removals + +local runner = require("_runner") +local test_client = require("_client") +local naughty = require("naughty") + +local real_screen = screen[1] +local fake_screen = screen.fake_add(50, 50, 500, 500) +local test_client1, test_client2 + +local steps = { + -- Step 1: Set up some clients to experiment with and assign them as needed + function(count) + if count == 1 then -- Setup. + test_client() + test_client() + end + local cls = client.get() + if #cls == 2 then + test_client1, test_client2 = cls[1], cls[2] + test_client1.screen = real_screen + test_client2.screen = fake_screen + + -- Display a notification on the screen-to-be-removed + naughty.notify{ text = "test", screen = fake_screen } + + return true + end + end, + + -- Step 2: Say goodbye to the screen + function() + fake_screen:fake_remove() + + -- TODO: This is a hack to make the test work, how to do this so that it + -- also works "in the wild"? + mypromptbox[fake_screen] = nil + mylayoutbox[fake_screen] = nil + mytaglist[fake_screen] = nil + mytasklist[fake_screen] = nil + mywibox[fake_screen] = nil + + -- Wrap in a weak table to allow garbage collection + fake_screen = setmetatable({ fake_screen }, { __mode = "v" }) + + return true + end, + + -- Step 3: Everything should now be on the main screen, the old screen + -- should be garbage collectable + function() + assert(test_client1.screen == real_screen, test_client1.screen) + assert(test_client2.screen == real_screen, test_client2.screen) + + collectgarbage("collect") + if #fake_screen == 0 then + return true + end + end, +} + +runner.run_steps(steps) + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80