diff --git a/lib/awful/widget/init.lua b/lib/awful/widget/init.lua index 02400a182..b10f21c79 100644 --- a/lib/awful/widget/init.lua +++ b/lib/awful/widget/init.lua @@ -19,6 +19,7 @@ return textclock = require("awful.widget.textclock"); keyboardlayout = require("awful.widget.keyboardlayout"); watch = require("awful.widget.watch"); + only_on_screen = require("awful.widget.only_on_screen"); } -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/lib/awful/widget/only_on_screen.lua b/lib/awful/widget/only_on_screen.lua new file mode 100644 index 000000000..0e9479933 --- /dev/null +++ b/lib/awful/widget/only_on_screen.lua @@ -0,0 +1,147 @@ +--------------------------------------------------------------------------- +-- +-- A container that makes a widget display only on a specified screen. +-- +-- @author Uli Schlachter +-- @copyright 2017 Uli Schlachter +-- @classmod awful.widget.only_on_screen +--------------------------------------------------------------------------- + +local type = type +local pairs = pairs +local setmetatable = setmetatable +local base = require("wibox.widget.base") +local util = require("awful.util") +local capi = { + screen = screen, + awesome = awesome +} + +local only_on_screen = { mt = {} } + +local instances = setmetatable({}, { __mode = "k" }) + +local function should_display_on(self, s) + if not self._private.widget then + return false + end + local success, own_s = pcall(function() + return capi.screen[self._private.screen] + end) + return success and own_s == s +end + +-- Layout this layout +function only_on_screen:layout(context, ...) + if not should_display_on(self, context.screen) then return end + return { base.place_widget_at(self._private.widget, 0, 0, ...) } +end + +-- Fit this layout into the given area +function only_on_screen:fit(context, ...) + if not should_display_on(self, context.screen) then + return 0, 0 + end + return base.fit_widget(self, context, self._private.widget, ...) +end + +--- The widget to be displayed +-- @property widget +-- @tparam widget widget The widget + +function only_on_screen:set_widget(widget) + if widget then + base.check_widget(widget) + end + self._private.widget = widget + self:emit_signal("widget::layout_changed") +end + +function only_on_screen:get_widget() + return self._private.widget +end + +--- Get the number of children element +-- @treturn table The children +function only_on_screen:get_children() + return {self._private.widget} +end + +--- Replace the layout children +-- This layout only accept one children, all others will be ignored +-- @tparam table children A table composed of valid widgets +function only_on_screen:set_children(children) + self:set_widget(children[1]) +end + +--- The screen to display on. Can be a screen object, a screen index, a screen +-- name ("VGA1") or the string "primary" for the primary screen. +-- @property screen +-- @tparam screen|string|integer screen The screen. + +function only_on_screen:set_screen(s) + self._private.screen = s + self:emit_signal("widget::layout_changed") +end + +function only_on_screen:get_screen() + return self._private.screen +end + +--- Returns a new only_on_screen container. +-- This widget makes some other widget visible on just some screens. Use +-- `:set_widget()` to set the widget and `:set_screen()` to set the screen. +-- @param[opt] widget The widget to display. +-- @param[opt] s The screen to display on. +-- @treturn table A new only_on_screen container +-- @function wibox.container.only_on_screen +local function new(widget, s) + local ret = base.make_widget(nil, nil, {enable_properties = true}) + + util.table.crush(ret, only_on_screen, true) + + ret:set_widget(widget) + ret:set_screen(s or "primary") + + instances[ret] = true + + return ret +end + +function only_on_screen.mt:__call(...) + return new(...) +end + +-- Handle lots of cases where a screen changes and thus this widget jumps around + +capi.screen.connect_signal("primary_changed", function() + for widget in pairs(instances) do + if widget._private.widget and widget._private.screen == "primary" then + widget:emit_signal("widget::layout_changed") + end + end +end) + +capi.screen.connect_signal("list", function() + for widget in pairs(instances) do + if widget._private.widget and type(widget._private.screen) == "number" then + widget:emit_signal("widget::layout_changed") + end + end +end) + +capi.screen.connect_signal("property::outputs", function() + for widget in pairs(instances) do + if widget._private.widget and type(widget._private.screen) == "string" then + widget:emit_signal("widget::layout_changed") + end + end +end) + +--@DOC_widget_COMMON@ + +--@DOC_object_COMMON@ + +return setmetatable(only_on_screen, only_on_screen.mt) + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/tests/test-awful-widget-only_on_screen.lua b/tests/test-awful-widget-only_on_screen.lua new file mode 100644 index 000000000..3c0cf8b61 --- /dev/null +++ b/tests/test-awful-widget-only_on_screen.lua @@ -0,0 +1,155 @@ +-- Test that awful.widget.only_on_screen works correctly + +local runner = require("_runner") +local wibox = require("wibox") +local awful = require("awful") + +-- Make sure we have at least two screens to test this on +screen.fake_add(-100, -100, 50, 50) +assert(screen.count() == 2) + +-- Each screen gets a wibox displaying our only_on_screen widget +local inner_widget = wibox.widget.textbox("This is only visible on some screen") +local only_widget = awful.widget.only_on_screen() +awful.screen.connect_for_each_screen(function(s) + local geo = s.geometry + -- Just so that :fit() also gets code coverage, we use an align layout + local widget = wibox.layout.align.horizontal(only_widget) + s.testwibox = wibox{ + x = geo.x + geo.width / 4, + y = geo.y + geo.height / 4, + width = geo.width / 2, + height = geo.height / 2, + widget = widget, + visible = true, + } +end) + +-- Check if the inner_widget is visible on the given screen +local function widget_visible_on(s) + for _, entry in ipairs(s.testwibox:find_widgets(1, 1)) do + if entry.widget == inner_widget then + return true + end + end + return false +end + +-- Test the widget's constructor +do + local widget = awful.widget.only_on_screen() + assert(widget.screen == "primary") + assert(widget.widget == nil) + + widget = awful.widget.only_on_screen(inner_widget, 42) + assert(widget.screen == 42) + assert(widget.widget == inner_widget) +end + +local steps = {} + +-- Test that the widget "does nothing" when no widget is set +table.insert(steps, function() + -- Idle step so that the testwibox added above is drawn + return true +end) +table.insert(steps, function() + for s in screen do + local res = s.testwibox:find_widgets(1, 1) + assert(#res == 1, #res) + end + return true +end) + +-- Test that the widget by default is displayed on the primary screen +table.insert(steps, function() + only_widget.widget = inner_widget + return true +end) +table.insert(steps, function() + assert(only_widget.screen == "primary", only_widget.screen) + for s in screen do + if s == screen.primary then + assert(widget_visible_on(s)) + else + assert(not widget_visible_on(s)) + end + end + return true +end) + +-- Test setting the screen +for s in screen do + local index = s.index + local function check_result() + for t in screen do + assert(widget_visible_on(t) == (t.index == index)) + end + return true + end + + -- Test by screen object + table.insert(steps, function() + only_widget.screen = s + return true + end) + table.insert(steps, check_result) + + -- Test by index + table.insert(steps, function() + only_widget.screen = index + return true + end) + table.insert(steps, check_result) +end + +-- Test that the widget is correctly updated when screen "2" is removed +table.insert(steps, function() + only_widget.screen = 2 + return true +end) +table.insert(steps, function() + screen[2]:fake_remove() + return true +end) +table.insert(steps, function() + for s in screen do + assert(not widget_visible_on(s)) + end + screen.fake_add(-100, -100, 50, 50) + return true +end) +table.insert(steps, function() + for s in screen do + assert(widget_visible_on(s) == (s.index == 2)) + end + return true +end) + +-- Test what happens when the primary screen is removed +table.insert(steps, function() + only_widget.screen = "primary" + return true +end) +table.insert(steps, function() + local s, s2 = screen.primary, screen[2] + assert(s.index == 1) + s:fake_remove() + assert(screen.primary == s2) + return true +end) +table.insert(steps, function() + assert(only_widget.screen == "primary", only_widget.screen) + for s in screen do + if s == screen.primary then + assert(widget_visible_on(s)) + else + assert(not widget_visible_on(s)) + end + end + return true +end) + +runner.run_steps(steps) + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80