summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorache <ache@ache.one>2017-03-13 23:17:19 +0100
committerache <ache@ache.one>2017-03-13 23:17:19 +0100
commit22d656903563f75678f3634964731ccf93355dfd (patch)
treee3cb6279d95c9764093072d5e946566ea6533799 /lib
Init commit
Diffstat (limited to 'lib')
-rw-r--r--lib/awful/autofocus.lua64
-rw-r--r--lib/awful/button.lua61
-rw-r--r--lib/awful/client.lua1238
-rw-r--r--lib/awful/client/focus.lua215
-rw-r--r--lib/awful/client/shape.lua93
-rw-r--r--lib/awful/client/urgent.lua88
-rw-r--r--lib/awful/completion.lua201
-rw-r--r--lib/awful/dbus.lua19
-rw-r--r--lib/awful/ewmh.lua295
-rw-r--r--lib/awful/hotkeys_popup/init.lua17
-rw-r--r--lib/awful/hotkeys_popup/keys/init.lua15
-rw-r--r--lib/awful/hotkeys_popup/keys/vim.lua173
-rw-r--r--lib/awful/hotkeys_popup/widget.lua482
-rw-r--r--lib/awful/init.lua64
-rw-r--r--lib/awful/key.lua136
-rw-r--r--lib/awful/keygrabber.lua96
-rw-r--r--lib/awful/layout/init.lua323
-rw-r--r--lib/awful/layout/suit/corner.lua204
-rw-r--r--lib/awful/layout/suit/fair.lua108
-rw-r--r--lib/awful/layout/suit/floating.lua112
-rw-r--r--lib/awful/layout/suit/init.lua19
-rw-r--r--lib/awful/layout/suit/magnifier.lua147
-rw-r--r--lib/awful/layout/suit/max.lua61
-rw-r--r--lib/awful/layout/suit/spiral.lua89
-rw-r--r--lib/awful/layout/suit/tile.lua348
-rw-r--r--lib/awful/menu.lua723
-rw-r--r--lib/awful/mouse/drag_to_tag.lua58
-rw-r--r--lib/awful/mouse/init.lua437
-rw-r--r--lib/awful/mouse/resize.lua229
-rw-r--r--lib/awful/mouse/snap.lua266
-rw-r--r--lib/awful/placement.lua1694
-rw-r--r--lib/awful/prompt.lua777
-rw-r--r--lib/awful/remote.lua47
-rw-r--r--lib/awful/rules.lua545
-rw-r--r--lib/awful/screen.lua477
-rw-r--r--lib/awful/spawn.lua421
-rw-r--r--lib/awful/startup_notification.lua53
-rw-r--r--lib/awful/tag.lua1505
-rw-r--r--lib/awful/titlebar.lua509
-rw-r--r--lib/awful/tooltip.lua616
-rw-r--r--lib/awful/util.lua588
-rw-r--r--lib/awful/wibar.lua603
-rw-r--r--lib/awful/wibox.lua12
-rw-r--r--lib/awful/widget/button.lua263
-rw-r--r--lib/awful/widget/common.lua119
-rw-r--r--lib/awful/widget/graph.lua16
-rw-r--r--lib/awful/widget/init.lua24
-rw-r--r--lib/awful/widget/keyboardlayout.lua308
-rw-r--r--lib/awful/widget/launcher.lua41
-rw-r--r--lib/awful/widget/layoutbox.lua73
-rw-r--r--lib/awful/widget/progressbar.lua16
-rw-r--r--lib/awful/widget/prompt.lua64
-rw-r--r--lib/awful/widget/taglist.lua452
-rw-r--r--lib/awful/widget/tasklist.lua573
-rw-r--r--lib/awful/widget/textclock.lua16
-rw-r--r--lib/awful/widget/watch.lua91
-rw-r--r--lib/beautiful.lua7
-rw-r--r--lib/beautiful/init.lua217
-rw-r--r--lib/beautiful/xresources.lua117
-rw-r--r--lib/gears/cache.lua51
-rw-r--r--lib/gears/color.lua346
-rw-r--r--lib/gears/debug.lua78
-rw-r--r--lib/gears/geometry.lua240
-rw-r--r--lib/gears/init.lua23
-rw-r--r--lib/gears/matrix.lua219
-rw-r--r--lib/gears/object.lua285
-rw-r--r--lib/gears/object/properties.lua88
-rw-r--r--lib/gears/protected_call.lua57
-rw-r--r--lib/gears/shape.lua785
-rw-r--r--lib/gears/surface.lua252
-rw-r--r--lib/gears/timer.lua187
-rw-r--r--lib/gears/wallpaper.lua221
-rw-r--r--lib/menubar/icon_theme.lua251
-rw-r--r--lib/menubar/index_theme.lua164
-rw-r--r--lib/menubar/init.lua480
-rw-r--r--lib/menubar/menu_gen.lua141
-rw-r--r--lib/menubar/utils.lua316
-rw-r--r--lib/naughty.lua7
-rw-r--r--lib/naughty/core.lua688
-rw-r--r--lib/naughty/dbus.lua261
-rw-r--r--lib/naughty/init.lua14
m---------lib/shifty0
-rw-r--r--lib/shifty.lua1138
-rw-r--r--lib/wibox/container/arcchart.lua566
-rw-r--r--lib/wibox/container/background.lua626
-rw-r--r--lib/wibox/container/constraint.lua371
-rw-r--r--lib/wibox/container/init.lua21
-rw-r--r--lib/wibox/container/margin.lua419
-rw-r--r--lib/wibox/container/mirror.lua340
-rw-r--r--lib/wibox/container/radialprogressbar.lua481
-rw-r--r--lib/wibox/container/rotate.lua384
-rw-r--r--lib/wibox/container/scroll.lua716
-rw-r--r--lib/wibox/drawable.lua489
-rw-r--r--lib/wibox/hierarchy.lua333
-rw-r--r--lib/wibox/init.lua479
-rw-r--r--lib/wibox/layout/align.lua526
-rw-r--r--lib/wibox/layout/constraint.lua17
-rw-r--r--lib/wibox/layout/fixed.lua585
-rw-r--r--lib/wibox/layout/flex.lua429
-rw-r--r--lib/wibox/layout/init.lua23
-rw-r--r--lib/wibox/layout/margin.lua17
-rw-r--r--lib/wibox/layout/mirror.lua17
-rw-r--r--lib/wibox/layout/ratio.lua583
-rw-r--r--lib/wibox/layout/rotate.lua17
-rw-r--r--lib/wibox/layout/scroll.lua16
-rw-r--r--lib/wibox/layout/stack.lua402
-rw-r--r--lib/wibox/widget/background.lua16
-rw-r--r--lib/wibox/widget/base.lua694
-rw-r--r--lib/wibox/widget/checkbox.lua530
-rw-r--r--lib/wibox/widget/graph.lua575
-rw-r--r--lib/wibox/widget/imagebox.lua395
-rw-r--r--lib/wibox/widget/init.lua22
-rw-r--r--lib/wibox/widget/piechart.lua467
-rw-r--r--lib/wibox/widget/progressbar.lua754
-rw-r--r--lib/wibox/widget/slider.lua709
-rw-r--r--lib/wibox/widget/systray.lua186
-rw-r--r--lib/wibox/widget/textbox.lua507
-rw-r--r--lib/wibox/widget/textclock.lua254
118 files changed, 35883 insertions, 0 deletions
diff --git a/lib/awful/autofocus.lua b/lib/awful/autofocus.lua
new file mode 100644
index 0000000..5fcd220
--- /dev/null
+++ b/lib/awful/autofocus.lua
@@ -0,0 +1,64 @@
+---------------------------------------------------------------------------
+--- Autofocus functions.
+--
+-- When loaded, this module makes sure that there's always a client that will
+-- have focus on events such as tag switching, client unmanaging, etc.
+--
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2009 Julien Danjou
+-- @module awful.autofocus
+---------------------------------------------------------------------------
+
+local client = client
+local aclient = require("awful.client")
+local timer = require("gears.timer")
+
+--- Give focus when clients appear/disappear.
+--
+-- @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)
+ if c then
+ c:emit_signal("request::activate", "autofocus.check_focus",
+ {raise=false})
+ end
+ end
+end
+
+--- Check client focus (delayed).
+-- @param obj An object that should have a .screen property.
+local function check_focus_delayed(obj)
+ timer.delayed_call(check_focus, {screen = obj.screen})
+end
+
+--- Give focus on tag selection change.
+--
+-- @param tag A tag object
+local function check_focus_tag(t)
+ local s = t.screen
+ 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
+ local c = aclient.focus.history.get(s, 0, aclient.focus.filter)
+ if c then
+ c:emit_signal("request::activate", "autofocus.check_focus_tag",
+ {raise=false})
+ end
+ end
+end
+
+tag.connect_signal("property::selected", function (t)
+ timer.delayed_call(check_focus_tag, t)
+end)
+client.connect_signal("unmanage", check_focus_delayed)
+client.connect_signal("tagged", check_focus_delayed)
+client.connect_signal("untagged", check_focus_delayed)
+client.connect_signal("property::hidden", check_focus_delayed)
+client.connect_signal("property::minimized", check_focus_delayed)
+client.connect_signal("property::sticky", check_focus_delayed)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/button.lua b/lib/awful/button.lua
new file mode 100644
index 0000000..b50664d
--- /dev/null
+++ b/lib/awful/button.lua
@@ -0,0 +1,61 @@
+---------------------------------------------------------------------------
+--- Create easily new buttons objects ignoring certain modifiers.
+--
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2009 Julien Danjou
+-- @classmod awful.button
+---------------------------------------------------------------------------
+
+-- Grab environment we need
+local setmetatable = setmetatable
+local ipairs = ipairs
+local capi = { button = button }
+local util = require("awful.util")
+
+local button = { mt = {} }
+
+--- Modifiers to ignore.
+--
+-- By default this is initialized as `{ "Lock", "Mod2" }`
+-- so the `Caps Lock` or `Num Lock` modifier are not taking into account by awesome
+-- when pressing keys.
+--
+-- @table ignore_modifiers
+local ignore_modifiers = { "Lock", "Mod2" }
+
+--- Create a new button to use as binding.
+--
+-- This function is useful to create several buttons from one, because it will use
+-- the ignore_modifier variable to create more button with or without the ignored
+-- modifiers activated.
+--
+-- For example if you want to ignore CapsLock in your buttonbinding (which is
+-- ignored by default by this function), creating button binding with this function
+-- will return 2 button objects: one with CapsLock on, and the other one with
+-- CapsLock off.
+--
+-- @see button
+-- @treturn table A table with one or several button objects.
+function button.new(mod, _button, press, release)
+ local ret = {}
+ local subsets = util.subsets(ignore_modifiers)
+ for _, set in ipairs(subsets) do
+ ret[#ret + 1] = capi.button({ modifiers = util.table.join(mod, set),
+ button = _button })
+ if press then
+ ret[#ret]:connect_signal("press", function(_, ...) press(...) end)
+ end
+ if release then
+ ret[#ret]:connect_signal("release", function (_, ...) release(...) end)
+ end
+ end
+ return ret
+end
+
+function button.mt:__call(...)
+ return button.new(...)
+end
+
+return setmetatable(button, button.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/client.lua b/lib/awful/client.lua
new file mode 100644
index 0000000..809c055
--- /dev/null
+++ b/lib/awful/client.lua
@@ -0,0 +1,1238 @@
+---------------------------------------------------------------------------
+--- Useful client manipulation functions.
+--
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2008 Julien Danjou
+-- @module client
+---------------------------------------------------------------------------
+
+-- Grab environment we need
+local util = require("awful.util")
+local spawn = require("awful.spawn")
+local object = require("gears.object")
+local grect = require("gears.geometry").rectangle
+local pairs = pairs
+local type = type
+local ipairs = ipairs
+local table = table
+local math = math
+local setmetatable = setmetatable
+local capi =
+{
+ client = client,
+ mouse = mouse,
+ screen = screen,
+ awesome = awesome,
+}
+
+local function get_screen(s)
+ return s and capi.screen[s]
+end
+
+-- We use a metatable to prevent circular dependency loops.
+local screen
+do
+ screen = setmetatable({}, {
+ __index = function(_, k)
+ screen = require("awful.screen")
+ return screen[k]
+ end,
+ __newindex = error -- Just to be sure in case anything ever does this
+ })
+end
+local client = {object={}}
+
+-- Private data
+client.data = {}
+client.data.marked = {}
+client.data.persistent_properties_registered = {} -- keys are names of persistent properties, value always true
+
+-- Functions
+client.urgent = require("awful.client.urgent")
+client.swap = {}
+client.floating = {}
+client.dockable = {}
+client.property = {}
+client.shape = require("awful.client.shape")
+client.focus = require("awful.client.focus")
+
+--- Jump to the given client.
+-- Takes care of focussing the screen, the right tag, etc.
+--
+-- @deprecated awful.client.jumpto
+-- @see client.jump_to
+-- @client c the client to jump to
+-- @tparam bool|function merge If true then merge tags (select the client's
+-- first tag additionally) when the client is not visible.
+-- If it is a function, it will be called with the client and its first
+-- tag as arguments.
+function client.jumpto(c, merge)
+ util.deprecate("Use c:jump_to(merge) instead of awful.client.jumpto")
+ client.object.jump_to(c, merge)
+end
+
+--- Jump to the given client.
+-- Takes care of focussing the screen, the right tag, etc.
+--
+-- @function client.jump_to
+-- @tparam bool|function merge If true then merge tags (select the client's
+-- first tag additionally) when the client is not visible.
+-- If it is a function, it will be called with the client and its first
+-- tag as arguments.
+function client.object.jump_to(self, merge)
+ local s = get_screen(screen.focused())
+ -- focus the screen
+ if s ~= get_screen(self.screen) then
+ screen.focus(self.screen)
+ end
+
+ self.minimized = false
+
+ -- Try to make client visible, this also covers e.g. sticky.
+ if not self:isvisible() then
+ local t = self.first_tag
+ if merge then
+ if type(merge) == "function" then
+ merge(self, t)
+ elseif t then
+ t.selected = true
+ end
+ elseif t then
+ t:view_only()
+ end
+ end
+
+ self:emit_signal("request::activate", "client.jumpto", {raise=true})
+end
+
+--- Get visible clients from a screen.
+--
+-- @deprecated awful.client.visible
+-- @see screen.clients
+-- @tparam[opt] integer|screen s The screen, or nil for all screens.
+-- @tparam[opt=false] boolean stacked Use stacking order? (top to bottom)
+-- @treturn table A table with all visible clients.
+function client.visible(s, stacked)
+ local cls = capi.client.get(s, stacked)
+ local vcls = {}
+ for _, c in pairs(cls) do
+ if c:isvisible() then
+ table.insert(vcls, c)
+ end
+ end
+ return vcls
+end
+
+--- Get visible and tiled clients
+--
+-- @deprecated awful.client.tiled
+-- @see screen.tiled_clients
+-- @tparam integer|screen s The screen, or nil for all screens.
+-- @tparam[opt=false] boolean stacked Use stacking order? (top to bottom)
+-- @treturn table A table with all visible and tiled clients.
+function client.tiled(s, stacked)
+ local clients = client.visible(s, stacked)
+ local tclients = {}
+ -- Remove floating clients
+ for _, c in pairs(clients) do
+ if not client.object.get_floating(c)
+ and not c.fullscreen
+ and not c.maximized_vertical
+ and not c.maximized_horizontal then
+ table.insert(tclients, c)
+ end
+ end
+ return tclients
+end
+
+--- Get a client by its relative index to another client.
+-- If no client is passed, the focused client will be used.
+--
+-- @function awful.client.next
+-- @tparam int i The index. Use 1 to get the next, -1 to get the previous.
+-- @client[opt] sel The client.
+-- @tparam[opt=false] boolean stacked Use stacking order? (top to bottom)
+-- @return A client, or nil if no client is available.
+--
+-- @usage -- focus the next window in the index
+-- awful.client.next(1)
+-- -- focus the previous
+-- awful.client.next(-1)
+function client.next(i, sel, stacked)
+ -- Get currently focused client
+ sel = sel or capi.client.focus
+ if sel then
+ -- Get all visible clients
+ local cls = client.visible(sel.screen, stacked)
+ local fcls = {}
+ -- Remove all non-normal clients
+ for _, c in ipairs(cls) do
+ if client.focus.filter(c) or c == sel then
+ table.insert(fcls, c)
+ end
+ end
+ cls = fcls
+ -- Loop upon each client
+ for idx, c in ipairs(cls) do
+ if c == sel then
+ -- Cycle
+ return cls[util.cycle(#cls, idx + i)]
+ end
+ end
+ end
+end
+
+--- Swap a client with another client in the given direction.
+-- @function awful.client.swap.bydirection
+-- @tparam string dir The direction, can be either "up", "down", "left" or "right".
+-- @client[opt=focused] c The client.
+-- @tparam[opt=false] boolean stacked Use stacking order? (top to bottom)
+function client.swap.bydirection(dir, c, stacked)
+ local sel = c or capi.client.focus
+ if sel then
+ local cltbl = client.visible(sel.screen, stacked)
+ local geomtbl = {}
+ for i,cl in ipairs(cltbl) do
+ geomtbl[i] = cl:geometry()
+ end
+ local target = grect.get_in_direction(dir, geomtbl, sel:geometry())
+
+ -- If we found a client to swap with, then go for it
+ if target then
+ cltbl[target]:swap(sel)
+ end
+ end
+end
+
+--- Swap a client with another client in the given direction. Swaps across screens.
+-- @function awful.client.swap.global_bydirection
+-- @param dir The direction, can be either "up", "down", "left" or "right".
+-- @client[opt] sel The client.
+function client.swap.global_bydirection(dir, sel)
+ sel = sel or capi.client.focus
+ local scr = get_screen(sel and sel.screen or screen.focused())
+
+ if sel then
+ -- move focus
+ client.focus.global_bydirection(dir, sel)
+ local c = capi.client.focus
+
+ -- swapping inside a screen
+ if get_screen(sel.screen) == get_screen(c.screen) and sel ~= c then
+ c:swap(sel)
+
+ -- swapping to an empty screen
+ elseif get_screen(sel.screen) ~= get_screen(c.screen) and sel == c then
+ sel:move_to_screen(screen.focused())
+
+ -- swapping to a nonempty screen
+ elseif get_screen(sel.screen) ~= get_screen(c.screen) and sel ~= c then
+ sel:move_to_screen(c.screen)
+ c:move_to_screen(scr)
+ end
+
+ screen.focus(sel.screen)
+ sel:emit_signal("request::activate", "client.swap.global_bydirection",
+ {raise=false})
+ end
+end
+
+--- Swap a client by its relative index.
+-- @function awful.client.swap.byidx
+-- @param i The index.
+-- @client[opt] c The client, otherwise focused one is used.
+function client.swap.byidx(i, c)
+ local sel = c or capi.client.focus
+ local target = client.next(i, sel)
+ if target then
+ target:swap(sel)
+ end
+end
+
+--- Cycle clients.
+--
+-- @function awful.client.cycle
+-- @param clockwise True to cycle clients clockwise.
+-- @param[opt] s The screen where to cycle clients.
+-- @tparam[opt=false] boolean stacked Use stacking order? (top to bottom)
+function client.cycle(clockwise, s, stacked)
+ s = s or screen.focused()
+ local cls = client.visible(s, stacked)
+ -- We can't rotate without at least 2 clients, buddy.
+ if #cls >= 2 then
+ local c = table.remove(cls, 1)
+ if clockwise then
+ for i = #cls, 1, -1 do
+ c:swap(cls[i])
+ end
+ else
+ for _, rc in pairs(cls) do
+ c:swap(rc)
+ end
+ end
+ end
+end
+
+--- Get the master window.
+--
+-- @legacylayout awful.client.getmaster
+-- @screen_or_idx[opt=awful.screen.focused()] s The screen.
+-- @return The master window.
+function client.getmaster(s)
+ s = s or screen.focused()
+ return client.visible(s)[1]
+end
+
+--- Set the client as master: put it at the beginning of other windows.
+--
+-- @legacylayout awful.client.setmaster
+-- @client c The window to set as master.
+function client.setmaster(c)
+ local cls = util.table.reverse(capi.client.get(c.screen))
+ for _, v in pairs(cls) do
+ c:swap(v)
+ end
+end
+
+--- Set the client as slave: put it at the end of other windows.
+-- @legacylayout awful.client.setslave
+-- @client c The window to set as slave.
+function client.setslave(c)
+ local cls = capi.client.get(c.screen)
+ for _, v in pairs(cls) do
+ c:swap(v)
+ end
+end
+
+--- Move/resize a client relative to current coordinates.
+-- @deprecated awful.client.moveresize
+-- @param x The relative x coordinate.
+-- @param y The relative y coordinate.
+-- @param w The relative width.
+-- @param h The relative height.
+-- @client[opt] c The client, otherwise focused one is used.
+-- @see client.relative_move
+function client.moveresize(x, y, w, h, c)
+ util.deprecate("Use c:relative_move(x, y, w, h) instead of awful.client.moveresize")
+ client.object.relative_move(c or capi.client.focus, x, y, w, h)
+end
+
+--- Move/resize a client relative to current coordinates.
+-- @function client.relative_move
+-- @see geometry
+-- @tparam[opt=c.x] number x The relative x coordinate.
+-- @tparam[opt=c.y] number y The relative y coordinate.
+-- @tparam[opt=c.width] number w The relative width.
+-- @tparam[opt=c.height] number h The relative height.
+function client.object.relative_move(self, x, y, w, h)
+ local geometry = self:geometry()
+ geometry['x'] = geometry['x'] + x
+ geometry['y'] = geometry['y'] + y
+ geometry['width'] = geometry['width'] + w
+ geometry['height'] = geometry['height'] + h
+ self:geometry(geometry)
+end
+
+--- Move a client to a tag.
+-- @deprecated awful.client.movetotag
+-- @param target The tag to move the client to.
+-- @client[opt] c The client to move, otherwise the focused one is used.
+-- @see client.move_to_tag
+function client.movetotag(target, c)
+ util.deprecate("Use c:move_to_tag(target) instead of awful.client.movetotag")
+ client.object.move_to_tag(c or capi.client.focus, target)
+end
+
+--- Move a client to a tag.
+-- @function client.move_to_tag
+-- @tparam tag target The tag to move the client to.
+function client.object.move_to_tag(self, target)
+ local s = target.screen
+ if self and s then
+ if self == capi.client.focus then
+ self:emit_signal("request::activate", "client.movetotag", {raise=true})
+ end
+ -- Set client on the same screen as the tag.
+ self.screen = s
+ self:tags({ target })
+ end
+end
+
+--- Toggle a tag on a client.
+-- @deprecated awful.client.toggletag
+-- @param target The tag to toggle.
+-- @client[opt] c The client to toggle, otherwise the focused one is used.
+-- @see client.toggle_tag
+function client.toggletag(target, c)
+ util.deprecate("Use c:toggle_tag(target) instead of awful.client.toggletag")
+ client.object.toggle_tag(c or capi.client.focus, target)
+end
+
+--- Toggle a tag on a client.
+-- @function client.toggle_tag
+-- @tparam tag target The tag to move the client to.
+function client.object.toggle_tag(self, target)
+ -- Check that tag and client screen are identical
+ if self and get_screen(self.screen) == get_screen(target.screen) then
+ local tags = self:tags()
+ local index = nil;
+ for i, v in ipairs(tags) do
+ if v == target then
+ index = i
+ break
+ end
+ end
+ if index then
+ -- If it's the only tag for the window, stop.
+ if #tags == 1 then return end
+ tags[index] = nil
+ else
+ tags[#tags + 1] = target
+ end
+ self:tags(tags)
+ end
+end
+
+--- Move a client to a screen. Default is next screen, cycling.
+-- @deprecated awful.client.movetoscreen
+-- @client c The client to move.
+-- @param s The screen, default to current + 1.
+-- @see screen
+-- @see client.move_to_screen
+function client.movetoscreen(c, s)
+ util.deprecate("Use c:move_to_screen(s) instead of awful.client.movetoscreen")
+ client.object.move_to_screen(c or capi.client.focus, s)
+end
+
+--- Move a client to a screen. Default is next screen, cycling.
+-- @function client.move_to_screen
+-- @tparam[opt=c.screen.index+1] screen s The screen, default to current + 1.
+-- @see screen
+-- @see request::activate
+function client.object.move_to_screen(self, s)
+ if self then
+ local sc = capi.screen.count()
+ if not s then
+ s = self.screen.index + 1
+ end
+ if type(s) == "number" then
+ if s > sc then s = 1 elseif s < 1 then s = sc end
+ end
+ s = get_screen(s)
+ if get_screen(self.screen) ~= s then
+ local sel_is_focused = self == capi.client.focus
+ self.screen = s
+ screen.focus(s)
+
+ if sel_is_focused then
+ self:emit_signal("request::activate", "client.movetoscreen",
+ {raise=true})
+ end
+ end
+ end
+end
+
+--- Tag a client with the set of current tags.
+-- @function client.to_selected_tags
+-- @see screen.selected_tags
+function client.object.to_selected_tags(self)
+ local tags = {}
+
+ for _, t in ipairs(self:tags()) do
+ if get_screen(t.screen) == get_screen(self.screen) then
+ table.insert(tags, t)
+ end
+ end
+
+ if self.screen then
+ if #tags == 0 then
+ tags = self.screen.selected_tags
+ end
+
+ if #tags == 0 then
+ tags = self.screen.tags
+ end
+ end
+
+ if #tags ~= 0 then
+ self:tags(tags)
+ end
+end
+
+--- If a client is marked or not.
+--
+-- **Signal:**
+--
+-- * *marked* (for legacy reasons, use `property::marked`)
+-- * *unmarked* (for legacy reasons, use `property::marked`)
+-- * *property::marked*
+--
+-- @property marked
+-- @param boolean
+
+--- The border color when the client is focused.
+--
+-- @beautiful beautiful.border_marked
+-- @param string
+--
+
+function client.object.set_marked(self, value)
+ local is_marked = self.marked
+
+ if value == false and is_marked then
+ for k, v in pairs(client.data.marked) do
+ if self == v then
+ table.remove(client.data.marked, k)
+ end
+ end
+ self:emit_signal("unmarked")
+ elseif not is_marked and value then
+ self:emit_signal("marked")
+ table.insert(client.data.marked, self)
+ end
+
+ client.property.set(self, "marked", value)
+end
+
+function client.object.get_marked(self)
+ return client.property.get(self, "marked")
+end
+
+--- Mark a client, and then call 'marked' hook.
+-- @deprecated awful.client.mark
+-- @client c The client to mark, the focused one if not specified.
+function client.mark(c)
+ util.deprecate("Use c.marked = true instead of awful.client.mark")
+ client.object.set_marked(c or capi.client.focus, true)
+end
+
+--- Unmark a client and then call 'unmarked' hook.
+-- @deprecated awful.client.unmark
+-- @client c The client to unmark, or the focused one if not specified.
+function client.unmark(c)
+ util.deprecate("Use c.marked = false instead of awful.client.unmark")
+ client.object.set_marked(c or capi.client.focus, false)
+end
+
+--- Check if a client is marked.
+-- @deprecated awful.client.ismarked
+-- @client c The client to check, or the focused one otherwise.
+function client.ismarked(c)
+ util.deprecate("Use c.marked instead of awful.client.ismarked")
+ return client.object.get_marked(c or capi.client.focus)
+end
+
+--- Toggle a client as marked.
+-- @deprecated awful.client.togglemarked
+-- @client c The client to toggle mark.
+function client.togglemarked(c)
+ util.deprecate("Use c.marked = not c.marked instead of awful.client.togglemarked")
+ c = c or capi.client.focus
+ if c then
+ c.marked = not c.marked
+ end
+end
+
+--- Return the marked clients and empty the marked table.
+-- @function awful.client.getmarked
+-- @return A table with all marked clients.
+function client.getmarked()
+ local copy = util.table.clone(client.data.marked, false)
+
+ for _, v in pairs(copy) do
+ client.property.set(v, "marked", false)
+ v:emit_signal("unmarked")
+ end
+
+ client.data.marked = {}
+
+ return copy
+end
+
+--- Set a client floating state, overriding auto-detection.
+-- Floating client are not handled by tiling layouts.
+-- @deprecated awful.client.floating.set
+-- @client c A client.
+-- @param s True or false.
+function client.floating.set(c, s)
+ util.deprecate("Use c.floating = true instead of awful.client.floating.set")
+ client.object.set_floating(c, s)
+end
+
+-- Set a client floating state, overriding auto-detection.
+-- Floating client are not handled by tiling layouts.
+-- @client c A client.
+-- @param s True or false.
+function client.object.set_floating(c, s)
+ c = c or capi.client.focus
+ if c and client.property.get(c, "floating") ~= s then
+ client.property.set(c, "floating", s)
+ local scr = c.screen
+ if s == true then
+ c:geometry(client.property.get(c, "floating_geometry"))
+ end
+ c.screen = scr
+ end
+end
+
+local function store_floating_geometry(c)
+ if client.object.get_floating(c) then
+ client.property.set(c, "floating_geometry", c:geometry())
+ end
+end
+
+-- Store the initial client geometry.
+capi.client.connect_signal("new", function(cl)
+ local function store_init_geometry(c)
+ client.property.set(c, "floating_geometry", c:geometry())
+ c:disconnect_signal("property::border_width", store_init_geometry)
+ end
+ cl:connect_signal("property::border_width", store_init_geometry)
+end)
+
+capi.client.connect_signal("property::geometry", store_floating_geometry)
+
+--- Return if a client has a fixed size or not.
+-- This function is deprecated, use `c.is_fixed`
+-- @client c The client.
+-- @deprecated awful.client.isfixed
+-- @see is_fixed
+-- @see size_hints_honor
+function client.isfixed(c)
+ util.deprecate("Use c.is_fixed instead of awful.client.isfixed")
+ c = c or capi.client.focus
+ return client.object.is_fixed(c)
+end
+
+--- Return if a client has a fixed size or not.
+--
+-- **Signal:**
+--
+-- * *property::is_fixed*
+--
+-- This property is read only.
+-- @property is_fixed
+-- @param boolean The floating state
+-- @see size_hints
+-- @see size_hints_honor
+
+function client.object.is_fixed(c)
+ if not c then return end
+ local h = c.size_hints
+ if h.min_width and h.max_width
+ and h.max_height and h.min_height
+ and h.min_width > 0 and h.max_width > 0
+ and h.max_height > 0 and h.min_height > 0
+ and h.min_width == h.max_width
+ and h.min_height == h.max_height then
+ return true
+ end
+ return false
+end
+
+--- Get a client floating state.
+-- @client c A client.
+-- @see floating
+-- @deprecated awful.client.floating.get
+-- @return True or false. Note that some windows might be floating even if you
+-- did not set them manually. For example, windows with a type different than
+-- normal.
+function client.floating.get(c)
+ util.deprecate("Use c.floating instead of awful.client.floating.get")
+ return client.object.get_floating(c)
+end
+
+--- The client floating state.
+-- If the client is part of the tiled layout or free floating.
+--
+-- Note that some windows might be floating even if you
+-- did not set them manually. For example, windows with a type different than
+-- normal.
+--
+-- **Signal:**
+--
+-- * *property::floating*
+--
+-- @property floating
+-- @param boolean The floating state
+
+function client.object.get_floating(c)
+ c = c or capi.client.focus
+ if c then
+ local value = client.property.get(c, "floating")
+ if value ~= nil then
+ return value
+ end
+ if c.type ~= "normal"
+ or c.fullscreen
+ or c.maximized_vertical
+ or c.maximized_horizontal
+ or client.object.is_fixed(c) then
+ return true
+ end
+ return false
+ end
+end
+
+--- Toggle the floating state of a client between 'auto' and 'true'.
+-- Use `c.floating = not c.floating`
+-- @deprecated awful.client.floating.toggle
+-- @client c A client.
+-- @see floating
+function client.floating.toggle(c)
+ c = c or capi.client.focus
+ -- If it has been set to floating
+ client.object.set_floating(c, not client.object.get_floating(c))
+end
+
+-- Remove the floating information on a client.
+-- @client c The client.
+function client.floating.delete(c)
+ client.object.set_floating(c, nil)
+end
+
+--- The x coordinates.
+--
+-- **Signal:**
+--
+-- * *property::x*
+--
+-- @property x
+-- @param integer
+
+--- The y coordinates.
+--
+-- **Signal:**
+--
+-- * *property::y*
+--
+-- @property y
+-- @param integer
+
+--- The width of the wibox.
+--
+-- **Signal:**
+--
+-- * *property::width*
+--
+-- @property width
+-- @param width
+
+--- The height of the wibox.
+--
+-- **Signal:**
+--
+-- * *property::height*
+--
+-- @property height
+-- @param height
+
+-- Add the geometry helpers to match the wibox API
+for _, v in ipairs {"x", "y", "width", "height"} do
+ client.object["get_"..v] = function(c)
+ return c:geometry()[v]
+ end
+
+ client.object["set_"..v] = function(c, value)
+ return c:geometry({[v] = value})
+ end
+end
+
+
+--- Restore (=unminimize) a random client.
+-- @function awful.client.restore
+-- @param s The screen to use.
+-- @return The restored client if some client was restored, otherwise nil.
+function client.restore(s)
+ s = s or screen.focused()
+ local cls = capi.client.get(s)
+ local tags = s.selected_tags
+ for _, c in pairs(cls) do
+ local ctags = c:tags()
+ if c.minimized then
+ for _, t in ipairs(tags) do
+ if util.table.hasitem(ctags, t) then
+ c.minimized = false
+ return c
+ end
+ end
+ end
+ end
+ return nil
+end
+
+--- Normalize a set of numbers to 1
+-- @param set the set of numbers to normalize
+-- @param num the number of numbers to normalize
+local function normalize(set, num)
+ num = num or #set
+ local total = 0
+ if num then
+ for i = 1,num do
+ total = total + set[i]
+ end
+ for i = 1,num do
+ set[i] = set[i] / total
+ end
+ else
+ for _,v in ipairs(set) do
+ total = total + v
+ end
+
+ for i,v in ipairs(set) do
+ set[i] = v / total
+ end
+ end
+end
+
+--- Calculate a client's column number, index in that column, and
+-- number of visible clients in this column.
+--
+-- @legacylayout awful.client.idx
+-- @client c the client
+-- @return col the column number
+-- @return idx index of the client in the column
+-- @return num the number of visible clients in the column
+function client.idx(c)
+ c = c or capi.client.focus
+ if not c then return end
+
+ -- Only check the tiled clients, the others un irrelevant
+ local clients = client.tiled(c.screen)
+ local idx = nil
+ for k, cl in ipairs(clients) do
+ if cl == c then
+ idx = k
+ break
+ end
+ end
+
+ local t = c.screen.selected_tag
+ local nmaster = t.master_count
+
+ -- This will happen for floating or maximized clients
+ if not idx then return nil end
+
+ if idx <= nmaster then
+ return {idx = idx, col=0, num=nmaster}
+ end
+ local nother = #clients - nmaster
+ idx = idx - nmaster
+
+ -- rather than regenerate the column number we can calculate it
+ -- based on the how the tiling algorithm places clients we calculate
+ -- the column, we could easily use the for loop in the program but we can
+ -- calculate it.
+ local ncol = t.column_count
+ -- minimum number of clients per column
+ local percol = math.floor(nother / ncol)
+ -- number of columns with an extra client
+ local overcol = math.fmod(nother, ncol)
+ -- number of columns filled with [percol] clients
+ local regcol = ncol - overcol
+
+ local col = math.floor( (idx - 1) / percol) + 1
+ if col > regcol then
+ -- col = math.floor( (idx - (percol*regcol) - 1) / (percol + 1) ) + regcol + 1
+ -- simplified
+ col = math.floor( (idx + regcol + percol) / (percol+1) )
+ -- calculate the index in the column
+ idx = idx - percol*regcol - (col - regcol - 1) * (percol+1)
+ percol = percol+1
+ else
+ idx = idx - percol*(col-1)
+ end
+
+ return {idx = idx, col=col, num=percol}
+end
+
+
+--- Set the window factor of a client
+--
+-- @legacylayout awful.client.setwfact
+-- @param wfact the window factor value
+-- @client c the client
+function client.setwfact(wfact, c)
+ -- get the currently selected window
+ c = c or capi.client.focus
+ if not c or not c:isvisible() then return end
+
+ local w = client.idx(c)
+
+ if not w then return end
+
+ local t = c.screen.selected_tag
+
+ -- n is the number of windows currently visible for which we have to be concerned with the properties
+ local data = t.windowfact or {}
+ local colfact = data[w.col]
+
+ local need_normalize = colfact ~= nil
+
+ if not need_normalize then
+ colfact = {}
+ end
+
+ colfact[w.idx] = wfact
+
+ if not need_normalize then
+ t:emit_signal("property::windowfact")
+ return
+ end
+
+ local rest = 1-wfact
+
+ -- calculate the current denominator
+ local total = 0
+ for i = 1,w.num do
+ if i ~= w.idx then
+ total = total + colfact[i]
+ end
+ end
+
+ -- normalize the windows
+ for i = 1,w.num do
+ if i ~= w.idx then
+ colfact[i] = (colfact[i] * rest) / total
+ end
+ end
+
+ t:emit_signal("property::windowfact")
+end
+
+--- Change window factor of a client.
+--
+-- @legacylayout awful.client.incwfact
+-- @tparam number add Amount to increase/decrease the client's window factor.
+-- Should be between `-current_window_factor` and something close to
+-- infinite. The normalisation then ensures that the sum of all factors is 1.
+-- @client c the client
+function client.incwfact(add, c)
+ c = c or capi.client.focus
+ if not c then return end
+
+ local t = c.screen.selected_tag
+ local w = client.idx(c)
+ local data = t.windowfact or {}
+ local colfact = data[w.col] or {}
+ local curr = colfact[w.idx] or 1
+ colfact[w.idx] = curr + add
+
+ -- keep our ratios normalized
+ normalize(colfact, w.num)
+
+ t:emit_signal("property::windowfact")
+end
+
+--- Get a client dockable state.
+--
+-- @client c A client.
+-- @return True or false. Note that some windows might be dockable even if you
+-- did not set them manually. For example, windows with a type "utility",
+-- "toolbar" or "dock"
+-- @deprecated awful.client.dockable.get
+function client.dockable.get(c)
+ util.deprecate("Use c.dockable instead of awful.client.dockable.get")
+ return client.object.get_dockable(c)
+end
+
+--- If the client is dockable.
+-- A dockable client is an application confined to the edge of the screen. The
+-- space it occupy is substracted from the `screen.workarea`.
+--
+-- **Signal:**
+--
+-- * *property::dockable*
+--
+-- @property dockable
+-- @param boolean The dockable state
+
+function client.object.get_dockable(c)
+ local value = client.property.get(c, "dockable")
+
+ -- Some sane defaults
+ if value == nil then
+ if (c.type == "utility" or c.type == "toolbar" or c.type == "dock") then
+ value = true
+ else
+ value = false
+ end
+ end
+
+ return value
+end
+
+--- Set a client dockable state, overriding auto-detection.
+-- With this enabled you can dock windows by moving them from the center
+-- to the edge of the workarea.
+--
+-- @client c A client.
+-- @param value True or false.
+-- @deprecated awful.client.dockable.set
+function client.dockable.set(c, value)
+ util.deprecate("Use c.dockable = value instead of awful.client.dockable.set")
+ client.property.set(c, "dockable", value)
+end
+
+--- Get a client property.
+--
+-- This method is deprecated. It is now possible to use `c.value` directly.
+--
+-- @client c The client.
+-- @param prop The property name.
+-- @return The property.
+-- @deprecated awful.client.property.get
+function client.property.get(c, prop)
+ if not c.data._persistent_properties_loaded then
+ c.data._persistent_properties_loaded = true
+ for p in pairs(client.data.persistent_properties_registered) do
+ local value = c:get_xproperty("awful.client.property." .. p)
+ if value ~= nil then
+ client.property.set(c, p, value)
+ end
+ end
+ end
+ if c.data.awful_client_properties then
+ return c.data.awful_client_properties[prop]
+ end
+end
+
+--- Set a client property.
+--
+-- This method is deprecated. It is now possible to use `c.value = value`
+-- directly.
+--
+-- @client c The client.
+-- @param prop The property name.
+-- @param value The value.
+-- @deprecated awful.client.property.set
+function client.property.set(c, prop, value)
+ if not c.data.awful_client_properties then
+ c.data.awful_client_properties = {}
+ end
+ if c.data.awful_client_properties[prop] ~= value then
+ if client.data.persistent_properties_registered[prop] then
+ c:set_xproperty("awful.client.property." .. prop, value)
+ end
+ c.data.awful_client_properties[prop] = value
+ c:emit_signal("property::" .. prop)
+ end
+end
+
+--- Set a client property to be persistent across restarts (via X properties).
+--
+-- @function awful.client.property.persist
+-- @param prop The property name.
+-- @param kind The type (used for register_xproperty).
+-- One of "string", "number" or "boolean".
+function client.property.persist(prop, kind)
+ local xprop = "awful.client.property." .. prop
+ capi.awesome.register_xproperty(xprop, kind)
+ client.data.persistent_properties_registered[prop] = true
+
+ -- Make already-set properties persistent
+ for c in pairs(capi.client.get()) do
+ if c.data.awful_client_properties and c.data.awful_client_properties[prop] ~= nil then
+ c:set_xproperty(xprop, c.data.awful_client_properties[prop])
+ end
+ end
+end
+
+---
+-- Returns an iterator to cycle through, starting from the client in focus or
+-- the given index, all clients that match a given criteria.
+--
+-- @param filter a function that returns true to indicate a positive match
+-- @param start what index to start iterating from. Defaults to using the
+-- index of the currently focused client.
+-- @param s which screen to use. nil means all screens.
+--
+-- @function awful.client.iterate
+-- @usage -- un-minimize all urxvt instances
+-- local urxvt = function (c)
+-- return awful.rules.match(c, {class = "URxvt"})
+-- end
+--
+-- for c in awful.client.iterate(urxvt) do
+-- c.minimized = false
+-- end
+function client.iterate(filter, start, s)
+ local clients = capi.client.get(s)
+ local focused = capi.client.focus
+ start = start or util.table.hasitem(clients, focused)
+ return util.table.iterate(clients, filter, start)
+end
+
+--- Switch to a client matching the given condition if running, else spawn it.
+-- If multiple clients match the given condition then the next one is
+-- focussed.
+--
+-- @param cmd the command to execute
+-- @param matcher a function that returns true to indicate a matching client
+-- @tparam bool|function merge If true then merge tags (select the client's
+-- first tag additionally) when the client is not visible.
+-- If it is a function, it will be called with the client as argument.
+--
+-- @function awful.client.run_or_raise
+-- @usage -- run or raise urxvt (perhaps, with tabs) on modkey + semicolon
+-- awful.key({ modkey, }, 'semicolon', function ()
+-- local matcher = function (c)
+-- return awful.rules.match(c, {class = 'URxvt'})
+-- end
+-- awful.client.run_or_raise('urxvt', matcher)
+-- end);
+function client.run_or_raise(cmd, matcher, merge)
+ local clients = capi.client.get()
+ local findex = util.table.hasitem(clients, capi.client.focus) or 1
+ local start = util.cycle(#clients, findex + 1)
+
+ local c = client.iterate(matcher, start)()
+ if c then
+ c:jump_to(merge)
+ else
+ -- client not found, spawn it
+ spawn(cmd)
+ end
+end
+
+--- Get a matching transient_for client (if any).
+-- @deprecated awful.client.get_transient_for_matching
+-- @see client.get_transient_for_matching
+-- @client c The client.
+-- @tparam function matcher A function that should return true, if
+-- a matching parent client is found.
+-- @treturn client.client|nil The matching parent client or nil.
+function client.get_transient_for_matching(c, matcher)
+ util.deprecate("Use c:get_transient_for_matching(matcher) instead of"..
+ "awful.client.get_transient_for_matching")
+
+ return client.object.get_transient_for_matching(c, matcher)
+end
+
+--- Get a matching transient_for client (if any).
+-- @function client.get_transient_for_matching
+-- @tparam function matcher A function that should return true, if
+-- a matching parent client is found.
+-- @treturn client.client|nil The matching parent client or nil.
+function client.object.get_transient_for_matching(self, matcher)
+ local tc = self.transient_for
+ while tc do
+ if matcher(tc) then
+ return tc
+ end
+ tc = tc.transient_for
+ end
+ return nil
+end
+
+--- Is a client transient for another one?
+-- @deprecated awful.client.is_transient_for
+-- @see client.is_transient_for
+-- @client c The child client (having transient_for).
+-- @client c2 The parent client to check.
+-- @treturn client.client|nil The parent client or nil.
+function client.is_transient_for(c, c2)
+ util.deprecate("Use c:is_transient_for(c2) instead of"..
+ "awful.client.is_transient_for")
+ return client.object.is_transient_for(c, c2)
+end
+
+--- Is a client transient for another one?
+-- @function client.is_transient_for
+-- @client c2 The parent client to check.
+-- @treturn client.client|nil The parent client or nil.
+function client.object.is_transient_for(self, c2)
+ local tc = self
+ while tc.transient_for do
+ if tc.transient_for == c2 then
+ return tc
+ end
+ tc = tc.transient_for
+ end
+ return nil
+end
+
+-- Register standards signals
+
+--- The last geometry when client was floating.
+-- @signal property::floating_geometry
+
+--- 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.
+
+--- The client marked signal (deprecated).
+-- @signal .marked
+
+--- The client unmarked signal (deprecated).
+-- @signal unmarked
+
+-- Add clients during startup to focus history.
+-- This used to happen through ewmh.activate, but that only handles visible
+-- clients now.
+capi.client.connect_signal("manage", function (c)
+ if awesome.startup then
+ client.focus.history.add(c)
+ end
+end)
+capi.client.connect_signal("unmanage", client.focus.history.delete)
+
+capi.client.connect_signal("unmanage", client.floating.delete)
+
+-- Connect to "focus" signal, and allow to disable tracking.
+do
+ local disabled_count = 1
+ --- Disable history tracking.
+ --
+ -- See `awful.client.focus.history.enable_tracking` to enable it again.
+ -- @treturn int The internal value of `disabled_count` (calls to this
+ -- function without calling `awful.client.focus.history.enable_tracking`).
+ -- @function awful.client.focus.history.disable_tracking
+ function client.focus.history.disable_tracking()
+ disabled_count = disabled_count + 1
+ if disabled_count == 1 then
+ capi.client.disconnect_signal("focus", client.focus.history.add)
+ end
+ return disabled_count
+ end
+
+ --- Enable history tracking.
+ --
+ -- This is the default, but can be disabled
+ -- through `awful.client.focus.history.disable_tracking`.
+ -- @treturn boolean True if history tracking has been enabled.
+ -- @function awful.client.focus.history.enable_tracking
+ function client.focus.history.enable_tracking()
+ assert(disabled_count > 0)
+ disabled_count = disabled_count - 1
+ if disabled_count == 0 then
+ capi.client.connect_signal("focus", client.focus.history.add)
+ end
+ return disabled_count == 0
+ end
+
+ --- Is history tracking enabled?
+ -- @treturn bool True if history tracking is enabled.
+ -- @treturn int The number of times that tracking has been disabled.
+ -- @function awful.client.focus.history.is_enabled
+ function client.focus.history.is_enabled()
+ return disabled_count == 0, disabled_count
+ end
+end
+client.focus.history.enable_tracking()
+
+-- Register persistent properties
+client.property.persist("floating", "boolean")
+
+-- Extend the luaobject
+object.properties(capi.client, {
+ getter_class = client.object,
+ setter_class = client.object,
+ getter_fallback = client.property.get,
+ setter_fallback = client.property.set,
+})
+
+return client
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/client/focus.lua b/lib/awful/client/focus.lua
new file mode 100644
index 0000000..a8b04f2
--- /dev/null
+++ b/lib/awful/client/focus.lua
@@ -0,0 +1,215 @@
+---------------------------------------------------------------------------
+--- Keep track of the focused clients.
+--
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2008 Julien Danjou
+-- @submodule client
+---------------------------------------------------------------------------
+local grect = require("gears.geometry").rectangle
+
+local capi =
+{
+ screen = screen,
+ client = client,
+}
+
+-- We use a metatable to prevent circular dependency loops.
+local screen
+do
+ screen = setmetatable({}, {
+ __index = function(_, k)
+ screen = require("awful.screen")
+ return screen[k]
+ end,
+ __newindex = error -- Just to be sure in case anything ever does this
+ })
+end
+
+local client
+do
+ client = setmetatable({}, {
+ __index = function(_, k)
+ client = require("awful.client")
+ return client[k]
+ end,
+ __newindex = error -- Just to be sure in case anything ever does this
+ })
+end
+
+local focus = {history = {list = {}}}
+
+local function get_screen(s)
+ return s and capi.screen[s]
+end
+
+--- Remove a client from the focus history
+--
+-- @client c The client that must be removed.
+-- @function awful.client.focus.history.delete
+function focus.history.delete(c)
+ for k, v in ipairs(focus.history.list) do
+ if v == c then
+ table.remove(focus.history.list, k)
+ break
+ end
+ end
+end
+
+--- Focus a client by its relative index.
+--
+-- @function awful.client.focus.byidx
+-- @param i The index.
+-- @client[opt] c The client.
+function focus.byidx(i, c)
+ local target = client.next(i, c)
+ if target then
+ target:emit_signal("request::activate", "client.focus.byidx",
+ {raise=true})
+ end
+end
+
+--- Filter out window that we do not want handled by focus.
+-- This usually means that desktop, dock and splash windows are
+-- not registered and cannot get focus.
+--
+-- @client c A client.
+-- @return The same client if it's ok, nil otherwise.
+-- @function awful.client.focus.filter
+function focus.filter(c)
+ if c.type == "desktop"
+ or c.type == "dock"
+ or c.type == "splash"
+ or not c.focusable then
+ return nil
+ end
+ return c
+end
+
+--- Update client focus history.
+--
+-- @client c The client that has been focused.
+-- @function awful.client.focus.history.add
+function focus.history.add(c)
+ -- Remove the client if its in stack
+ focus.history.delete(c)
+ -- Record the client has latest focused
+ table.insert(focus.history.list, 1, c)
+end
+
+--- Get the latest focused client for a screen in history.
+--
+-- @tparam int|screen s The screen to look for.
+-- @tparam int idx The index: 0 will return first candidate,
+-- 1 will return second, etc.
+-- @tparam function filter An optional filter. If no client is found in the
+-- first iteration, `awful.client.focus.filter` is used by default to get any
+-- client.
+-- @treturn client.object A client.
+-- @function awful.client.focus.history.get
+function focus.history.get(s, idx, filter)
+ s = get_screen(s)
+ -- When this counter is equal to idx, we return the client
+ local counter = 0
+ local vc = client.visible(s, true)
+ for _, c in ipairs(focus.history.list) do
+ if get_screen(c.screen) == s then
+ if not filter or filter(c) then
+ for _, vcc in ipairs(vc) do
+ if vcc == c then
+ if counter == idx then
+ return c
+ end
+ -- We found one, increment the counter only.
+ counter = counter + 1
+ break
+ end
+ end
+ end
+ end
+ end
+ -- Argh nobody found in history, give the first one visible if there is one
+ -- that passes the filter.
+ filter = filter or focus.filter
+ if counter == 0 then
+ for _, v in ipairs(vc) do
+ if filter(v) then
+ return v
+ end
+ end
+ end
+end
+
+--- Focus the previous client in history.
+-- @function awful.client.focus.history.previous
+function focus.history.previous()
+ local sel = capi.client.focus
+ local s = sel and sel.screen or screen.focused()
+ local c = focus.history.get(s, 1)
+ if c then
+ c:emit_signal("request::activate", "client.focus.history.previous",
+ {raise=false})
+ end
+end
+
+--- Focus a client by the given direction.
+--
+-- @tparam string dir The direction, can be either
+-- `"up"`, `"down"`, `"left"` or `"right"`.
+-- @client[opt] c The client.
+-- @tparam[opt=false] boolean stacked Use stacking order? (top to bottom)
+-- @function awful.client.focus.bydirection
+function focus.bydirection(dir, c, stacked)
+ local sel = c or capi.client.focus
+ if sel then
+ local cltbl = client.visible(sel.screen, stacked)
+ local geomtbl = {}
+ for i,cl in ipairs(cltbl) do
+ geomtbl[i] = cl:geometry()
+ end
+
+ local target = grect.get_in_direction(dir, geomtbl, sel:geometry())
+
+ -- If we found a client to focus, then do it.
+ if target then
+ cltbl[target]:emit_signal("request::activate",
+ "client.focus.bydirection", {raise=false})
+ end
+ end
+end
+
+--- Focus a client by the given direction. Moves across screens.
+--
+-- @param dir The direction, can be either "up", "down", "left" or "right".
+-- @client[opt] c The client.
+-- @tparam[opt=false] boolean stacked Use stacking order? (top to bottom)
+-- @function awful.client.focus.global_bydirection
+function focus.global_bydirection(dir, c, stacked)
+ local sel = c or capi.client.focus
+ local scr = get_screen(sel and sel.screen or screen.focused())
+
+ -- change focus inside the screen
+ focus.bydirection(dir, sel)
+
+ -- if focus not changed, we must change screen
+ if sel == capi.client.focus then
+ screen.focus_bydirection(dir, scr)
+ if scr ~= get_screen(screen.focused()) then
+ local cltbl = client.visible(screen.focused(), stacked)
+ local geomtbl = {}
+ for i,cl in ipairs(cltbl) do
+ geomtbl[i] = cl:geometry()
+ end
+ local target = grect.get_in_direction(dir, geomtbl, scr.geometry)
+
+ if target then
+ cltbl[target]:emit_signal("request::activate",
+ "client.focus.global_bydirection",
+ {raise=false})
+ end
+ end
+ end
+end
+
+return focus
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/client/shape.lua b/lib/awful/client/shape.lua
new file mode 100644
index 0000000..e51d873
--- /dev/null
+++ b/lib/awful/client/shape.lua
@@ -0,0 +1,93 @@
+---------------------------------------------------------------------------
+--- Handle client shapes.
+--
+-- @author Uli Schlachter &lt;psychon@znc.in&gt;
+-- @copyright 2014 Uli Schlachter
+-- @submodule client
+---------------------------------------------------------------------------
+
+-- Grab environment we need
+local surface = require("gears.surface")
+local cairo = require("lgi").cairo
+local capi =
+{
+ client = client,
+}
+
+local shape = {}
+shape.update = {}
+
+--- Get one of a client's shapes and transform it to include window decorations.
+-- @function awful.shape.get_transformed
+-- @client c The client whose shape should be retrieved
+-- @tparam string shape_name Either "bounding" or "clip"
+function shape.get_transformed(c, shape_name)
+ local border = shape_name == "bounding" and c.border_width or 0
+ local shape_img = surface.load_silently(c["client_shape_" .. shape_name], false)
+ if not shape_img then return end
+
+ -- Get information about various sizes on the client
+ local geom = c:geometry()
+ local _, t = c:titlebar_top()
+ local _, b = c:titlebar_bottom()
+ local _, l = c:titlebar_left()
+ local _, r = c:titlebar_right()
+
+ -- Figure out the size of the shape that we need
+ local img_width = geom.width + 2*border
+ local img_height = geom.height + 2*border
+ local result = cairo.ImageSurface(cairo.Format.A1, img_width, img_height)
+ local cr = cairo.Context(result)
+
+ -- Fill everything (this paints the titlebars and border)
+ cr:paint()
+
+ -- Draw the client's shape in the middle
+ cr:set_operator(cairo.Operator.SOURCE)
+ cr:set_source_surface(shape_img, border + l, border + t)
+ cr:rectangle(border + l, border + t, geom.width - l - r, geom.height - t - b)
+ cr:fill()
+
+ return result
+end
+
+--- Update a client's bounding shape from the shape the client set itself.
+-- @function awful.shape.update.bounding
+-- @client c The client to act on
+function shape.update.bounding(c)
+ local res = shape.get_transformed(c, "bounding")
+ c.shape_bounding = res and res._native
+ -- Free memory
+ if res then
+ res:finish()
+ end
+end
+
+--- Update a client's clip shape from the shape the client set itself.
+-- @function awful.shape.update.clip
+-- @client c The client to act on
+function shape.update.clip(c)
+ local res = shape.get_transformed(c, "clip")
+ c.shape_clip = res and res._native
+ -- Free memory
+ if res then
+ res:finish()
+ end
+end
+
+--- Update all of a client's shapes from the shapes the client set itself.
+-- @function awful.shape.update.all
+-- @client c The client to act on
+function shape.update.all(c)
+ shape.update.bounding(c)
+ shape.update.clip(c)
+end
+
+capi.client.connect_signal("property::shape_client_bounding", shape.update.bounding)
+capi.client.connect_signal("property::shape_client_clip", shape.update.clip)
+capi.client.connect_signal("property::width", shape.update.all)
+capi.client.connect_signal("property::height", shape.update.all)
+
+return shape
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/client/urgent.lua b/lib/awful/client/urgent.lua
new file mode 100644
index 0000000..22f6a6d
--- /dev/null
+++ b/lib/awful/client/urgent.lua
@@ -0,0 +1,88 @@
+---------------------------------------------------------------------------
+--- Keep track of the urgent clients.
+--
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2008 Julien Danjou
+-- @submodule client
+---------------------------------------------------------------------------
+
+local urgent = {}
+
+local capi =
+{
+ client = client,
+}
+
+local client
+do
+ client = setmetatable({}, {
+ __index = function(_, k)
+ client = require("awful.client")
+ return client[k]
+ end,
+ __newindex = error -- Just to be sure in case anything ever does this
+ })
+end
+
+local data = setmetatable({}, { __mode = 'k' })
+
+--- Get the first client that got the urgent hint.
+--
+-- @function awful.urgent.get
+-- @treturn client.object The first urgent client.
+function urgent.get()
+ if #data > 0 then
+ return data[1]
+ else
+ -- fallback behaviour: iterate through clients and get the first urgent
+ local clients = capi.client.get()
+ for _, cl in pairs(clients) do
+ if cl.urgent then
+ return cl
+ end
+ end
+ end
+end
+
+--- Jump to the client that received the urgent hint first.
+--
+-- @function awful.urgent.jumpto
+-- @tparam bool|function merge If true then merge tags (select the client's
+-- first tag additionally) when the client is not visible.
+-- If it is a function, it will be called with the client as argument.
+function urgent.jumpto(merge)
+ local c = client.urgent.get()
+ if c then
+ c:jump_to(merge)
+ end
+end
+
+--- Adds client to urgent stack.
+--
+-- @function awful.urgent.add
+-- @client c The client object.
+-- @param prop The property which is updated.
+function urgent.add(c, prop)
+ if type(c) == "client" and prop == "urgent" and c.urgent then
+ table.insert(data, c)
+ end
+end
+
+--- Remove client from urgent stack.
+--
+-- @function awful.urgent.delete
+-- @client c The client object.
+function urgent.delete(c)
+ for k, cl in ipairs(data) do
+ if c == cl then
+ table.remove(data, k)
+ break
+ end
+ end
+end
+
+capi.client.connect_signal("property::urgent", urgent.add)
+capi.client.connect_signal("focus", urgent.delete)
+capi.client.connect_signal("unmanage", urgent.delete)
+
+return urgent
diff --git a/lib/awful/completion.lua b/lib/awful/completion.lua
new file mode 100644
index 0000000..3462ed1
--- /dev/null
+++ b/lib/awful/completion.lua
@@ -0,0 +1,201 @@
+---------------------------------------------------------------------------
+--- Completion module.
+--
+-- This module store a set of function using shell to complete commands name.
+--
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @author Sébastien Gross &lt;seb-awesome@chezwam.org&gt;
+-- @copyright 2008 Julien Danjou, Sébastien Gross
+-- @module awful.completion
+---------------------------------------------------------------------------
+
+-- Grab environment we need
+local io = io
+local os = os
+local table = table
+local math = math
+local print = print
+local pairs = pairs
+local string = string
+
+local completion = {}
+
+-- mapping of command/completion function
+local bashcomp_funcs = {}
+local bashcomp_src = "/etc/bash_completion"
+
+--- Enable programmable bash completion in awful.completion.bash at the price of
+-- a slight overhead.
+-- @param src The bash completion source file, /etc/bash_completion by default.
+function completion.bashcomp_load(src)
+ if src then bashcomp_src = src end
+ local c, err = io.popen("/usr/bin/env bash -c 'source " .. bashcomp_src .. "; complete -p'")
+ if c then
+ while true do
+ local line = c:read("*line")
+ if not line then break end
+ -- if a bash function is used for completion, register it
+ if line:match(".* -F .*") then
+ bashcomp_funcs[line:gsub(".* (%S+)$","%1")] = line:gsub(".*-F +(%S+) .*$", "%1")
+ end
+ end
+ c:close()
+ else
+ print(err)
+ end
+end
+
+local function bash_escape(str)
+ str = str:gsub(" ", "\\ ")
+ str = str:gsub("%[", "\\[")
+ str = str:gsub("%]", "\\]")
+ str = str:gsub("%(", "\\(")
+ str = str:gsub("%)", "\\)")
+ return str
+end
+
+--- Use shell completion system to complete command and filename.
+-- @param command The command line.
+-- @param cur_pos The cursor position.
+-- @param ncomp The element number to complete.
+-- @param shell The shell to use for completion (bash (default) or zsh).
+-- @return The new command, the new cursor position, the table of all matches.
+function completion.shell(command, cur_pos, ncomp, shell)
+ local wstart = 1
+ local wend = 1
+ local words = {}
+ local cword_index = 0
+ local cword_start = 0
+ local cword_end = 0
+ local i = 1
+ local comptype = "file"
+
+ -- do nothing if we are on a letter, i.e. not at len + 1 or on a space
+ if cur_pos ~= #command + 1 and command:sub(cur_pos, cur_pos) ~= " " then
+ return command, cur_pos
+ elseif #command == 0 then
+ return command, cur_pos
+ end
+
+ while wend <= #command do
+ wend = command:find(" ", wstart)
+ if not wend then wend = #command + 1 end
+ table.insert(words, command:sub(wstart, wend - 1))
+ if cur_pos >= wstart and cur_pos <= wend + 1 then
+ cword_start = wstart
+ cword_end = wend
+ cword_index = i
+ end
+ wstart = wend + 1
+ i = i + 1
+ end
+
+ if cword_index == 1 and not string.find(words[cword_index], "/") then
+ comptype = "command"
+ end
+
+ local shell_cmd
+ if shell == "zsh" or (not shell and os.getenv("SHELL"):match("zsh$")) then
+ if comptype == "file" then
+ -- NOTE: ${~:-"..."} turns on GLOB_SUBST, useful for expansion of
+ -- "~/" ($HOME). ${:-"foo"} is the string "foo" as var.
+ shell_cmd = "/usr/bin/env zsh -c 'local -a res; res=( ${~:-"
+ .. string.format('%q', words[cword_index]) .. "}* ); "
+ .. "print -ln -- ${res[@]}'"
+ else
+ -- check commands, aliases, builtins, functions and reswords
+ shell_cmd = "/usr/bin/env zsh -c 'local -a res; "..
+ "res=( "..
+ "\"${(k)commands[@]}\" \"${(k)aliases[@]}\" \"${(k)builtins[@]}\" \"${(k)functions[@]}\" \"${(k)reswords[@]}\" "..
+ "${PWD}/*(:t)"..
+ "); "..
+ "print -ln -- ${(M)res[@]:#" .. string.format('%q', words[cword_index]) .. "*}'"
+ end
+ else
+ if bashcomp_funcs[words[1]] then
+ -- fairly complex command with inline bash script to get the possible completions
+ shell_cmd = "/usr/bin/env bash -c 'source " .. bashcomp_src .. "; " ..
+ "__print_completions() { for ((i=0;i<${#COMPREPLY[*]};i++)); do echo ${COMPREPLY[i]}; done }; " ..
+ "COMP_WORDS=(" .. command .."); COMP_LINE=\"" .. command .. "\"; " ..
+ "COMP_COUNT=" .. cur_pos .. "; COMP_CWORD=" .. cword_index-1 .. "; " ..
+ bashcomp_funcs[words[1]] .. "; __print_completions'"
+ else
+ shell_cmd = "/usr/bin/env bash -c 'compgen -A " .. comptype .. " "
+ .. string.format('%q', words[cword_index]) .. "'"
+ end
+ end
+ local c, err = io.popen(shell_cmd .. " | sort -u")
+ local output = {}
+ if c then
+ while true do
+ local line = c:read("*line")
+ if not line then break end
+ if os.execute("test -d " .. string.format('%q', line)) == 0 then
+ line = line .. "/"
+ end
+ table.insert(output, bash_escape(line))
+ end
+
+ c:close()
+ else
+ print(err)
+ end
+
+ -- no completion, return
+ if #output == 0 then
+ return command, cur_pos
+ end
+
+ -- cycle
+ while ncomp > #output do
+ ncomp = ncomp - #output
+ end
+
+ local str = command:sub(1, cword_start - 1) .. output[ncomp] .. command:sub(cword_end)
+ cur_pos = cword_end + #output[ncomp] + 1
+
+ return str, cur_pos, output
+end
+
+--- Run a generic completion.
+-- For this function to run properly the awful.completion.keyword table should
+-- be fed up with all keywords. The completion is run against these keywords.
+-- @param text The current text the user had typed yet.
+-- @param cur_pos The current cursor position.
+-- @param ncomp The number of yet requested completion using current text.
+-- @param keywords The keywords table uised for completion.
+-- @return The new match, the new cursor position, the table of all matches.
+function completion.generic(text, cur_pos, ncomp, keywords) -- luacheck: no unused args
+ -- The keywords table may be empty
+ if #keywords == 0 then
+ return text, #text + 1
+ end
+
+ -- if no text had been typed yet, then we could start cycling around all
+ -- keywords with out filtering and move the cursor at the end of keyword
+ if text == nil or #text == 0 then
+ ncomp = math.fmod(ncomp - 1, #keywords) + 1
+ return keywords[ncomp], #keywords[ncomp] + 2
+ end
+
+ -- Filter out only keywords starting with text
+ local matches = {}
+ for _, x in pairs(keywords) do
+ if x:sub(1, #text) == text then
+ table.insert(matches, x)
+ end
+ end
+
+ -- if there are no matches just leave out with the current text and position
+ if #matches == 0 then
+ return text, #text + 1, matches
+ end
+
+ -- cycle around all matches
+ ncomp = math.fmod(ncomp - 1, #matches) + 1
+ return matches[ncomp], #matches[ncomp] + 1, matches
+end
+
+return completion
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/dbus.lua b/lib/awful/dbus.lua
new file mode 100644
index 0000000..8660dce
--- /dev/null
+++ b/lib/awful/dbus.lua
@@ -0,0 +1,19 @@
+---------------------------------------------------------------------------
+--- D-Bus module for awful.
+--
+-- This module simply request the org.awesomewm.awful name on the D-Bus
+-- for futur usage by other awful modules.
+--
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2009 Julien Danjou
+-- @module awful.dbus
+---------------------------------------------------------------------------
+
+-- Grab environment we need
+local dbus = dbus
+
+if dbus then
+ dbus.request_name("session", "org.awesomewm.awful")
+end
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/ewmh.lua b/lib/awful/ewmh.lua
new file mode 100644
index 0000000..eb72a16
--- /dev/null
+++ b/lib/awful/ewmh.lua
@@ -0,0 +1,295 @@
+---------------------------------------------------------------------------
+--- Implements EWMH requests handling.
+--
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2009 Julien Danjou
+-- @module awful.ewmh
+---------------------------------------------------------------------------
+
+local client = client
+local screen = screen
+local ipairs = ipairs
+local util = require("awful.util")
+local aclient = require("awful.client")
+local aplace = require("awful.placement")
+local asuit = require("awful.layout.suit")
+
+local ewmh = {
+ generic_activate_filters = {},
+ contextual_activate_filters = {},
+}
+
+--- The list of all registered generic request::activate (focus stealing)
+-- filters. If a filter is added to only one context, it will be in
+-- `ewmh.contextual_activate_filters`["context_name"].
+-- @table[opt={}] generic_activate_filters
+-- @see ewmh.activate
+-- @see ewmh.add_activate_filter
+-- @see ewmh.remove_activate_filter
+
+--- The list of all registered contextual request::activate (focus stealing)
+-- filters. If a filter is added to only one context, it will be in
+-- `ewmh.generic_activate_filters`.
+-- @table[opt={}] contextual_activate_filters
+-- @see ewmh.activate
+-- @see ewmh.add_activate_filter
+-- @see ewmh.remove_activate_filter
+
+--- Update a client's settings when its geometry changes, skipping signals
+-- resulting from calls within.
+local geometry_change_lock = false
+local function geometry_change(window)
+ if geometry_change_lock then return end
+ geometry_change_lock = true
+
+ -- Fix up the geometry in case this window needs to cover the whole screen.
+ local bw = window.border_width or 0
+ local g = window.screen.workarea
+ if window.maximized_vertical then
+ window:geometry { height = g.height - 2*bw, y = g.y }
+ end
+ if window.maximized_horizontal then
+ window:geometry { width = g.width - 2*bw, x = g.x }
+ end
+ if window.fullscreen then
+ window.border_width = 0
+ window:geometry(window.screen.geometry)
+ end
+
+ geometry_change_lock = false
+end
+
+--- Activate a window.
+--
+-- This sets the focus only if the client is visible.
+--
+-- It is the default signal handler for `request::activate` on a `client`.
+--
+-- @signalhandler awful.ewmh.activate
+-- @client c A client to use
+-- @tparam string context The context where this signal was used.
+-- @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
+
+ local found, ret = false
+
+ -- Execute the filters until something handle the request
+ for _, tab in ipairs {
+ ewmh.contextual_activate_filters[context] or {},
+ ewmh.generic_activate_filters
+ } do
+ for i=#tab, 1, -1 do
+ ret = tab[i](c, context, hints)
+ if ret ~= nil then found=true; break end
+ end
+
+ if found then break end
+ end
+
+ if ret ~= false and c:isvisible() then
+ client.focus = c
+ elseif ret == false and not hints.force then
+ return
+ end
+
+ if hints and hints.raise then
+ c:raise()
+ if not awesome.startup and not c:isvisible() then
+ c.urgent = true
+ end
+ end
+end
+
+--- Add an activate (focus stealing) filter function.
+--
+-- The callback takes the following parameters:
+--
+-- * **c** (*client*) The client requesting the activation
+-- * **context** (*string*) The activation context.
+-- * **hints** (*table*) Some additional hints (depending on the context)
+--
+-- If the callback returns `true`, the client will be activated unless the `force`
+-- hint is set. If the callback returns `false`, the activation request is
+-- cancelled. If the callback returns `nil`, the previous callback will be
+-- executed. This will continue until either a callback handles the request or
+-- when it runs out of callbacks. In that case, the request will be granted if
+-- the client is visible.
+--
+-- For example, to block Firefox from stealing the focus, use:
+--
+-- awful.ewmh.add_activate_filter(function(c, "ewmh")
+-- if c.class == "Firefox" then return false end
+-- end)
+--
+-- @tparam function f The callback
+-- @tparam[opt] string context The `request::activate` context
+-- @see generic_activate_filters
+-- @see contextual_activate_filters
+-- @see remove_activate_filter
+function ewmh.add_activate_filter(f, context)
+ if not context then
+ table.insert(ewmh.generic_activate_filters, f)
+ else
+ ewmh.contextual_activate_filters[context] =
+ ewmh.contextual_activate_filters[context] or {}
+
+ table.insert(ewmh.contextual_activate_filters[context], f)
+ end
+end
+
+--- Remove an activate (focus stealing) filter function.
+-- This is an helper to avoid dealing with `ewmh.add_activate_filter` directly.
+-- @tparam function f The callback
+-- @tparam[opt] string context The `request::activate` context
+-- @treturn boolean If the callback existed
+-- @see generic_activate_filters
+-- @see contextual_activate_filters
+-- @see add_activate_filter
+function ewmh.remove_activate_filter(f, context)
+ local tab = context and (ewmh.contextual_activate_filters[context] or {})
+ or ewmh.generic_activate_filters
+
+ for k, v in ipairs(tab) do
+ if v == f then
+ table.remove(tab, k)
+
+ -- In case the callback is there multiple time.
+ ewmh.remove_activate_filter(f, context)
+
+ return true
+ end
+ end
+
+ return false
+end
+
+-- Get tags that are on the same screen as the client. This should _almost_
+-- always return the same content as c:tags().
+local function get_valid_tags(c, s)
+ local tags, new_tags = c:tags(), {}
+
+ for _, t in ipairs(tags) do
+ if s == t.screen then
+ table.insert(new_tags, t)
+ end
+ end
+
+ return new_tags
+end
+
+--- Tag a window with its requested tag.
+--
+-- It is the default signal handler for `request::tag` on a `client`.
+--
+-- @signalhandler awful.ewmh.tag
+-- @client c A client to tag
+-- @tparam[opt] tag|boolean t A tag to use. If true, then the client is made sticky.
+-- @tparam[opt={}] table hints Extra information
+function ewmh.tag(c, t, hints) --luacheck: no unused
+ -- There is nothing to do
+ if not t and #get_valid_tags(c, c.screen) > 0 then return end
+
+ if not t then
+ if c.transient_for then
+ c.screen = c.transient_for.screen
+ if not c.sticky then
+ c:tags(c.transient_for:tags())
+ end
+ else
+ c:to_selected_tags()
+ end
+ elseif type(t) == "boolean" and t then
+ c.sticky = true
+ else
+ c.screen = t.screen
+ c:tags({ t })
+ end
+end
+
+--- Handle client urgent request
+-- @signalhandler awful.ewmh.urgent
+-- @client c A client
+-- @tparam boolean urgent If the client should be urgent
+function ewmh.urgent(c, urgent)
+ if c ~= client.focus and not aclient.property.get(c,"ignore_urgent") then
+ c.urgent = 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.
+--
+-- @signalhandler awful.ewmh.geometry
+-- @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)
+ local layout = c.screen.selected_tag and c.screen.selected_tag.layout or nil
+
+ -- Setting the geometry wont work unless the client is floating.
+ if (not c.floating) and (not layout == asuit.floating) then
+ return
+ end
+
+ 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::geometry", ewmh.geometry)
+client.connect_signal("property::border_width", geometry_change)
+client.connect_signal("property::geometry", geometry_change)
+screen.connect_signal("property::workarea", function(s)
+ for _, c in pairs(client.get(s)) do
+ geometry_change(c)
+ end
+end)
+
+return ewmh
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/hotkeys_popup/init.lua b/lib/awful/hotkeys_popup/init.lua
new file mode 100644
index 0000000..cb8c8be
--- /dev/null
+++ b/lib/awful/hotkeys_popup/init.lua
@@ -0,0 +1,17 @@
+---------------------------------------------------------------------------
+--- Popup widget which shows current hotkeys and their descriptions.
+--
+-- @author Yauheni Kirylau &lt;yawghen@gmail.com&gt;
+-- @copyright 2014-2015 Yauheni Kirylau
+-- @module awful.hotkeys_popup
+---------------------------------------------------------------------------
+
+
+local hotkeys_popup = {
+ widget = require("awful.hotkeys_popup.widget"),
+ keys = require("awful.hotkeys_popup.keys")
+}
+hotkeys_popup.show_help = hotkeys_popup.widget.show_help
+return hotkeys_popup
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/hotkeys_popup/keys/init.lua b/lib/awful/hotkeys_popup/keys/init.lua
new file mode 100644
index 0000000..cbcbb62
--- /dev/null
+++ b/lib/awful/hotkeys_popup/keys/init.lua
@@ -0,0 +1,15 @@
+---------------------------------------------------------------------------
+--- Additional hotkeys for awful.hotkeys_widget
+--
+-- @author Yauheni Kirylau &lt;yawghen@gmail.com&gt;
+-- @copyright 2014-2015 Yauheni Kirylau
+-- @module awful.hotkeys_popup.keys
+---------------------------------------------------------------------------
+
+
+local keys = {
+ vim = require("awful.hotkeys_popup.keys.vim")
+}
+return keys
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/hotkeys_popup/keys/vim.lua b/lib/awful/hotkeys_popup/keys/vim.lua
new file mode 100644
index 0000000..69d21e9
--- /dev/null
+++ b/lib/awful/hotkeys_popup/keys/vim.lua
@@ -0,0 +1,173 @@
+---------------------------------------------------------------------------
+--- VIM hotkeys for awful.hotkeys_widget
+--
+-- @author Yauheni Kirylau &lt;yawghen@gmail.com&gt;
+-- @copyright 2014-2015 Yauheni Kirylau
+-- @module awful.hotkeys_popup.keys.vim
+---------------------------------------------------------------------------
+
+local hotkeys_popup = require("awful.hotkeys_popup.widget")
+
+local vim_rule_any = {name={"vim", "VIM"}}
+for group_name, group_data in pairs({
+ ["VIM: motion"] = { color="#009F00", rule_any=vim_rule_any },
+ ["VIM: command"] = { color="#aFaF00", rule_any=vim_rule_any },
+ ["VIM: command (insert)"] = { color="#cF4F40", rule_any=vim_rule_any },
+ ["VIM: operator"] = { color="#aF6F00", rule_any=vim_rule_any },
+ ["VIM: find"] = { color="#65cF9F", rule_any=vim_rule_any },
+ ["VIM: scroll"] = { color="#659FdF", rule_any=vim_rule_any },
+}) do
+ hotkeys_popup.group_rules[group_name] = group_data
+end
+
+
+local vim_keys = {
+
+ ["VIM: motion"] = {{
+ modifiers = {},
+ keys = {
+ ['`']="goto mark",
+ ['0']='"hard" BOL',
+ ['-']="prev line",
+ w="next word",
+ e="end word",
+ ['[']=". misc",
+ [']']=". misc",
+ ["'"]=". goto mk. BOL",
+ b="prev word",
+ ["|"]='BOL/goto col',
+ ["$"]='EOL',
+ ["%"]='goto matching bracket',
+ ["^"]='"soft" BOL',
+ ["("]='sentence begin',
+ [")"]='sentence end',
+ ["_"]='"soft" BOL down',
+ ["+"]='next line',
+ W='next WORD',
+ E='end WORD',
+ ['{']="paragraph begin",
+ ['}']="paragraph end",
+ G='EOF/goto line',
+ H='move cursor to screen top',
+ M='move cursor to screen middle',
+ L='move cursor to screen bottom',
+ B='prev WORD',
+ }
+ }, {
+ modifiers = {"Ctrl"},
+ keys = {
+ u="half page up",
+ d="half page down",
+ b="page up",
+ f="page down",
+ o="prev mark",
+ }
+ }},
+
+ ["VIM: operator"] = {{
+ modifiers = {},
+ keys = {
+ ['=']="auto format",
+ y="yank",
+ d="delete",
+ c="change",
+ ["!"]='external filter',
+ ['&lt;']='unindent',
+ ['&gt;']='indent',
+ }
+ }},
+
+ ["VIM: command"] = {{
+ modifiers = {},
+ keys = {
+ ['~']="toggle case",
+ q=". record macro",
+ r=". replace char",
+ u="undo",
+ p="paste after",
+ gg="go to the top of file",
+ gf="open file under cursor",
+ x="delete char",
+ v="visual mode",
+ m=". set mark",
+ ['.']="repeat command",
+ ["@"]='. play macro',
+ ["&amp;"]='repeat :s',
+ Q='ex mode',
+ Y='yank line',
+ U='undo line',
+ P='paste before cursor',
+ D='delete to EOL',
+ J='join lines',
+ K='help',
+ [':']='ex cmd line',
+ ['"']='. register spec',
+ ZZ='quit and save',
+ ZQ='quit discarding changes',
+ X='back-delete',
+ V='visual lines selection',
+ }
+ }, {
+ modifiers = {"Ctrl"},
+ keys = {
+ w=". window operations",
+ r="redo",
+ ["["]="normal mode",
+ a="increase number",
+ x="decrease number",
+ g="file/cursor info",
+ z="suspend",
+ c="cancel/normal mode",
+ v="visual block selection",
+ }
+ }},
+
+ ["VIM: command (insert)"] = {{
+ modifiers = {},
+ keys = {
+ i="insert mode",
+ o="open below",
+ a="append",
+ s="subst char",
+ R='replace mode',
+ I='insert at BOL',
+ O='open above',
+ A='append at EOL',
+ S='subst line',
+ C='change to EOL',
+ }
+ }},
+
+ ["VIM: find"] = {{
+ modifiers = {},
+ keys = {
+ [';']="repeat t/T/f/F",
+ [',']="reverse t/T/f/F",
+ ['/']=". find",
+ ['?']='. reverse find',
+ n="next search match",
+ N='prev search match',
+ f=". find char",
+ F='. reverse find char',
+ t=". 'till char",
+ T=". reverse 'till char",
+ ["*"]='find word under cursor',
+ ["#"]='reverse find under cursor',
+ }
+ }},
+
+ ["VIM: scroll"] = {{
+ modifiers = {},
+ keys = {
+ e="scroll line up",
+ y="scroll line down",
+ zt="scroll cursor to the top",
+ zz="scroll cursor to the center",
+ zb="scroll cursor to the bottom",
+ }
+ }},
+}
+
+hotkeys_popup.add_hotkeys(vim_keys)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/hotkeys_popup/widget.lua b/lib/awful/hotkeys_popup/widget.lua
new file mode 100644
index 0000000..62a2231
--- /dev/null
+++ b/lib/awful/hotkeys_popup/widget.lua
@@ -0,0 +1,482 @@
+---------------------------------------------------------------------------
+--- Popup widget which shows current hotkeys and their descriptions.
+--
+-- @author Yauheni Kirylau &lt;yawghen@gmail.com&gt;
+-- @copyright 2014-2015 Yauheni Kirylau
+-- @module awful.hotkeys_popup.widget
+---------------------------------------------------------------------------
+
+local capi = {
+ screen = screen,
+ client = client,
+ keygrabber = keygrabber,
+}
+local awful = require("awful")
+local wibox = require("wibox")
+local beautiful = require("beautiful")
+local dpi = beautiful.xresources.apply_dpi
+local compute_textbox_width = require("menubar").utils.compute_textbox_width
+
+
+-- Stripped copy of this module https://github.com/copycat-killer/lain/blob/master/util/markup.lua:
+local markup = {}
+-- Set the font.
+function markup.font(font, text)
+ return '<span font="' .. tostring(font) .. '">' .. tostring(text) ..'</span>'
+end
+-- Set the foreground.
+function markup.fg(color, text)
+ return '<span foreground="' .. tostring(color) .. '">' .. tostring(text) .. '</span>'
+end
+-- Set the background.
+function markup.bg(color, text)
+ return '<span background="' .. tostring(color) .. '">' .. tostring(text) .. '</span>'
+end
+
+local widget_module = {
+ group_rules = {},
+}
+
+--- Don't show hotkeys without descriptions.
+widget_module.hide_without_description = true
+
+--- Merge hotkey records into one if they have the same modifiers and
+-- description.
+widget_module.merge_duplicates = true
+
+
+function widget_module.new()
+local widget = {
+ hide_without_description = widget_module.hide_without_description,
+ merge_duplicates = widget_module.merge_duplicates,
+ group_rules = awful.util.table.clone(widget_module.group_rules),
+ title_font = "Monospace Bold 9",
+ description_font = "Monospace 8",
+ width = dpi(1200),
+ height = dpi(800),
+ border_width = beautiful.border_width or dpi(2),
+ modifiers_color = beautiful.bg_minimize or "#555555",
+ group_margin = dpi(6),
+ additional_hotkeys = {},
+ labels = {
+ Mod4="Super",
+ Mod1="Alt",
+ Escape="Esc",
+ Insert="Ins",
+ Delete="Del",
+ Backspace="BackSpc",
+ Return="Enter",
+ Next="PgDn",
+ Prior="PgUp",
+ ['#108']="Alt Gr",
+ Left='←',
+ Up='↑',
+ Right='→',
+ Down='↓',
+ ['#67']="F1",
+ ['#68']="F2",
+ ['#69']="F3",
+ ['#70']="F4",
+ ['#71']="F5",
+ ['#72']="F6",
+ ['#73']="F7",
+ ['#74']="F8",
+ ['#75']="F9",
+ ['#76']="F10",
+ ['#95']="F11",
+ ['#96']="F12",
+ ['#10']="1",
+ ['#11']="2",
+ ['#12']="3",
+ ['#13']="4",
+ ['#14']="5",
+ ['#15']="6",
+ ['#16']="7",
+ ['#17']="8",
+ ['#18']="9",
+ ['#19']="0",
+ ['#20']="-",
+ ['#21']="=",
+ Control="Ctrl"
+ },
+}
+
+local cached_wiboxes = {}
+local cached_awful_keys = nil
+local colors_counter = {}
+local colors = beautiful.xresources.get_current_theme()
+local group_list = {}
+
+
+local function get_next_color(id)
+ id = id or "default"
+ if colors_counter[id] then
+ colors_counter[id] = math.fmod(colors_counter[id] + 1, 15) + 1
+ else
+ colors_counter[id] = 1
+ end
+ return colors["color"..tostring(colors_counter[id], 15)]
+end
+
+
+local function join_plus_sort(modifiers)
+ if #modifiers<1 then return "none" end
+ table.sort(modifiers)
+ return table.concat(modifiers, '+')
+end
+
+
+local function add_hotkey(key, data, target)
+ if widget.hide_without_description and not data.description then return end
+
+ local readable_mods = {}
+ for _, mod in ipairs(data.mod) do
+ table.insert(readable_mods, widget.labels[mod] or mod)
+ end
+ local joined_mods = join_plus_sort(readable_mods)
+
+ local group = data.group or "none"
+ group_list[group] = true
+ if not target[group] then target[group] = {} end
+ local new_key = {
+ key = (widget.labels[key] or key),
+ mod = joined_mods,
+ description = data.description
+ }
+ local index = data.description or "none" -- or use its hash?
+ if not target[group][index] then
+ target[group][index] = new_key
+ else
+ if widget.merge_duplicates and joined_mods == target[group][index].mod then
+ target[group][index].key = target[group][index].key .. "/" .. new_key.key
+ else
+ while target[group][index] do
+ index = index .. " "
+ end
+ target[group][index] = new_key
+ end
+ end
+end
+
+
+local function sort_hotkeys(target)
+ -- @TODO: add sort by 12345qwertyasdf etc
+ for group, _ in pairs(group_list) do
+ if target[group] then
+ local sorted_table = {}
+ for _, key in pairs(target[group]) do
+ table.insert(sorted_table, key)
+ end
+ table.sort(
+ sorted_table,
+ function(a,b) return (a.mod or '')..a.key<(b.mod or '')..b.key end
+ )
+ target[group] = sorted_table
+ end
+ end
+end
+
+
+local function import_awful_keys()
+ if cached_awful_keys then
+ return
+ end
+ cached_awful_keys = {}
+ for _, data in pairs(awful.key.hotkeys) do
+ add_hotkey(data.key, data, cached_awful_keys)
+ end
+ sort_hotkeys(cached_awful_keys)
+end
+
+
+local function group_label(group, color)
+ local textbox = wibox.widget.textbox(
+ markup.font(widget.title_font,
+ markup.bg(
+ color or (widget.group_rules[group] and
+ widget.group_rules[group].color or get_next_color("group_title")
+ ),
+ markup.fg(beautiful.bg_normal or "#000000", " "..group.." ")
+ )
+ )
+ )
+ local margin = wibox.container.margin()
+ margin:set_widget(textbox)
+ margin:set_top(widget.group_margin)
+ return margin
+end
+
+local function get_screen(s)
+ return s and capi.screen[s]
+end
+
+local function create_wibox(s, available_groups)
+ s = get_screen(s)
+
+ local wa = s.workarea
+ local height = (widget.height < wa.height) and widget.height or
+ (wa.height - widget.border_width * 2)
+ local width = (widget.width < wa.width) and widget.width or
+ (wa.width - widget.border_width * 2)
+
+ -- arrange hotkey groups into columns
+ local line_height = beautiful.get_font_height(widget.title_font)
+ local group_label_height = line_height + widget.group_margin
+ -- -1 for possible pagination:
+ local max_height_px = height - group_label_height
+ local column_layouts = {}
+ for _, group in ipairs(available_groups) do
+ local keys = cached_awful_keys[group] or widget.additional_hotkeys[group]
+ local joined_descriptions = ""
+ for i, key in ipairs(keys) do
+ joined_descriptions = joined_descriptions .. key.description .. (i~=#keys and "\n" or "")
+ end
+ -- +1 for group label:
+ local items_height = awful.util.linecount(joined_descriptions) * line_height + group_label_height
+ local current_column
+ local available_height_px = max_height_px
+ local add_new_column = true
+ for i, column in ipairs(column_layouts) do
+ if ((column.height_px + items_height) < max_height_px) or
+ (i == #column_layouts and column.height_px < max_height_px / 2)
+ then
+ current_column = column
+ add_new_column = false
+ available_height_px = max_height_px - current_column.height_px
+ break
+ end
+ end
+ local overlap_leftovers
+ if items_height > available_height_px then
+ local new_keys = {}
+ overlap_leftovers = {}
+ -- +1 for group title and +1 for possible hyphen (v):
+ local available_height_items = (available_height_px - group_label_height*2) / line_height
+ for i=1,#keys do
+ table.insert(((i<available_height_items) and new_keys or overlap_leftovers), keys[i])
+ end
+ keys = new_keys
+ table.insert(keys, {key=markup.fg(widget.modifiers_color, "▽"), description=""})
+ end
+ if not current_column then
+ current_column = {layout=wibox.layout.fixed.vertical()}
+ end
+ current_column.layout:add(group_label(group))
+
+ local function insert_keys(_keys, _add_new_column)
+ local max_label_width = 0
+ local max_label_content = ""
+ local joined_labels = ""
+ for i, key in ipairs(_keys) do
+ local length = string.len(key.key or '') + string.len(key.description or '')
+ local modifiers = key.mod
+ if not modifiers or modifiers == "none" then
+ modifiers = ""
+ else
+ length = length + string.len(modifiers) + 1 -- +1 for "+" character
+ modifiers = markup.fg(widget.modifiers_color, modifiers.."+")
+ end
+ local rendered_hotkey = markup.font(widget.title_font,
+ modifiers .. (key.key or "") .. " "
+ ) .. markup.font(widget.description_font,
+ key.description or ""
+ )
+ if length > max_label_width then
+ max_label_width = length
+ max_label_content = rendered_hotkey
+ end
+ joined_labels = joined_labels .. rendered_hotkey .. (i~=#_keys and "\n" or "")
+ end
+ current_column.layout:add(wibox.widget.textbox(joined_labels))
+ local max_width = compute_textbox_width(wibox.widget.textbox(max_label_content), s) +
+ widget.group_margin
+ if not current_column.max_width or max_width > current_column.max_width then
+ current_column.max_width = max_width
+ end
+ -- +1 for group label:
+ current_column.height_px = (current_column.height_px or 0) +
+ awful.util.linecount(joined_labels)*line_height + group_label_height
+ if _add_new_column then
+ table.insert(column_layouts, current_column)
+ end
+ end
+
+ insert_keys(keys, add_new_column)
+ if overlap_leftovers then
+ current_column = {layout=wibox.layout.fixed.vertical()}
+ insert_keys(overlap_leftovers, true)
+ end
+ end
+
+ -- arrange columns into pages
+ local available_width_px = width
+ local pages = {}
+ local columns = wibox.layout.fixed.horizontal()
+ for _, item in ipairs(column_layouts) do
+ if item.max_width > available_width_px then
+ columns.widgets[#columns.widgets]['widget']:add(
+ group_label("PgDn - Next Page", beautiful.fg_normal)
+ )
+ table.insert(pages, columns)
+ columns = wibox.layout.fixed.horizontal()
+ available_width_px = width - item.max_width
+ local old_widgets = item.layout.widgets
+ item.layout.widgets = {group_label("PgUp - Prev Page", beautiful.fg_normal)}
+ awful.util.table.merge(item.layout.widgets, old_widgets)
+ else
+ available_width_px = available_width_px - item.max_width
+ end
+ local column_margin = wibox.container.margin()
+ column_margin:set_widget(item.layout)
+ column_margin:set_left(widget.group_margin)
+ columns:add(column_margin)
+ end
+ table.insert(pages, columns)
+
+ local mywibox = wibox({
+ ontop = true,
+ opacity = beautiful.notification_opacity or 1,
+ border_width = widget.border_width,
+ border_color = beautiful.fg_normal,
+ })
+ mywibox:geometry({
+ x = wa.x + math.floor((wa.width - width - widget.border_width*2) / 2),
+ y = wa.y + math.floor((wa.height - height - widget.border_width*2) / 2),
+ width = width,
+ height = height,
+ })
+ mywibox:set_widget(pages[1])
+ mywibox:buttons(awful.util.table.join(
+ awful.button({ }, 1, function () mywibox.visible=false end),
+ awful.button({ }, 3, function () mywibox.visible=false end)
+ ))
+
+ local widget_obj = {}
+ widget_obj.current_page = 1
+ widget_obj.wibox = mywibox
+ function widget_obj:page_next()
+ if self.current_page == #pages then return end
+ self.current_page = self.current_page + 1
+ self.wibox:set_widget(pages[self.current_page])
+ end
+ function widget_obj:page_prev()
+ if self.current_page == 1 then return end
+ self.current_page = self.current_page - 1
+ self.wibox:set_widget(pages[self.current_page])
+ end
+ function widget_obj:show()
+ self.wibox.visible = true
+ end
+ function widget_obj:hide()
+ self.wibox.visible = false
+ end
+
+ return widget_obj
+end
+
+
+--- Show popup with hotkeys help.
+-- @tparam[opt] client c Client.
+-- @tparam[opt] screen s Screen.
+function widget.show_help(c, s)
+ import_awful_keys()
+ c = c or capi.client.focus
+ s = s or (c and c.screen or awful.screen.focused())
+
+ local available_groups = {}
+ for group, _ in pairs(group_list) do
+ local need_match
+ for group_name, data in pairs(widget.group_rules) do
+ if group_name==group and (
+ data.rule or data.rule_any or data.except or data.except_any
+ ) then
+ if not c or not awful.rules.matches(c, {
+ rule=data.rule,
+ rule_any=data.rule_any,
+ except=data.except,
+ except_any=data.except_any
+ }) then
+ need_match = true
+ break
+ end
+ end
+ end
+ if not need_match then table.insert(available_groups, group) end
+ end
+
+ local joined_groups = join_plus_sort(available_groups)
+ if not cached_wiboxes[s] then
+ cached_wiboxes[s] = {}
+ end
+ if not cached_wiboxes[s][joined_groups] then
+ cached_wiboxes[s][joined_groups] = create_wibox(s, available_groups)
+ end
+ local help_wibox = cached_wiboxes[s][joined_groups]
+ help_wibox:show()
+
+ return capi.keygrabber.run(function(_, key, event)
+ if event == "release" then return end
+ if key then
+ if key == "Next" then
+ help_wibox:page_next()
+ elseif key == "Prior" then
+ help_wibox:page_prev()
+ else
+ capi.keygrabber.stop()
+ help_wibox:hide()
+ end
+ end
+ end)
+end
+
+
+--- Add hotkey descriptions for third-party applications.
+-- @tparam table hotkeys Table with bindings,
+-- see `awful.hotkeys_popup.key.vim` as an example.
+function widget.add_hotkeys(hotkeys)
+ for group, bindings in pairs(hotkeys) do
+ for _, binding in ipairs(bindings) do
+ local modifiers = binding.modifiers
+ local keys = binding.keys
+ for key, description in pairs(keys) do
+ add_hotkey(key, {
+ mod=modifiers,
+ description=description,
+ group=group},
+ widget.additional_hotkeys
+ )
+ end
+ end
+ end
+ sort_hotkeys(widget.additional_hotkeys)
+end
+
+
+return widget
+end
+
+local function get_default_widget()
+ if not widget_module.default_widget then
+ widget_module.default_widget = widget_module.new()
+ end
+ return widget_module.default_widget
+end
+
+--- Show popup with hotkeys help (default widget instance will be used).
+-- @tparam[opt] client c Client.
+-- @tparam[opt] screen s Screen.
+function widget_module.show_help(...)
+ return get_default_widget().show_help(...)
+end
+
+--- Add hotkey descriptions for third-party applications
+-- (default widget instance will be used).
+-- @tparam table hotkeys Table with bindings,
+-- see `awful.hotkeys_popup.key.vim` as an example.
+function widget_module.add_hotkeys(...)
+ return get_default_widget().add_hotkeys(...)
+end
+
+return widget_module
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/init.lua b/lib/awful/init.lua
new file mode 100644
index 0000000..840897e
--- /dev/null
+++ b/lib/awful/init.lua
@@ -0,0 +1,64 @@
+---------------------------------------------------------------------------
+--- AWesome Functions very UsefuL
+--
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2008 Julien Danjou
+-- @module awful
+---------------------------------------------------------------------------
+
+-- TODO: This is a hack for backwards-compatibility with 3.5, remove!
+local util = require("awful.util")
+local gtimer = require("gears.timer")
+function timer(...) -- luacheck: ignore
+ util.deprecate("gears.timer")
+ return gtimer(...)
+end
+
+--TODO: This is a hack for backwards-compatibility with 3.5, remove!
+-- Set awful.util.spawn* and awful.util.pread.
+local spawn = require("awful.spawn")
+
+util.spawn = function(...)
+ util.deprecate("awful.spawn")
+ return spawn.spawn(...)
+end
+
+util.spawn_with_shell = function(...)
+ util.deprecate("awful.spawn.with_shell")
+ return spawn.with_shell(...)
+end
+
+util.pread = function()
+ util.deprecate("Use io.popen() directly or look at awful.spawn.easy_async() "
+ .. "for an asynchronous alternative")
+ return ""
+end
+
+return
+{
+ client = require("awful.client");
+ completion = require("awful.completion");
+ layout = require("awful.layout");
+ placement = require("awful.placement");
+ prompt = require("awful.prompt");
+ screen = require("awful.screen");
+ tag = require("awful.tag");
+ util = require("awful.util");
+ widget = require("awful.widget");
+ keygrabber = require("awful.keygrabber");
+ menu = require("awful.menu");
+ mouse = require("awful.mouse");
+ remote = require("awful.remote");
+ key = require("awful.key");
+ button = require("awful.button");
+ wibar = require("awful.wibar");
+ wibox = require("awful.wibox");
+ startup_notification = require("awful.startup_notification");
+ tooltip = require("awful.tooltip");
+ ewmh = require("awful.ewmh");
+ titlebar = require("awful.titlebar");
+ rules = require("awful.rules");
+ spawn = spawn;
+}
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/key.lua b/lib/awful/key.lua
new file mode 100644
index 0000000..c3ce359
--- /dev/null
+++ b/lib/awful/key.lua
@@ -0,0 +1,136 @@
+---------------------------------------------------------------------------
+--- Create easily new key objects ignoring certain modifiers.
+--
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2009 Julien Danjou
+-- @module awful.key
+---------------------------------------------------------------------------
+
+-- Grab environment we need
+local setmetatable = setmetatable
+local ipairs = ipairs
+local capi = { key = key, root = root }
+local util = require("awful.util")
+
+
+
+local key = { mt = {}, hotkeys = {} }
+
+
+--- Modifiers to ignore.
+-- By default this is initialized as { "Lock", "Mod2" }
+-- so the Caps Lock or Num Lock modifier are not taking into account by awesome
+-- when pressing keys.
+-- @name awful.key.ignore_modifiers
+-- @class table
+key.ignore_modifiers = { "Lock", "Mod2" }
+
+--- Convert the modifiers into pc105 key names
+local conversion = {
+ mod4 = "Super_L",
+ control = "Control_L",
+ shift = "Shift_L",
+ mod1 = "Alt_L",
+}
+
+--- Execute a key combination.
+-- If an awesome keybinding is assigned to the combination, it should be
+-- executed.
+-- @see root.fake_input
+-- @tparam table mod A modified table. Valid modifiers are: Any, Mod1,
+-- Mod2, Mod3, Mod4, Mod5, Shift, Lock and Control.
+-- @tparam string k The key
+function key.execute(mod, k)
+ for _, v in ipairs(mod) do
+ local m = conversion[v:lower()]
+ if m then
+ root.fake_input("key_press", m)
+ end
+ end
+
+ root.fake_input("key_press" , k)
+ root.fake_input("key_release", k)
+
+ for _, v in ipairs(mod) do
+ local m = conversion[v:lower()]
+ if m then
+ root.fake_input("key_release", m)
+ end
+ end
+end
+
+--- Create a new key to use as binding.
+-- This function is useful to create several keys from one, because it will use
+-- the ignore_modifier variable to create several keys with and without the
+-- ignored modifiers activated.
+-- For example if you want to ignore CapsLock in your keybinding (which is
+-- ignored by default by this function), creating a key binding with this
+-- function will return 2 key objects: one with CapsLock on, and another one
+-- with CapsLock off.
+-- @see key.key
+-- @tparam table mod A list of modifier keys. Valid modifiers are: Any, Mod1,
+-- Mod2, Mod3, Mod4, Mod5, Shift, Lock and Control.
+-- @tparam string _key The key to trigger an event.
+-- @tparam function press Callback for when the key is pressed.
+-- @tparam[opt] function release Callback for when the key is released.
+-- @tparam table data User data for key,
+-- for example {description="select next tag", group="tag"}.
+-- @treturn table A table with one or several key objects.
+function key.new(mod, _key, press, release, data)
+ if type(release)=='table' then
+ data=release
+ release=nil
+ end
+ local ret = {}
+ local subsets = util.subsets(key.ignore_modifiers)
+ for _, set in ipairs(subsets) do
+ ret[#ret + 1] = capi.key({ modifiers = util.table.join(mod, set),
+ key = _key })
+ if press then
+ ret[#ret]:connect_signal("press", function(_, ...) press(...) end)
+ end
+ if release then
+ ret[#ret]:connect_signal("release", function(_, ...) release(...) end)
+ end
+ end
+
+ -- append custom userdata (like description) to a hotkey
+ data = data or {}
+ data.mod = mod
+ data.key = _key
+ table.insert(key.hotkeys, data)
+ data.execute = function(_) key.execute(mod, _key) end
+
+ return ret
+end
+
+--- Compare a key object with modifiers and key.
+-- @param _key The key object.
+-- @param pressed_mod The modifiers to compare with.
+-- @param pressed_key The key to compare with.
+function key.match(_key, pressed_mod, pressed_key)
+ -- First, compare key.
+ if pressed_key ~= _key.key then return false end
+ -- Then, compare mod
+ local mod = _key.modifiers
+ -- For each modifier of the key object, check that the modifier has been
+ -- pressed.
+ for _, m in ipairs(mod) do
+ -- Has it been pressed?
+ if not util.table.hasitem(pressed_mod, m) then
+ -- No, so this is failure!
+ return false
+ end
+ end
+ -- If the number of pressed modifier is ~=, it is probably >, so this is not
+ -- the same, return false.
+ return #pressed_mod == #mod
+end
+
+function key.mt:__call(...)
+ return key.new(...)
+end
+
+return setmetatable(key, key.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/keygrabber.lua b/lib/awful/keygrabber.lua
new file mode 100644
index 0000000..4351f5b
--- /dev/null
+++ b/lib/awful/keygrabber.lua
@@ -0,0 +1,96 @@
+---------------------------------------------------------------------------
+--- Keygrabber Stack
+--
+-- @author dodo
+-- @copyright 2012 dodo
+-- @module awful.keygrabber
+---------------------------------------------------------------------------
+
+local ipairs = ipairs
+local table = table
+local capi = {
+ keygrabber = keygrabber }
+
+local keygrabber = {}
+
+-- Private data
+local grabbers = {}
+local keygrabbing = false
+
+
+local function grabber(mod, key, event)
+ for _, keygrabber_function in ipairs(grabbers) do
+ -- continue if the grabber explicitly returns false
+ if keygrabber_function(mod, key, event) ~= false then
+ break
+ end
+ end
+end
+
+--- Stop grabbing the keyboard for the provided callback.
+-- When no callback is given, the last grabber gets removed (last one added to
+-- the stack).
+-- @param g The key grabber that must be removed.
+function keygrabber.stop(g)
+ for i, v in ipairs(grabbers) do
+ if v == g then
+ table.remove(grabbers, i)
+ break
+ end
+ end
+ -- Stop the global key grabber if the last grabber disappears from stack.
+ if #grabbers == 0 then
+ keygrabbing = false
+ capi.keygrabber.stop()
+ end
+end
+
+---
+-- Grab keyboard input and read pressed keys, calling the least callback
+-- function from the stack at each keypress, until the stack is empty.
+--
+-- Calling run with the same callback again will bring the callback
+-- to the top of the stack.
+--
+-- The callback function receives three arguments:
+--
+-- * a table containing modifiers keys
+-- * a string with the pressed key
+-- * a string with either "press" or "release" to indicate the event type
+--
+-- A callback can return `false` to pass the events to the next
+-- keygrabber in the stack.
+-- @param g The key grabber callback that will get the key events until it will be deleted or a new grabber is added.
+-- @return the given callback `g`.
+-- @usage
+-- -- The following function can be bound to a key, and be used to resize a
+-- -- client using the keyboard.
+--
+-- function resize(c)
+-- local grabber = awful.keygrabber.run(function(mod, key, event)
+-- if event == "release" then return end
+--
+-- if key == 'Up' then awful.client.moveresize(0, 0, 0, 5, c)
+-- elseif key == 'Down' then awful.client.moveresize(0, 0, 0, -5, c)
+-- elseif key == 'Right' then awful.client.moveresize(0, 0, 5, 0, c)
+-- elseif key == 'Left' then awful.client.moveresize(0, 0, -5, 0, c)
+-- else awful.keygrabber.stop(grabber)
+-- end
+-- end)
+-- end
+function keygrabber.run(g)
+ -- Remove the grabber if it is in the stack.
+ keygrabber.stop(g)
+ -- Record the grabber that has been added most recently.
+ table.insert(grabbers, 1, g)
+ -- Start the keygrabber if it is not running already.
+ if not keygrabbing then
+ keygrabbing = true
+ capi.keygrabber.run(grabber)
+ end
+ return g
+end
+
+return keygrabber
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/layout/init.lua b/lib/awful/layout/init.lua
new file mode 100644
index 0000000..dfce562
--- /dev/null
+++ b/lib/awful/layout/init.lua
@@ -0,0 +1,323 @@
+---------------------------------------------------------------------------
+--- Layout module for awful
+--
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2008 Julien Danjou
+-- @module awful.layout
+---------------------------------------------------------------------------
+
+-- Grab environment we need
+local ipairs = ipairs
+local type = type
+local util = require("awful.util")
+local capi = {
+ screen = screen,
+ mouse = mouse,
+ awesome = awesome,
+ client = client,
+ tag = tag
+}
+local tag = require("awful.tag")
+local client = require("awful.client")
+local ascreen = require("awful.screen")
+local timer = require("gears.timer")
+
+local function get_screen(s)
+ return s and capi.screen[s]
+end
+
+local layout = {}
+
+layout.suit = require("awful.layout.suit")
+
+layout.layouts = {
+ layout.suit.floating,
+ layout.suit.tile,
+ layout.suit.tile.left,
+ layout.suit.tile.bottom,
+ layout.suit.tile.top,
+ layout.suit.fair,
+ layout.suit.fair.horizontal,
+ layout.suit.spiral,
+ layout.suit.spiral.dwindle,
+ layout.suit.max,
+ layout.suit.max.fullscreen,
+ layout.suit.magnifier,
+ layout.suit.corner.nw,
+ layout.suit.corner.ne,
+ layout.suit.corner.sw,
+ layout.suit.corner.se,
+}
+
+--- The default list of layouts.
+--
+-- The default value is:
+--
+-- awful.layout.suit.floating,
+-- awful.layout.suit.tile,
+-- awful.layout.suit.tile.left,
+-- awful.layout.suit.tile.bottom,
+-- awful.layout.suit.tile.top,
+-- awful.layout.suit.fair,
+-- awful.layout.suit.fair.horizontal,
+-- awful.layout.suit.spiral,
+-- awful.layout.suit.spiral.dwindle,
+-- awful.layout.suit.max,
+-- awful.layout.suit.max.fullscreen,
+-- awful.layout.suit.magnifier,
+-- awful.layout.suit.corner.nw,
+-- awful.layout.suit.corner.ne,
+-- awful.layout.suit.corner.sw,
+-- awful.layout.suit.corner.se,
+--
+-- @field layout.layouts
+
+
+-- This is a special lock used by the arrange function.
+-- This avoids recurring call by emitted signals.
+local arrange_lock = false
+-- Delay one arrange call per screen.
+local delayed_arrange = {}
+
+--- Get the current layout.
+-- @param screen The screen.
+-- @return The layout function.
+function layout.get(screen)
+ screen = screen or capi.mouse.screen
+ local t = get_screen(screen).selected_tag
+ return tag.getproperty(t, "layout") or layout.suit.floating
+end
+
+--- Change the layout of the current tag.
+-- @param i Relative index.
+-- @param s The screen.
+-- @param[opt] layouts A table of layouts.
+function layout.inc(i, s, layouts)
+ if type(i) == "table" then
+ -- Older versions of this function had arguments (layouts, i, s), but
+ -- this was changed so that 'layouts' can be an optional parameter
+ layouts, i, s = i, s, layouts
+ end
+ s = get_screen(s or ascreen.focused())
+ local t = s.selected_tag
+ layouts = layouts or layout.layouts
+ if t then
+ local curlayout = layout.get(s)
+ local curindex
+ for k, v in ipairs(layouts) do
+ if v == curlayout or curlayout._type == v then
+ curindex = k
+ break
+ end
+ end
+ if not curindex then
+ -- Safety net: handle cases where another reference of the layout
+ -- might be given (e.g. when (accidentally) cloning it).
+ for k, v in ipairs(layouts) do
+ if v.name == curlayout.name then
+ curindex = k
+ break
+ end
+ end
+ end
+ if curindex then
+ local newindex = util.cycle(#layouts, curindex + i)
+ layout.set(layouts[newindex], t)
+ end
+ end
+end
+
+--- Set the layout function of the current tag.
+-- @param _layout Layout name.
+-- @tparam[opt=mouse.screen.selected_tag] tag t The tag to modify.
+function layout.set(_layout, t)
+ t = t or capi.mouse.screen.selected_tag
+ t.layout = _layout
+end
+
+--- Get the layout parameters used for the screen
+--
+-- This should give the same result as "arrange", but without the "geometries"
+-- parameter, as this is computed during arranging.
+--
+-- If `t` is given, `screen` is ignored, if none are given, the mouse screen is
+-- used.
+--
+-- @tparam[opt] tag t The tag to query
+-- @param[opt] screen The screen
+-- @treturn table A table with the workarea (x, y, width, height), the screen
+-- geometry (x, y, width, height), the clients, the screen and sometime, a
+-- "geometries" table with client as keys and geometry as value
+function layout.parameters(t, screen)
+ screen = get_screen(screen)
+ t = t or screen.selected_tag
+
+ screen = get_screen(t and t.screen or 1)
+
+ local p = {}
+
+ local clients = client.tiled(screen)
+ local gap_single_client = true
+
+ if(t and t.gap_single_client ~= nil) then
+ gap_single_client = t.gap_single_client
+ end
+
+ local min_clients = gap_single_client and 1 or 2
+ local useless_gap = t and (#clients >= min_clients and t.gap or 0) or 0
+
+ p.workarea = screen:get_bounding_geometry {
+ honor_padding = true,
+ honor_workarea = true,
+ margins = useless_gap,
+ }
+
+ p.geometry = screen.geometry
+ p.clients = clients
+ p.screen = screen.index
+ p.padding = screen.padding
+ p.useless_gap = useless_gap
+
+ return p
+end
+
+--- Arrange a screen using its current layout.
+-- @param screen The screen to arrange.
+function layout.arrange(screen)
+ screen = get_screen(screen)
+ if not screen or delayed_arrange[screen] then return end
+ delayed_arrange[screen] = true
+
+ timer.delayed_call(function()
+ if not screen.valid then
+ -- Screen was removed
+ delayed_arrange[screen] = nil
+ return
+ end
+ if arrange_lock then return end
+ arrange_lock = true
+
+ local p = layout.parameters(nil, screen)
+
+ local useless_gap = p.useless_gap
+
+ p.geometries = setmetatable({}, {__mode = "k"})
+ layout.get(screen).arrange(p)
+ for c, g in pairs(p.geometries) do
+ g.width = math.max(1, g.width - c.border_width * 2 - useless_gap * 2)
+ g.height = math.max(1, g.height - c.border_width * 2 - useless_gap * 2)
+ g.x = g.x + useless_gap
+ g.y = g.y + useless_gap
+ c:geometry(g)
+ end
+ arrange_lock = false
+ delayed_arrange[screen] = nil
+
+ screen:emit_signal("arrange")
+ end)
+end
+
+--- Get the current layout name.
+-- @param _layout The layout.
+-- @return The layout name.
+function layout.getname(_layout)
+ _layout = _layout or layout.get()
+ return _layout.name
+end
+
+local function arrange_prop_nf(obj)
+ if not client.object.get_floating(obj) then
+ layout.arrange(obj.screen)
+ end
+end
+
+local function arrange_prop(obj) layout.arrange(obj.screen) end
+
+capi.client.connect_signal("property::size_hints_honor", arrange_prop_nf)
+capi.client.connect_signal("property::struts", arrange_prop)
+capi.client.connect_signal("property::minimized", arrange_prop_nf)
+capi.client.connect_signal("property::sticky", arrange_prop_nf)
+capi.client.connect_signal("property::fullscreen", arrange_prop_nf)
+capi.client.connect_signal("property::maximized_horizontal", arrange_prop_nf)
+capi.client.connect_signal("property::maximized_vertical", arrange_prop_nf)
+capi.client.connect_signal("property::border_width", arrange_prop_nf)
+capi.client.connect_signal("property::hidden", arrange_prop_nf)
+capi.client.connect_signal("property::floating", arrange_prop)
+capi.client.connect_signal("property::geometry", arrange_prop_nf)
+capi.client.connect_signal("property::screen", function(c, old_screen)
+ if old_screen then
+ layout.arrange(old_screen)
+ end
+ layout.arrange(c.screen)
+end)
+
+local function arrange_tag(t)
+ layout.arrange(t.screen)
+end
+
+capi.tag.connect_signal("property::master_width_factor", arrange_tag)
+capi.tag.connect_signal("property::master_count", arrange_tag)
+capi.tag.connect_signal("property::column_count", arrange_tag)
+capi.tag.connect_signal("property::layout", arrange_tag)
+capi.tag.connect_signal("property::windowfact", arrange_tag)
+capi.tag.connect_signal("property::selected", arrange_tag)
+capi.tag.connect_signal("property::activated", arrange_tag)
+capi.tag.connect_signal("property::useless_gap", arrange_tag)
+capi.tag.connect_signal("property::master_fill_policy", arrange_tag)
+capi.tag.connect_signal("tagged", arrange_tag)
+
+capi.screen.connect_signal("property::workarea", layout.arrange)
+capi.screen.connect_signal("padding", layout.arrange)
+
+capi.client.connect_signal("raised", function(c) layout.arrange(c.screen) end)
+capi.client.connect_signal("lowered", function(c) layout.arrange(c.screen) end)
+capi.client.connect_signal("list", function()
+ for screen in capi.screen do
+ layout.arrange(screen)
+ end
+ end)
+
+--- Default handler for `request::geometry` signals for tiled clients with
+-- the "mouse.move" context.
+-- @tparam client c The client
+-- @tparam string context The context
+-- @tparam table hints Additional hints
+function layout.move_handler(c, context, hints) --luacheck: no unused args
+ -- Quit if it isn't a mouse.move on a tiled layout, that's handled elsewhere
+ if c.floating then return end
+ if context ~= "mouse.move" then return end
+
+ if capi.mouse.screen ~= c.screen then
+ c.screen = capi.mouse.screen
+ end
+
+ local l = c.screen.selected_tag and c.screen.selected_tag.layout or nil
+ if l == layout.suit.floating then return end
+
+ local c_u_m = capi.mouse.current_client
+ if c_u_m and not c_u_m.floating then
+ if c_u_m ~= c then
+ c:swap(c_u_m)
+ end
+ end
+end
+
+capi.client.connect_signal("request::geometry", layout.move_handler)
+
+-- When a screen is moved, make (floating) clients follow it
+capi.screen.connect_signal("property::geometry", function(s, old_geom)
+ local geom = s.geometry
+ local xshift = geom.x - old_geom.x
+ local yshift = geom.y - old_geom.y
+ for _, c in ipairs(capi.client.get(s)) do
+ local cgeom = c:geometry()
+ c:geometry({
+ x = cgeom.x + xshift,
+ y = cgeom.y + yshift
+ })
+ end
+end)
+
+return layout
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/layout/suit/corner.lua b/lib/awful/layout/suit/corner.lua
new file mode 100644
index 0000000..4b746c9
--- /dev/null
+++ b/lib/awful/layout/suit/corner.lua
@@ -0,0 +1,204 @@
+---------------------------------------------------------------------------
+-- Corner layout.
+-- Display master client in a corner of the screen, and slaves in one
+-- column and one row around the master.
+-- See Pull Request for example : https://github.com/awesomeWM/awesome/pull/251
+-- @module awful.layout
+-- @author Alexis Brenon &lt;brenon.alexis+awesomewm@gmail.com&gt;
+-- @copyright 2015 Alexis Brenon
+
+-- Grab environment we need
+local ipairs = ipairs
+local math = math
+local capi = {screen = screen}
+
+--- The cornernw layout layoutbox icon.
+-- @beautiful beautiful.layout_cornernw
+-- @param surface
+-- @see gears.surface
+
+--- The cornerne layout layoutbox icon.
+-- @beautiful beautiful.layout_cornerne
+-- @param surface
+-- @see gears.surface
+
+--- The cornersw layout layoutbox icon.
+-- @beautiful beautiful.layout_cornersw
+-- @param surface
+-- @see gears.surface
+
+--- The cornerse layout layoutbox icon.
+-- @beautiful beautiful.layout_cornerse
+-- @param surface
+-- @see gears.surface
+
+-- Actually arrange clients of p.clients for corner layout
+-- @param p Mandatory table containing required informations for layouts
+-- (clients to arrange, workarea geometry, etc.)
+-- @param orientation String indicating in which corner is the master window.
+-- Available values are : NE, NW, SW, SE
+local function do_corner(p, orientation)
+ local t = p.tag or capi.screen[p.screen].selected_tag
+ local wa = p.workarea
+ local cls = p.clients
+
+ if #cls == 0 then return end
+
+ local master = {}
+ local column = {}
+ local row = {}
+ -- Use the nmaster field of the tag in a cheaty way
+ local row_privileged = ((cls[1].screen.selected_tag.master_count % 2) == 0)
+
+ local master_factor = cls[1].screen.selected_tag.master_width_factor
+ master.width = master_factor * wa.width
+ master.height = master_factor * wa.height
+
+ local number_privileged_win = math.ceil((#cls - 1)/2)
+ local number_unprivileged_win = (#cls - 1) - number_privileged_win
+
+ -- Define some obvious parameters
+ column.width = wa.width - master.width
+ column.x_increment = 0
+ row.height = wa.height - master.height
+ row.y_increment = 0
+
+ -- Place master at the right place and move row and column accordingly
+ column.y = wa.y
+ row.x = wa.x
+ if orientation:match('N.') then
+ master.y = wa.y
+ row.y = master.y + master.height
+ elseif orientation:match('S.') then
+ master.y = wa.y + wa.height - master.height
+ row.y = wa.y
+ end
+ if orientation:match('.W') then
+ master.x = wa.x
+ column.x = master.x + master.width
+ elseif orientation:match('.E') then
+ master.x = wa.x + wa.width - master.width
+ column.x = wa.x
+ end
+ -- At this point, master is in a corner
+ -- but row and column are overlayed in the opposite corner...
+
+ -- Reduce the unprivileged slaves to remove overlay
+ -- and define actual width and height
+ if row_privileged then
+ row.width = wa.width
+ row.number_win = number_privileged_win
+ column.y = master.y
+ column.height = master.height
+ column.number_win = number_unprivileged_win
+ else
+ column.height = wa.height
+ column.number_win = number_privileged_win
+ row.x = master.x
+ row.width = master.width
+ row.number_win = number_unprivileged_win
+ end
+
+ column.win_height = column.height/column.number_win
+ column.win_width = column.width
+ column.y_increment = column.win_height
+ column.win_idx = 0
+
+ row.win_width = row.width/row.number_win
+ row.win_height = row.height
+ row.x_increment = row.win_width
+ row.win_idx = 0
+
+ -- Extend master if there is only a few windows and "expand" policy is set
+ if #cls < 3 then
+ if row_privileged then
+ master.x = wa.x
+ master.width = wa.width
+ else
+ master.y = wa.y
+ master.height = wa.height
+ end
+ if #cls < 2 then
+ if t.master_fill_policy == "expand" then
+ master = wa
+ else
+ master.x = master.x + (wa.width - master.width)/2
+ master.y = master.y + (wa.height - master.height)/2
+ end
+ end
+ end
+
+ for i, c in ipairs(cls) do
+ local g
+ -- Handle master window
+ if i == 1 then
+ g = {
+ x = master.x,
+ y = master.y,
+ width = master.width,
+ height = master.height
+ }
+ -- handle column windows
+ elseif i % 2 == 0 then
+ g = {
+ x = column.x + column.win_idx * column.x_increment,
+ y = column.y + column.win_idx * column.y_increment,
+ width = column.win_width,
+ height = column.win_height
+ }
+ column.win_idx = column.win_idx + 1
+ else
+ g = {
+ x = row.x + row.win_idx * row.x_increment,
+ y = row.y + row.win_idx * row.y_increment,
+ width = row.win_width,
+ height = row.win_height
+ }
+ row.win_idx = row.win_idx + 1
+ end
+ p.geometries[c] = g
+ end
+end
+
+local corner = {}
+corner.row_privileged = false
+
+--- Corner layout.
+-- Display master client in a corner of the screen, and slaves in one
+-- column and one row around the master.
+-- @clientlayout awful.layout.suit.corner.nw
+corner.nw = {
+ name = "cornernw",
+ arrange = function (p) return do_corner(p, "NW") end
+ }
+
+--- Corner layout.
+-- Display master client in a corner of the screen, and slaves in one
+-- column and one row around the master.
+-- @clientlayout awful.layout.suit.corner.ne
+corner.ne = {
+ name = "cornerne",
+ arrange = function (p) return do_corner(p, "NE") end
+ }
+
+--- Corner layout.
+-- Display master client in a corner of the screen, and slaves in one
+-- column and one row around the master.
+-- @clientlayout awful.layout.suit.corner.sw
+corner.sw = {
+ name = "cornersw",
+ arrange = function (p) return do_corner(p, "SW") end
+ }
+
+--- Corner layout.
+-- Display master client in a corner of the screen, and slaves in one
+-- column and one row around the master.
+-- @clientlayout awful.layout.suit.corner.se
+corner.se = {
+ name = "cornerse",
+ arrange = function (p) return do_corner(p, "SE") end
+ }
+
+return corner
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/layout/suit/fair.lua b/lib/awful/layout/suit/fair.lua
new file mode 100644
index 0000000..161b6ed
--- /dev/null
+++ b/lib/awful/layout/suit/fair.lua
@@ -0,0 +1,108 @@
+---------------------------------------------------------------------------
+--- Fair layouts module for awful.
+--
+-- @author Josh Komoroske
+-- @copyright 2012 Josh Komoroske
+-- @module awful.layout
+---------------------------------------------------------------------------
+
+-- Grab environment we need
+local ipairs = ipairs
+local math = math
+
+--- The fairh layout layoutbox icon.
+-- @beautiful beautiful.layout_fairh
+-- @param surface
+-- @see gears.surface
+
+--- The fairv layout layoutbox icon.
+-- @beautiful beautiful.layout_fairv
+-- @param surface
+-- @see gears.surface
+
+local fair = {}
+
+local function do_fair(p, orientation)
+ local wa = p.workarea
+ local cls = p.clients
+
+ -- Swap workarea dimensions, if our orientation is "east"
+ if orientation == 'east' then
+ wa.width, wa.height = wa.height, wa.width
+ wa.x, wa.y = wa.y, wa.x
+ end
+
+ if #cls > 0 then
+ local rows, cols
+ if #cls == 2 then
+ rows, cols = 1, 2
+ else
+ rows = math.ceil(math.sqrt(#cls))
+ cols = math.ceil(#cls / rows)
+ end
+
+ for k, c in ipairs(cls) do
+ k = k - 1
+ local g = {}
+
+ local row, col
+ row = k % rows
+ col = math.floor(k / rows)
+
+ local lrows, lcols
+ if k >= rows * cols - rows then
+ lrows = #cls - (rows * cols - rows)
+ lcols = cols
+ else
+ lrows = rows
+ lcols = cols
+ end
+
+ if row == lrows - 1 then
+ g.height = wa.height - math.ceil(wa.height / lrows) * row
+ g.y = wa.height - g.height
+ else
+ g.height = math.ceil(wa.height / lrows)
+ g.y = g.height * row
+ end
+
+ if col == lcols - 1 then
+ g.width = wa.width - math.ceil(wa.width / lcols) * col
+ g.x = wa.width - g.width
+ else
+ g.width = math.ceil(wa.width / lcols)
+ g.x = g.width * col
+ end
+
+ g.y = g.y + wa.y
+ g.x = g.x + wa.x
+
+ -- Swap window dimensions, if our orientation is "east"
+ if orientation == 'east' then
+ g.width, g.height = g.height, g.width
+ g.x, g.y = g.y, g.x
+ end
+
+ p.geometries[c] = g
+ end
+ end
+end
+
+--- Horizontal fair layout.
+-- @param screen The screen to arrange.
+fair.horizontal = {}
+fair.horizontal.name = "fairh"
+function fair.horizontal.arrange(p)
+ return do_fair(p, "east")
+end
+
+--- Vertical fair layout.
+-- @param screen The screen to arrange.
+fair.name = "fairv"
+function fair.arrange(p)
+ return do_fair(p, "south")
+end
+
+return fair
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/layout/suit/floating.lua b/lib/awful/layout/suit/floating.lua
new file mode 100644
index 0000000..b769435
--- /dev/null
+++ b/lib/awful/layout/suit/floating.lua
@@ -0,0 +1,112 @@
+---------------------------------------------------------------------------
+--- Dummy function for floating layout
+--
+-- @author Gregor Best
+-- @copyright 2008 Gregor Best
+-- @module awful.layout
+---------------------------------------------------------------------------
+
+-- Grab environment we need
+local ipairs = ipairs
+local capi =
+{
+ mouse = mouse,
+ mousegrabber = mousegrabber
+}
+
+--- The floating layout layoutbox icon.
+-- @beautiful beautiful.layout_floating
+-- @param surface
+-- @see gears.surface
+
+local floating = {}
+
+--- Jump mouse cursor to the client's corner when resizing it.
+floating.resize_jump_to_corner = true
+
+function floating.mouse_resize_handler(c, corner, x, y)
+ local g = c:geometry()
+
+ -- Do not allow maximized clients to be resized by mouse
+ local fixed_x = c.maximized_horizontal
+ local fixed_y = c.maximized_vertical
+
+ local prev_coords = {}
+ local coordinates_delta = {x=0,y=0}
+ if floating.resize_jump_to_corner then
+ -- Warp mouse pointer
+ capi.mouse.coords({ x = x, y = y })
+ else
+ local corner_x, corner_y = x, y
+ local mouse_coords = capi.mouse.coords()
+ x = mouse_coords.x
+ y = mouse_coords.y
+ coordinates_delta = {x=corner_x-x,y=corner_y-y}
+ end
+
+ capi.mousegrabber.run(function (_mouse)
+ if not c.valid then return false end
+
+ _mouse.x = _mouse.x + coordinates_delta.x
+ _mouse.y = _mouse.y + coordinates_delta.y
+ for _, v in ipairs(_mouse.buttons) do
+ if v then
+ local ng
+ prev_coords = { x =_mouse.x, y = _mouse.y }
+ if corner == "bottom_right" then
+ ng = { width = _mouse.x - g.x,
+ height = _mouse.y - g.y }
+ elseif corner == "bottom_left" then
+ ng = { x = _mouse.x,
+ width = (g.x + g.width) - _mouse.x,
+ height = _mouse.y - g.y }
+ elseif corner == "top_left" then
+ ng = { x = _mouse.x,
+ width = (g.x + g.width) - _mouse.x,
+ y = _mouse.y,
+ height = (g.y + g.height) - _mouse.y }
+ else
+ ng = { width = _mouse.x - g.x,
+ y = _mouse.y,
+ height = (g.y + g.height) - _mouse.y }
+ end
+ if ng.width <= 0 then ng.width = nil end
+ if ng.height <= 0 then ng.height = nil end
+ if fixed_x then ng.width = g.width ng.x = g.x end
+ if fixed_y then ng.height = g.height ng.y = g.y end
+ c:geometry(ng)
+ -- Get real geometry that has been applied
+ -- in case we honor size hints
+ -- XXX: This should be rewritten when size
+ -- hints are available from Lua.
+ local rg = c:geometry()
+
+ if corner == "bottom_right" then
+ ng = {}
+ elseif corner == "bottom_left" then
+ ng = { x = (g.x + g.width) - rg.width }
+ elseif corner == "top_left" then
+ ng = { x = (g.x + g.width) - rg.width,
+ y = (g.y + g.height) - rg.height }
+ else
+ ng = { y = (g.y + g.height) - rg.height }
+ end
+ c:geometry({ x = ng.x, y = ng.y })
+ return true
+ end
+ end
+ return prev_coords.x == _mouse.x and prev_coords.y == _mouse.y
+ end, corner .. "_corner")
+end
+
+function floating.arrange()
+end
+
+--- The floating layout.
+-- @clientlayout awful.layout.suit.
+
+floating.name = "floating"
+
+return floating
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/layout/suit/init.lua b/lib/awful/layout/suit/init.lua
new file mode 100644
index 0000000..57a49fa
--- /dev/null
+++ b/lib/awful/layout/suit/init.lua
@@ -0,0 +1,19 @@
+---------------------------------------------------------------------------
+--- Suits for awful
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2008 Julien Danjou
+-- @module awful.layout
+---------------------------------------------------------------------------
+
+return
+{
+ corner = require("awful.layout.suit.corner");
+ max = require("awful.layout.suit.max");
+ tile = require("awful.layout.suit.tile");
+ fair = require("awful.layout.suit.fair");
+ floating = require("awful.layout.suit.floating");
+ magnifier = require("awful.layout.suit.magnifier");
+ spiral = require("awful.layout.suit.spiral");
+}
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/layout/suit/magnifier.lua b/lib/awful/layout/suit/magnifier.lua
new file mode 100644
index 0000000..f30d7ee
--- /dev/null
+++ b/lib/awful/layout/suit/magnifier.lua
@@ -0,0 +1,147 @@
+---------------------------------------------------------------------------
+--- Magnifier layout module for awful
+--
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2008 Julien Danjou
+-- @module awful.layout
+---------------------------------------------------------------------------
+
+-- Grab environment we need
+local ipairs = ipairs
+local math = math
+local capi =
+{
+ client = client,
+ screen = screen,
+ mouse = mouse,
+ mousegrabber = mousegrabber
+}
+
+--- The magnifier layout layoutbox icon.
+-- @beautiful beautiful.layout_magnifier
+-- @param surface
+-- @see gears.surface
+
+local magnifier = {}
+
+function magnifier.mouse_resize_handler(c, corner, x, y)
+ capi.mouse.coords({ x = x, y = y })
+
+ local wa = c.screen.workarea
+ local center_x = wa.x + wa.width / 2
+ local center_y = wa.y + wa.height / 2
+ local maxdist_pow = (wa.width^2 + wa.height^2) / 4
+
+ local prev_coords = {}
+ capi.mousegrabber.run(function (_mouse)
+ if not c.valid then return false end
+
+ for _, v in ipairs(_mouse.buttons) do
+ if v then
+ prev_coords = { x =_mouse.x, y = _mouse.y }
+ local dx = center_x - _mouse.x
+ local dy = center_y - _mouse.y
+ local dist = dx^2 + dy^2
+
+ -- New master width factor
+ local mwfact = dist / maxdist_pow
+ c.screen.selected_tag.master_width_factor
+ = math.min(math.max(0.01, mwfact), 0.99)
+ return true
+ end
+ end
+ return prev_coords.x == _mouse.x and prev_coords.y == _mouse.y
+ end, corner .. "_corner")
+end
+
+function magnifier.arrange(p)
+ -- Fullscreen?
+ local area = p.workarea
+ local cls = p.clients
+ local focus = p.focus or capi.client.focus
+ local t = p.tag or capi.screen[p.screen].selected_tag
+ local mwfact = t.master_width_factor
+ local fidx
+
+ -- Check that the focused window is on the right screen
+ if focus and focus.screen ~= p.screen then focus = nil end
+
+ -- 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
+
+ -- Abort if no clients are present
+ if not focus then return end
+
+ local geometry = {}
+ if #cls > 1 then
+ geometry.width = area.width * math.sqrt(mwfact)
+ geometry.height = area.height * math.sqrt(mwfact)
+ geometry.x = area.x + (area.width - geometry.width) / 2
+ geometry.y = area.y + (area.height - geometry.height) /2
+ else
+ geometry.x = area.x
+ geometry.y = area.y
+ geometry.width = area.width
+ geometry.height = area.height
+ end
+
+ local g = {
+ x = geometry.x,
+ y = geometry.y,
+ width = geometry.width,
+ height = geometry.height
+ }
+ p.geometries[focus] = g
+
+ if #cls > 1 then
+ geometry.x = area.x
+ geometry.y = area.y
+ geometry.height = area.height / (#cls - 1)
+ geometry.width = area.width
+
+ -- We don't know the focus window index. Try to find it.
+ if not fidx then
+ for k, c in ipairs(cls) do
+ if c == focus then
+ fidx = k
+ break
+ end
+ end
+ end
+
+ -- First move clients that are before focused client.
+ for k = fidx + 1, #cls do
+ p.geometries[cls[k]] = {
+ x = geometry.x,
+ y = geometry.y,
+ width = geometry.width,
+ height = geometry.height
+ }
+ geometry.y = geometry.y + geometry.height
+ end
+
+ -- Then move clients that are after focused client.
+ -- So the next focused window will be the one at the top of the screen.
+ for k = 1, fidx - 1 do
+ p.geometries[cls[k]] = {
+ x = geometry.x,
+ y = geometry.y,
+ width = geometry.width,
+ height = geometry.height
+ }
+ geometry.y = geometry.y + geometry.height
+ end
+ end
+end
+
+--- The magnifier layout.
+-- @clientlayout awful.layout.suit.magnifier
+
+magnifier.name = "magnifier"
+
+return magnifier
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/layout/suit/max.lua b/lib/awful/layout/suit/max.lua
new file mode 100644
index 0000000..2cd1812
--- /dev/null
+++ b/lib/awful/layout/suit/max.lua
@@ -0,0 +1,61 @@
+---------------------------------------------------------------------------
+--- Maximized and fullscreen layouts module for awful
+--
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2008 Julien Danjou
+-- @module awful.layout
+---------------------------------------------------------------------------
+
+-- Grab environment we need
+local pairs = pairs
+
+local max = {}
+
+--- The max layout layoutbox icon.
+-- @beautiful beautiful.layout_max
+-- @param surface
+-- @see gears.surface
+
+--- The fullscreen layout layoutbox icon.
+-- @beautiful beautiful.layout_fullscreen
+-- @param surface
+-- @see gears.surface
+
+local function fmax(p, fs)
+ -- Fullscreen?
+ local area
+ if fs then
+ area = p.geometry
+ else
+ area = p.workarea
+ end
+
+ for _, c in pairs(p.clients) do
+ local g = {
+ x = area.x,
+ y = area.y,
+ width = area.width,
+ height = area.height
+ }
+ p.geometries[c] = g
+ end
+end
+
+--- Maximized layout.
+-- @clientlayout awful.layout.suit.max.name
+max.name = "max"
+function max.arrange(p)
+ return fmax(p, false)
+end
+
+--- Fullscreen layout.
+-- @clientlayout awful.layout.suit.max.fullscreen
+max.fullscreen = {}
+max.fullscreen.name = "fullscreen"
+function max.fullscreen.arrange(p)
+ return fmax(p, true)
+end
+
+return max
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/layout/suit/spiral.lua b/lib/awful/layout/suit/spiral.lua
new file mode 100644
index 0000000..0a7eb9b
--- /dev/null
+++ b/lib/awful/layout/suit/spiral.lua
@@ -0,0 +1,89 @@
+---------------------------------------------------------------------------
+--- Dwindle and spiral layouts
+--
+-- @author Uli Schlachter &lt;psychon@znc.in&gt;
+-- @copyright 2009 Uli Schlachter
+-- @copyright 2008 Julien Danjou
+--
+-- @module awful.layout
+---------------------------------------------------------------------------
+
+-- Grab environment we need
+local ipairs = ipairs
+local math = math
+
+--- The spiral layout layoutbox icon.
+-- @beautiful beautiful.layout_spiral
+-- @param surface
+-- @see gears.surface
+
+--- The dwindle layout layoutbox icon.
+-- @beautiful beautiful.layout_dwindle
+-- @param surface
+-- @see gears.surface
+
+local spiral = {}
+
+local function do_spiral(p, _spiral)
+ local wa = p.workarea
+ local cls = p.clients
+ local n = #cls
+ local old_width, old_height = wa.width, 2 * wa.height
+
+ for k, c in ipairs(cls) do
+ if k % 2 == 0 then
+ wa.width, old_width = math.ceil(old_width / 2), wa.width
+ if k ~= n then
+ wa.height, old_height = math.floor(wa.height / 2), wa.height
+ end
+ else
+ wa.height, old_height = math.ceil(old_height / 2), wa.height
+ if k ~= n then
+ wa.width, old_width = math.floor(wa.width / 2), wa.width
+ end
+ end
+
+ if k % 4 == 0 and _spiral then
+ wa.x = wa.x - wa.width
+ elseif k % 2 == 0 then
+ wa.x = wa.x + old_width
+ elseif k % 4 == 3 and k < n and _spiral then
+ wa.x = wa.x + math.ceil(old_width / 2)
+ end
+
+ if k % 4 == 1 and k ~= 1 and _spiral then
+ wa.y = wa.y - wa.height
+ elseif k % 2 == 1 and k ~= 1 then
+ wa.y = wa.y + old_height
+ elseif k % 4 == 0 and k < n and _spiral then
+ wa.y = wa.y + math.ceil(old_height / 2)
+ end
+
+ local g = {
+ x = wa.x,
+ y = wa.y,
+ width = wa.width,
+ height = wa.height
+ }
+ p.geometries[c] = g
+ end
+end
+
+--- Dwindle layout.
+-- @clientlayout awful.layout.suit.spiral.dwindle
+spiral.dwindle = {}
+spiral.dwindle.name = "dwindle"
+function spiral.dwindle.arrange(p)
+ return do_spiral(p, false)
+end
+
+--- Spiral layout.
+-- @clientlayout awful.layout.suit.spiral.name
+spiral.name = "spiral"
+function spiral.arrange(p)
+ return do_spiral(p, true)
+end
+
+return spiral
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/layout/suit/tile.lua b/lib/awful/layout/suit/tile.lua
new file mode 100644
index 0000000..9fa263c
--- /dev/null
+++ b/lib/awful/layout/suit/tile.lua
@@ -0,0 +1,348 @@
+---------------------------------------------------------------------------
+--- Tiled layouts module for awful
+--
+-- @author Donald Ephraim Curtis &lt;dcurtis@cs.uiowa.edu&gt;
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2009 Donald Ephraim Curtis
+-- @copyright 2008 Julien Danjou
+-- @module awful.layout
+---------------------------------------------------------------------------
+
+-- Grab environment we need
+local tag = require("awful.tag")
+local client = require("awful.client")
+local ipairs = ipairs
+local math = math
+local capi =
+{
+ mouse = mouse,
+ screen = screen,
+ mousegrabber = mousegrabber
+}
+
+local tile = {}
+
+--- The tile layout layoutbox icon.
+-- @beautiful beautiful.layout_tile
+-- @param surface
+-- @see gears.surface
+
+--- The tile top layout layoutbox icon.
+-- @beautiful beautiful.layout_tiletop
+-- @param surface
+-- @see gears.surface
+
+--- The tile bottom layout layoutbox icon.
+-- @beautiful beautiful.layout_tilebottom
+-- @param surface
+-- @see gears.surface
+
+--- The tile left layout layoutbox icon.
+-- @beautiful beautiful.layout_tileleft
+-- @param surface
+-- @see gears.surface
+
+--- Jump mouse cursor to the client's corner when resizing it.
+tile.resize_jump_to_corner = true
+
+local function mouse_resize_handler(c, _, _, _, orientation)
+ orientation = orientation or "tile"
+ local wa = c.screen.workarea
+ local mwfact = c.screen.selected_tag.master_width_factor
+ local cursor
+ local g = c:geometry()
+ local offset = 0
+ local corner_coords
+ local coordinates_delta = {x=0,y=0}
+
+ if orientation == "tile" then
+ cursor = "cross"
+ if g.height+15 > wa.height then
+ offset = g.height * .5
+ cursor = "sb_h_double_arrow"
+ elseif not (g.y+g.height+15 > wa.y+wa.height) then
+ offset = g.height
+ end
+ corner_coords = { x = wa.x + wa.width * mwfact, y = g.y + offset }
+ elseif orientation == "left" then
+ cursor = "cross"
+ if g.height+15 >= wa.height then
+ offset = g.height * .5
+ cursor = "sb_h_double_arrow"
+ elseif not (g.y+g.height+15 > wa.y+wa.height) then
+ offset = g.height
+ end
+ corner_coords = { x = wa.x + wa.width * (1 - mwfact), y = g.y + offset }
+ elseif orientation == "bottom" then
+ cursor = "cross"
+ if g.width+15 >= wa.width then
+ offset = g.width * .5
+ cursor = "sb_v_double_arrow"
+ elseif not (g.x+g.width+15 > wa.x+wa.width) then
+ offset = g.width
+ end
+ corner_coords = { y = wa.y + wa.height * mwfact, x = g.x + offset}
+ else
+ cursor = "cross"
+ if g.width+15 >= wa.width then
+ offset = g.width * .5
+ cursor = "sb_v_double_arrow"
+ elseif not (g.x+g.width+15 > wa.x+wa.width) then
+ offset = g.width
+ end
+ corner_coords = { y = wa.y + wa.height * (1 - mwfact), x= g.x + offset }
+ end
+ if tile.resize_jump_to_corner then
+ capi.mouse.coords(corner_coords)
+ else
+ local mouse_coords = capi.mouse.coords()
+ coordinates_delta = {
+ x = corner_coords.x - mouse_coords.x,
+ y = corner_coords.y - mouse_coords.y,
+ }
+ end
+
+ local prev_coords = {}
+ capi.mousegrabber.run(function (_mouse)
+ if not c.valid then return false end
+
+ _mouse.x = _mouse.x + coordinates_delta.x
+ _mouse.y = _mouse.y + coordinates_delta.y
+ for _, v in ipairs(_mouse.buttons) do
+ if v then
+ prev_coords = { x =_mouse.x, y = _mouse.y }
+ local fact_x = (_mouse.x - wa.x) / wa.width
+ local fact_y = (_mouse.y - wa.y) / wa.height
+ local new_mwfact
+
+ local geom = c:geometry()
+
+ -- we have to make sure we're not on the last visible client where we have to use different settings.
+ local wfact
+ local wfact_x, wfact_y
+ if (geom.y+geom.height+15) > (wa.y+wa.height) then
+ wfact_y = (geom.y + geom.height - _mouse.y) / wa.height
+ else
+ wfact_y = (_mouse.y - geom.y) / wa.height
+ end
+
+ if (geom.x+geom.width+15) > (wa.x+wa.width) then
+ wfact_x = (geom.x + geom.width - _mouse.x) / wa.width
+ else
+ wfact_x = (_mouse.x - geom.x) / wa.width
+ end
+
+
+ if orientation == "tile" then
+ new_mwfact = fact_x
+ wfact = wfact_y
+ elseif orientation == "left" then
+ new_mwfact = 1 - fact_x
+ wfact = wfact_y
+ elseif orientation == "bottom" then
+ new_mwfact = fact_y
+ wfact = wfact_x
+ else
+ new_mwfact = 1 - fact_y
+ wfact = wfact_x
+ end
+
+ c.screen.selected_tag.master_width_factor
+ = math.min(math.max(new_mwfact, 0.01), 0.99)
+ client.setwfact(math.min(math.max(wfact,0.01), 0.99), c)
+ return true
+ end
+ end
+ return prev_coords.x == _mouse.x and prev_coords.y == _mouse.y
+ end, cursor)
+end
+
+local function tile_group(gs, cls, wa, orientation, fact, group)
+ -- get our orientation right
+ local height = "height"
+ local width = "width"
+ local x = "x"
+ local y = "y"
+ if orientation == "top" or orientation == "bottom" then
+ height = "width"
+ width = "height"
+ x = "y"
+ y = "x"
+ end
+
+ -- make this more generic (not just width)
+ local available = wa[width] - (group.coord - wa[x])
+
+ -- find our total values
+ local total_fact = 0
+ local min_fact = 1
+ local size = group.size
+ for c = group.first,group.last do
+ -- determine the width/height based on the size_hint
+ local i = c - group.first +1
+ local size_hints = cls[c].size_hints
+ local size_hint = size_hints["min_"..width] or size_hints["base_"..width] or 0
+ size = math.max(size_hint, size)
+
+ -- calculate the height
+ if not fact[i] then
+ fact[i] = min_fact
+ else
+ min_fact = math.min(fact[i],min_fact)
+ end
+ total_fact = total_fact + fact[i]
+ end
+ size = math.max(1, math.min(size, available))
+
+ local coord = wa[y]
+ local used_size = 0
+ local unused = wa[height]
+ for c = group.first,group.last do
+ local geom = {}
+ local hints = {}
+ local i = c - group.first +1
+ geom[width] = size
+ geom[height] = math.max(1, math.floor(unused * fact[i] / total_fact))
+ geom[x] = group.coord
+ geom[y] = coord
+ gs[cls[c]] = geom
+ hints.width, hints.height = cls[c]:apply_size_hints(geom.width, geom.height)
+ coord = coord + hints[height]
+ unused = unused - hints[height]
+ total_fact = total_fact - fact[i]
+ used_size = math.max(used_size, hints[width])
+ end
+
+ return used_size
+end
+
+local function do_tile(param, orientation)
+ local t = param.tag or capi.screen[param.screen].selected_tag
+ orientation = orientation or "right"
+
+ -- This handles all different orientations.
+ local width = "width"
+ local x = "x"
+ if orientation == "top" or orientation == "bottom" then
+ width = "height"
+ x = "y"
+ end
+
+ local gs = param.geometries
+ local cls = param.clients
+ local nmaster = math.min(t.master_count, #cls)
+ local nother = math.max(#cls - nmaster,0)
+
+ local mwfact = t.master_width_factor
+ local wa = param.workarea
+ local ncol = t.column_count
+
+ local data = tag.getdata(t).windowfact
+
+ if not data then
+ data = {}
+ tag.getdata(t).windowfact = data
+ end
+
+ local coord = wa[x]
+ local place_master = true
+ if orientation == "left" or orientation == "top" then
+ -- if we are on the left or top we need to render the other windows first
+ place_master = false
+ end
+
+ local grow_master = t.master_fill_policy == "expand"
+ -- this was easier than writing functions because there is a lot of data we need
+ for _ = 1,2 do
+ if place_master and nmaster > 0 then
+ local size = wa[width]
+ if nother > 0 or not grow_master then
+ size = math.min(wa[width] * mwfact, wa[width] - (coord - wa[x]))
+ end
+ if nother == 0 and not grow_master then
+ coord = coord + (wa[width] - size)/2
+ end
+ if not data[0] then
+ data[0] = {}
+ end
+ coord = coord + tile_group(gs, cls, wa, orientation, data[0], {first=1, last=nmaster, coord = coord, size = size})
+ end
+
+ if not place_master and nother > 0 then
+ local last = nmaster
+
+ -- we have to modify the work area size to consider left and top views
+ local wasize = wa[width]
+ if nmaster > 0 and (orientation == "left" or orientation == "top") then
+ wasize = wa[width] - wa[width]*mwfact
+ end
+ for i = 1,ncol do
+ -- Try to get equal width among remaining columns
+ local size = math.min( (wasize - (coord - wa[x])) / (ncol - i + 1) )
+ local first = last + 1
+ last = last + math.floor((#cls - last)/(ncol - i + 1))
+ -- tile the column and update our current x coordinate
+ if not data[i] then
+ data[i] = {}
+ end
+ coord = coord + tile_group(gs, cls, wa, orientation, data[i], { first = first, last = last, coord = coord, size = size })
+ end
+ end
+ place_master = not place_master
+ end
+
+end
+
+--- The main tile algo, on the right.
+-- @param screen The screen number to tile.
+-- @clientlayout awful.layout.suit.tile.top
+tile.right = {}
+tile.right.name = "tile"
+tile.right.arrange = do_tile
+function tile.right.mouse_resize_handler(c, corner, x, y)
+ return mouse_resize_handler(c, corner, x, y)
+end
+
+--- The main tile algo, on the left.
+-- @param screen The screen number to tile.
+-- @clientlayout awful.layout.suit.tile.left
+tile.left = {}
+tile.left.name = "tileleft"
+function tile.left.arrange(p)
+ return do_tile(p, "left")
+end
+function tile.left.mouse_resize_handler(c, corner, x, y)
+ return mouse_resize_handler(c, corner, x, y, "left")
+end
+
+--- The main tile algo, on the bottom.
+-- @param screen The screen number to tile.
+-- @clientlayout awful.layout.suit.tile.bottom
+tile.bottom = {}
+tile.bottom.name = "tilebottom"
+function tile.bottom.arrange(p)
+ return do_tile(p, "bottom")
+end
+function tile.bottom.mouse_resize_handler(c, corner, x, y)
+ return mouse_resize_handler(c, corner, x, y, "bottom")
+end
+
+--- The main tile algo, on the top.
+-- @param screen The screen number to tile.
+-- @clientlayout awful.layout.suit.tile.top
+tile.top = {}
+tile.top.name = "tiletop"
+function tile.top.arrange(p)
+ return do_tile(p, "top")
+end
+function tile.top.mouse_resize_handler(c, corner, x, y)
+ return mouse_resize_handler(c, corner, x, y, "top")
+end
+
+tile.arrange = tile.right.arrange
+tile.mouse_resize_handler = tile.right.mouse_resize_handler
+tile.name = tile.right.name
+
+return tile
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/menu.lua b/lib/awful/menu.lua
new file mode 100644
index 0000000..dcbc5fa
--- /dev/null
+++ b/lib/awful/menu.lua
@@ -0,0 +1,723 @@
+--------------------------------------------------------------------------------
+--- A menu for awful
+--
+-- @author Damien Leone &lt;damien.leone@gmail.com&gt;
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @author dodo
+-- @copyright 2008, 2011 Damien Leone, Julien Danjou, dodo
+-- @module awful.menu
+--------------------------------------------------------------------------------
+
+local wibox = require("wibox")
+local button = require("awful.button")
+local util = require("awful.util")
+local spawn = require("awful.spawn")
+local tags = require("awful.tag")
+local keygrabber = require("awful.keygrabber")
+local client_iterate = require("awful.client").iterate
+local beautiful = require("beautiful")
+local dpi = require("beautiful").xresources.apply_dpi
+local object = require("gears.object")
+local surface = require("gears.surface")
+local protected_call = require("gears.protected_call")
+local cairo = require("lgi").cairo
+local setmetatable = setmetatable
+local tonumber = tonumber
+local string = string
+local ipairs = ipairs
+local pairs = pairs
+local print = print
+local table = table
+local type = type
+local math = math
+local capi = {
+ screen = screen,
+ mouse = mouse,
+ client = client }
+local screen = require("awful.screen")
+
+
+local menu = { mt = {} }
+
+
+local table_update = function (t, set)
+ for k, v in pairs(set) do
+ t[k] = v
+ end
+ return t
+end
+
+--- The icon used for sub-menus.
+-- @beautiful beautiful.menu_submenu_icon
+
+--- The item height.
+-- @beautiful beautiful.menu_height
+-- @tparam[opt=16] number menu_height
+
+--- The default menu width.
+-- @beautiful beautiful.menu_width
+-- @tparam[opt=100] number menu_width
+
+--- The menu item border color.
+-- @beautiful beautiful.menu_border_color
+-- @tparam[opt=0] number menu_border_color
+
+--- The menu item border width.
+-- @beautiful beautiful.menu_border_width
+-- @tparam[opt=0] number menu_border_width
+
+--- The default focused item foreground (text) color.
+-- @beautiful beautiful.menu_fg_focus
+-- @param color
+-- @see gears.color
+
+--- The default focused item background color.
+-- @beautiful beautiful.menu_bg_focus
+-- @param color
+-- @see gears.color
+
+--- The default foreground (text) color.
+-- @beautiful beautiful.menu_fg_normal
+-- @param color
+-- @see gears.color
+
+--- The default background color.
+-- @beautiful beautiful.menu_bg_normal
+-- @param color
+-- @see gears.color
+
+--- The default sub-menu indicator if no menu_submenu_icon is provided.
+-- @beautiful beautiful.menu_submenu
+-- @tparam[opt="▶"] string menu_submenu The sub-menu text.
+-- @see beautiful.menu_submenu_icon
+
+--- Key bindings for menu navigation.
+-- Keys are: up, down, exec, enter, back, close. Value are table with a list of valid
+-- keys for the action, i.e. menu_keys.up = { "j", "k" } will bind 'j' and 'k'
+-- key to up action. This is common to all created menu.
+-- @class table
+-- @name menu_keys
+menu.menu_keys = { up = { "Up", "k" },
+ down = { "Down", "j" },
+ back = { "Left", "h" },
+ exec = { "Return" },
+ enter = { "Right", "l" },
+ close = { "Escape" } }
+
+
+local function load_theme(a, b)
+ a = a or {}
+ b = b or {}
+ local ret = {}
+ local fallback = beautiful.get()
+ if a.reset then b = fallback end
+ if a == "reset" then a = fallback end
+ ret.border = a.border_color or b.menu_border_color or b.border_normal or
+ fallback.menu_border_color or fallback.border_normal
+ ret.border_width= a.border_width or b.menu_border_width or b.border_width or
+ fallback.menu_border_width or fallback.border_width or 0
+ ret.fg_focus = a.fg_focus or b.menu_fg_focus or b.fg_focus or
+ fallback.menu_fg_focus or fallback.fg_focus
+ ret.bg_focus = a.bg_focus or b.menu_bg_focus or b.bg_focus or
+ fallback.menu_bg_focus or fallback.bg_focus
+ ret.fg_normal = a.fg_normal or b.menu_fg_normal or b.fg_normal or
+ fallback.menu_fg_normal or fallback.fg_normal
+ ret.bg_normal = a.bg_normal or b.menu_bg_normal or b.bg_normal or
+ fallback.menu_bg_normal or fallback.bg_normal
+ ret.submenu_icon= a.submenu_icon or b.menu_submenu_icon or b.submenu_icon or
+ fallback.menu_submenu_icon or fallback.submenu_icon
+ ret.submenu = a.submenu or b.menu_submenu or b.submenu or
+ fallback.menu_submenu or fallback.submenu or "▶"
+ ret.height = a.height or b.menu_height or b.height or
+ fallback.menu_height or 16
+ ret.width = a.width or b.menu_width or b.width or
+ fallback.menu_width or 100
+ ret.font = a.font or b.font or fallback.font
+ for _, prop in ipairs({"width", "height", "menu_width"}) do
+ if type(ret[prop]) ~= "number" then ret[prop] = tonumber(ret[prop]) end
+ end
+ return ret
+end
+
+
+local function item_position(_menu, child)
+ local a, b = "height", "width"
+ local dir = _menu.layout.dir or "y"
+ if dir == "x" then a, b = b, a end
+
+ local in_dir, other = 0, _menu[b]
+ local num = util.table.hasitem(_menu.child, child)
+ if num then
+ for i = 0, num - 1 do
+ local item = _menu.items[i]
+ if item then
+ other = math.max(other, item[b])
+ in_dir = in_dir + item[a]
+ end
+ end
+ end
+ local w, h = other, in_dir
+ if dir == "x" then w, h = h, w end
+ return w, h
+end
+
+
+local function set_coords(_menu, s, m_coords)
+ local s_geometry = s.workarea
+ local screen_w = s_geometry.x + s_geometry.width
+ local screen_h = s_geometry.y + s_geometry.height
+
+ _menu.width = _menu.wibox.width
+ _menu.height = _menu.wibox.height
+
+ _menu.x = _menu.wibox.x
+ _menu.y = _menu.wibox.y
+
+ if _menu.parent then
+ local w, h = item_position(_menu.parent, _menu)
+ w = w + _menu.parent.theme.border_width
+
+ _menu.y = _menu.parent.y + h + _menu.height > screen_h and
+ screen_h - _menu.height or _menu.parent.y + h
+ _menu.x = _menu.parent.x + w + _menu.width > screen_w and
+ _menu.parent.x - _menu.width or _menu.parent.x + w
+ else
+ if m_coords == nil then
+ m_coords = capi.mouse.coords()
+ m_coords.x = m_coords.x + 1
+ m_coords.y = m_coords.y + 1
+ end
+ _menu.y = m_coords.y < s_geometry.y and s_geometry.y or m_coords.y
+ _menu.x = m_coords.x < s_geometry.x and s_geometry.x or m_coords.x
+
+ _menu.y = _menu.y + _menu.height > screen_h and
+ screen_h - _menu.height or _menu.y
+ _menu.x = _menu.x + _menu.width > screen_w and
+ screen_w - _menu.width or _menu.x
+ end
+
+ _menu.wibox.x = _menu.x
+ _menu.wibox.y = _menu.y
+end
+
+
+local function set_size(_menu)
+ local in_dir, other, a, b = 0, 0, "height", "width"
+ local dir = _menu.layout.dir or "y"
+ if dir == "x" then a, b = b, a end
+ for _, item in ipairs(_menu.items) do
+ other = math.max(other, item[b])
+ in_dir = in_dir + item[a]
+ end
+ _menu[a], _menu[b] = in_dir, other
+ if in_dir > 0 and other > 0 then
+ _menu.wibox[a] = in_dir
+ _menu.wibox[b] = other
+ return true
+ end
+ return false
+end
+
+
+local function check_access_key(_menu, key)
+ for i, item in ipairs(_menu.items) do
+ if item.akey == key then
+ _menu:item_enter(i)
+ _menu:exec(i, { exec = true })
+ return
+ end
+ end
+ if _menu.parent then
+ check_access_key(_menu.parent, key)
+ end
+end
+
+
+local function grabber(_menu, _, key, event)
+ if event ~= "press" then return end
+
+ local sel = _menu.sel or 0
+ if util.table.hasitem(menu.menu_keys.up, key) then
+ local sel_new = sel-1 < 1 and #_menu.items or sel-1
+ _menu:item_enter(sel_new)
+ elseif util.table.hasitem(menu.menu_keys.down, key) then
+ local sel_new = sel+1 > #_menu.items and 1 or sel+1
+ _menu:item_enter(sel_new)
+ elseif sel > 0 and util.table.hasitem(menu.menu_keys.enter, key) then
+ _menu:exec(sel)
+ elseif sel > 0 and util.table.hasitem(menu.menu_keys.exec, key) then
+ _menu:exec(sel, { exec = true })
+ elseif util.table.hasitem(menu.menu_keys.back, key) then
+ _menu:hide()
+ elseif util.table.hasitem(menu.menu_keys.close, key) then
+ menu.get_root(_menu):hide()
+ else
+ check_access_key(_menu, key)
+ end
+end
+
+
+function menu:exec(num, opts)
+ opts = opts or {}
+ local item = self.items[num]
+ if not item then return end
+ local cmd = item.cmd
+ if type(cmd) == "table" then
+ local action = cmd.cmd
+ if #cmd == 0 then
+ if opts.exec and action and type(action) == "function" then
+ action()
+ end
+ return
+ end
+ if not self.child[num] then
+ self.child[num] = menu.new(cmd, self)
+ end
+ local can_invoke_action = opts.exec and
+ action and type(action) == "function" and
+ (not opts.mouse or (opts.mouse and (self.auto_expand or
+ (self.active_child == self.child[num] and
+ self.active_child.wibox.visible))))
+ if can_invoke_action then
+ local visible = action(self.child[num], item)
+ if not visible then
+ menu.get_root(self):hide()
+ return
+ else
+ self.child[num]:update()
+ end
+ end
+ if self.active_child and self.active_child ~= self.child[num] then
+ self.active_child:hide()
+ end
+ self.active_child = self.child[num]
+ if not self.active_child.wibox.visible then
+ self.active_child:show()
+ end
+ elseif type(cmd) == "string" then
+ menu.get_root(self):hide()
+ spawn(cmd)
+ elseif type(cmd) == "function" then
+ local visible, action = cmd(item, self)
+ if not visible then
+ menu.get_root(self):hide()
+ else
+ self:update()
+ if self.items[num] then
+ self:item_enter(num, opts)
+ end
+ end
+ if action and type(action) == "function" then
+ action()
+ end
+ end
+end
+
+function menu:item_enter(num, opts)
+ opts = opts or {}
+ local item = self.items[num]
+ if num == nil or self.sel == num or not item then
+ return
+ elseif self.sel then
+ self:item_leave(self.sel)
+ end
+ --print("sel", num, menu.sel, item.theme.bg_focus)
+ item._background:set_fg(item.theme.fg_focus)
+ item._background:set_bg(item.theme.bg_focus)
+ self.sel = num
+
+ if self.auto_expand and opts.hover then
+ if self.active_child then
+ self.active_child:hide()
+ self.active_child = nil
+ end
+
+ if type(item.cmd) == "table" then
+ self:exec(num, opts)
+ end
+ end
+end
+
+
+function menu:item_leave(num)
+ --print("leave", num)
+ local item = self.items[num]
+ if item then
+ item._background:set_fg(item.theme.fg_normal)
+ item._background:set_bg(item.theme.bg_normal)
+ end
+end
+
+
+--- Show a menu.
+-- @param args The arguments
+-- @param args.coords Menu position defaulting to mouse.coords()
+function menu:show(args)
+ args = args or {}
+ local coords = args.coords or nil
+ local s = capi.screen[screen.focused()]
+
+ if not set_size(self) then return end
+ set_coords(self, s, coords)
+
+ keygrabber.run(self._keygrabber)
+ self.wibox.visible = true
+end
+
+--- Hide a menu popup.
+function menu:hide()
+ -- Remove items from screen
+ for i = 1, #self.items do
+ self:item_leave(i)
+ end
+ if self.active_child then
+ self.active_child:hide()
+ self.active_child = nil
+ end
+ self.sel = nil
+
+ keygrabber.stop(self._keygrabber)
+ self.wibox.visible = false
+end
+
+--- Toggle menu visibility.
+-- @param args The arguments
+-- @param args.coords Menu position {x,y}
+function menu:toggle(args)
+ if self.wibox.visible then
+ self:hide()
+ else
+ self:show(args)
+ end
+end
+
+--- Update menu content
+function menu:update()
+ if self.wibox.visible then
+ self:show({ coords = { x = self.x, y = self.y } })
+ end
+end
+
+
+--- Get the elder parent so for example when you kill
+-- it, it will destroy the whole family.
+function menu:get_root()
+ return self.parent and menu.get_root(self.parent) or self
+end
+
+--- Add a new menu entry.
+-- args.* params needed for the menu entry constructor.
+-- @param args The item params
+-- @param args.new (Default: awful.menu.entry) The menu entry constructor.
+-- @param[opt] args.theme The menu entry theme.
+-- @param[opt] index The index where the new entry will inserted.
+function menu:add(args, index)
+ if not args then return end
+ local theme = load_theme(args.theme or {}, self.theme)
+ args.theme = theme
+ args.new = args.new or menu.entry
+ local item = protected_call(args.new, self, args)
+ if (not item) or (not item.widget) then
+ print("Error while checking menu entry: no property widget found.")
+ return
+ end
+ item.parent = self
+ item.theme = item.theme or theme
+ item.width = item.width or theme.width
+ item.height = item.height or theme.height
+ wibox.widget.base.check_widget(item.widget)
+ item._background = wibox.container.background()
+ item._background:set_widget(item.widget)
+ item._background:set_fg(item.theme.fg_normal)
+ item._background:set_bg(item.theme.bg_normal)
+
+
+ -- Create bindings
+ item._background:buttons(util.table.join(
+ button({}, 3, function () self:hide() end),
+ button({}, 1, function ()
+ local num = util.table.hasitem(self.items, item)
+ self:item_enter(num, { mouse = true })
+ self:exec(num, { exec = true, mouse = true })
+ end )))
+
+
+ item._mouse = function ()
+ local num = util.table.hasitem(self.items, item)
+ self:item_enter(num, { hover = true, moue = true })
+ end
+ item.widget:connect_signal("mouse::enter", item._mouse)
+
+ if index then
+ self.layout:reset()
+ table.insert(self.items, index, item)
+ for _, i in ipairs(self.items) do
+ self.layout:add(i._background)
+ end
+ else
+ table.insert(self.items, item)
+ self.layout:add(item._background)
+ end
+ if self.wibox then
+ set_size(self)
+ end
+ return item
+end
+
+--- Delete menu entry at given position
+-- @param num The position in the table of the menu entry to be deleted; can be also the menu entry itself
+function menu:delete(num)
+ if type(num) == "table" then
+ num = util.table.hasitem(self.items, num)
+ end
+ local item = self.items[num]
+ if not item then return end
+ item.widget:disconnect_signal("mouse::enter", item._mouse)
+ item.widget:set_visible(false)
+ table.remove(self.items, num)
+ if self.sel == num then
+ self:item_leave(self.sel)
+ self.sel = nil
+ end
+ self.layout:reset()
+ for _, i in ipairs(self.items) do
+ self.layout:add(i._background)
+ end
+ if self.child[num] then
+ self.child[num]:hide()
+ if self.active_child == self.child[num] then
+ self.active_child = nil
+ end
+ table.remove(self.child, num)
+ end
+ if self.wibox then
+ set_size(self)
+ end
+end
+
+--------------------------------------------------------------------------------
+
+--- Build a popup menu with running clients and show it.
+-- @tparam[opt] table args Menu table, see `new()` for more information.
+-- @tparam[opt] table item_args Table that will be merged into each item, see
+-- `new()` for more information.
+-- @tparam[opt] func filter A function taking a client as an argument and
+-- returning `true` or `false` to indicate whether the client should be
+-- included in the menu.
+-- @return The menu.
+function menu.clients(args, item_args, filter)
+ local cls_t = {}
+ for c in client_iterate(filter or function() return true end) do
+ cls_t[#cls_t + 1] = {
+ c.name or "",
+ function ()
+ if not c:isvisible() then
+ tags.viewmore(c:tags(), c.screen)
+ end
+ c:emit_signal("request::activate", "menu.clients", {raise=true})
+ end,
+ c.icon }
+ if item_args then
+ if type(item_args) == "function" then
+ util.table.merge(cls_t[#cls_t], item_args(c))
+ else
+ util.table.merge(cls_t[#cls_t], item_args)
+ end
+ end
+ end
+ args = args or {}
+ args.items = args.items or {}
+ util.table.merge(args.items, cls_t)
+
+ local m = menu.new(args)
+ m:show(args)
+ return m
+end
+
+--------------------------------------------------------------------------------
+
+--- Default awful.menu.entry constructor
+-- @param parent The parent menu (TODO: This is apparently unused)
+-- @param args the item params
+-- @return table with 'widget', 'cmd', 'akey' and all the properties the user wants to change
+function menu.entry(parent, args) -- luacheck: no unused args
+ args = args or {}
+ args.text = args[1] or args.text or ""
+ args.cmd = args[2] or args.cmd
+ args.icon = args[3] or args.icon
+ local ret = {}
+ -- Create the item label widget
+ local label = wibox.widget.textbox()
+ local key = ''
+ label:set_font(args.theme.font)
+ label:set_markup(string.gsub(
+ util.escape(args.text), "&amp;(%w)",
+ function (l)
+ key = string.lower(l)
+ return "<u>" .. l .. "</u>"
+ end, 1))
+ -- Set icon if needed
+ local icon, iconbox
+ local margin = wibox.container.margin()
+ margin:set_widget(label)
+ if args.icon then
+ icon = surface.load(args.icon)
+ end
+ if icon then
+ local iw = icon:get_width()
+ local ih = icon:get_height()
+ if iw > args.theme.width or ih > args.theme.height then
+ local w, h
+ if ((args.theme.height / ih) * iw) > args.theme.width then
+ w, h = args.theme.height, (args.theme.height / iw) * ih
+ else
+ w, h = (args.theme.height / ih) * iw, args.theme.height
+ end
+ -- We need to scale the image to size w x h
+ local img = cairo.ImageSurface(cairo.Format.ARGB32, w, h)
+ local cr = cairo.Context(img)
+ cr:scale(w / iw, h / ih)
+ cr:set_source_surface(icon, 0, 0)
+ cr:paint()
+ icon = img
+ end
+ iconbox = wibox.widget.imagebox()
+ if iconbox:set_image(icon) then
+ margin:set_left(dpi(2))
+ else
+ iconbox = nil
+ end
+ end
+ if not iconbox then
+ margin:set_left(args.theme.height + dpi(2))
+ end
+ -- Create the submenu icon widget
+ local submenu
+ if type(args.cmd) == "table" then
+ if args.theme.submenu_icon then
+ submenu = wibox.widget.imagebox()
+ submenu:set_image(args.theme.submenu_icon)
+ else
+ submenu = wibox.widget.textbox()
+ submenu:set_font(args.theme.font)
+ submenu:set_text(args.theme.submenu)
+ end
+ end
+ -- Add widgets to the wibox
+ local left = wibox.layout.fixed.horizontal()
+ if iconbox then
+ left:add(iconbox)
+ end
+ -- This contains the label
+ left:add(margin)
+
+ local layout = wibox.layout.align.horizontal()
+ layout:set_left(left)
+ if submenu then
+ layout:set_right(submenu)
+ end
+
+ return table_update(ret, {
+ label = label,
+ sep = submenu,
+ icon = iconbox,
+ widget = layout,
+ cmd = args.cmd,
+ akey = key,
+ })
+end
+
+--------------------------------------------------------------------------------
+
+--- Create a menu popup.
+-- @param args Table containing the menu informations.
+--
+-- * Key items: Table containing the displayed items. Each element is a table by default (when element 'new' is awful.menu.entry) containing: item name, triggered action, submenu table or function, item icon (optional).
+-- * Keys theme.[fg|bg]_[focus|normal], theme.border_color, theme.border_width, theme.submenu_icon, theme.height and theme.width override the default display for your menu and/or of your menu entry, each of them are optional.
+-- * Key auto_expand controls the submenu auto expand behaviour by setting it to true (default) or false.
+--
+-- @param parent Specify the parent menu if we want to open a submenu, this value should never be set by the user.
+-- @usage -- The following function builds and shows a menu of clients that match
+-- -- a particular rule.
+-- -- Bound to a key, it can be used to select from dozens of terminals open on
+-- -- several tags.
+-- -- When using @{rules.match_any} instead of @{rules.match},
+-- -- a menu of clients with different classes could be build.
+--
+-- function terminal_menu ()
+-- terms = {}
+-- for i, c in pairs(client.get()) do
+-- if awful.rules.match(c, {class = "URxvt"}) then
+-- terms[i] =
+-- {c.name,
+-- function()
+-- c.first_tag:view_only()
+-- client.focus = c
+-- end,
+-- c.icon
+-- }
+-- end
+-- end
+-- awful.menu(terms):show()
+-- end
+function menu.new(args, parent)
+ args = args or {}
+ args.layout = args.layout or wibox.layout.flex.vertical
+ local _menu = table_update(object(), {
+ item_enter = menu.item_enter,
+ item_leave = menu.item_leave,
+ get_root = menu.get_root,
+ delete = menu.delete,
+ update = menu.update,
+ toggle = menu.toggle,
+ hide = menu.hide,
+ show = menu.show,
+ exec = menu.exec,
+ add = menu.add,
+ child = {},
+ items = {},
+ parent = parent,
+ layout = args.layout(),
+ theme = load_theme(args.theme or {}, parent and parent.theme) })
+
+ if parent then
+ _menu.auto_expand = parent.auto_expand
+ elseif args.auto_expand ~= nil then
+ _menu.auto_expand = args.auto_expand
+ else
+ _menu.auto_expand = true
+ end
+
+ -- Create items
+ for _, v in ipairs(args) do _menu:add(v) end
+ if args.items then
+ for _, v in pairs(args.items) do _menu:add(v) end
+ end
+
+ _menu._keygrabber = function (...)
+ grabber(_menu, ...)
+ end
+
+ _menu.wibox = wibox({
+ ontop = true,
+ fg = _menu.theme.fg_normal,
+ bg = _menu.theme.bg_normal,
+ border_color = _menu.theme.border,
+ border_width = _menu.theme.border_width,
+ type = "popup_menu" })
+ _menu.wibox.visible = false
+ _menu.wibox:set_widget(_menu.layout)
+ set_size(_menu)
+
+ _menu.x = _menu.wibox.x
+ _menu.y = _menu.wibox.y
+ return _menu
+end
+
+function menu.mt:__call(...)
+ return menu.new(...)
+end
+
+return setmetatable(menu, menu.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/mouse/drag_to_tag.lua b/lib/awful/mouse/drag_to_tag.lua
new file mode 100644
index 0000000..141456b
--- /dev/null
+++ b/lib/awful/mouse/drag_to_tag.lua
@@ -0,0 +1,58 @@
+---------------------------------------------------------------------------
+--- When the the mouse reach the end of the screen, then switch tag instead
+-- of screens.
+--
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2008 Julien Danjou
+-- @submodule mouse
+---------------------------------------------------------------------------
+
+local capi = {screen = screen, mouse = mouse}
+local util = require("awful.util")
+local tag = require("awful.tag")
+local resize = require("awful.mouse.resize")
+
+local module = {}
+
+function module.drag_to_tag(c)
+ if (not c) or (not c.valid) then return end
+
+ local coords = capi.mouse.coords()
+
+ local dir = nil
+
+ local wa = c.screen.workarea
+
+ if coords.x >= wa.x + wa.width - 1 then
+ capi.mouse.coords({ x = wa.x + 2 }, true)
+ dir = "right"
+ elseif coords.x <= wa.x + 1 then
+ capi.mouse.coords({ x = wa.x + wa.width - 2 }, true)
+ dir = "left"
+ end
+
+ local tags = c.screen.tags
+ local t = c.screen.selected_tag
+ local idx = t.index
+
+ if dir then
+
+ if dir == "right" then
+ local newtag = tags[util.cycle(#tags, idx + 1)]
+ c:move_to_tag(newtag)
+ tag.viewnext()
+ elseif dir == "left" then
+ local newtag = tags[util.cycle(#tags, idx - 1)]
+ c:move_to_tag(newtag)
+ tag.viewprev()
+ end
+ end
+end
+
+resize.add_move_callback(function(c, _, _)
+ if module.enabled then
+ module.drag_to_tag(c)
+ end
+end, "mouse.move")
+
+return setmetatable(module, {__call = function(_, ...) return module.drag_to_tag(...) end})
diff --git a/lib/awful/mouse/init.lua b/lib/awful/mouse/init.lua
new file mode 100644
index 0000000..03f7e89
--- /dev/null
+++ b/lib/awful/mouse/init.lua
@@ -0,0 +1,437 @@
+---------------------------------------------------------------------------
+--- Mouse module for awful
+--
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2008 Julien Danjou
+-- @module mouse
+---------------------------------------------------------------------------
+
+-- Grab environment we need
+local layout = require("awful.layout")
+local aplace = require("awful.placement")
+local util = require("awful.util")
+local type = type
+local ipairs = ipairs
+local capi =
+{
+ root = root,
+ mouse = mouse,
+ screen = screen,
+ client = client,
+ mousegrabber = mousegrabber,
+}
+
+local mouse = {
+ resize = require("awful.mouse.resize"),
+ snap = require("awful.mouse.snap"),
+ drag_to_tag = require("awful.mouse.drag_to_tag")
+}
+
+mouse.object = {}
+mouse.client = {}
+mouse.wibox = {}
+
+--- The default snap distance.
+-- @tfield integer awful.mouse.snap.default_distance
+-- @tparam[opt=8] integer default_distance
+-- @see awful.mouse.snap
+
+--- Enable screen edges snapping.
+-- @tfield[opt=true] boolean awful.mouse.snap.edge_enabled
+
+--- Enable client to client snapping.
+-- @tfield[opt=true] boolean awful.mouse.snap.client_enabled
+
+--- Enable changing tag when a client is dragged to the edge of the screen.
+-- @tfield[opt=false] integer awful.mouse.drag_to_tag.enabled
+
+--- The snap outline background color.
+-- @beautiful beautiful.snap_bg
+-- @tparam color|string|gradient|pattern color
+
+--- The snap outline width.
+-- @beautiful beautiful.snap_border_width
+-- @param integer
+
+--- The snap outline shape.
+-- @beautiful beautiful.snap_shape
+-- @tparam function shape A `gears.shape` compatible function
+
+--- Get the client object under the pointer.
+-- @deprecated awful.mouse.client_under_pointer
+-- @return The client object under the pointer, if one can be found.
+-- @see current_client
+function mouse.client_under_pointer()
+ util.deprecate("Use mouse.current_client instead of awful.mouse.client_under_pointer()")
+
+ return mouse.object.get_current_client()
+end
+
+--- Move a client.
+-- @function awful.mouse.client.move
+-- @param c The client to move, or the focused one if nil.
+-- @param snap The pixel to snap clients.
+-- @param finished_cb Deprecated, do not use
+function mouse.client.move(c, snap, finished_cb) --luacheck: no unused args
+ if finished_cb then
+ util.deprecate("The mouse.client.move `finished_cb` argument is no longer"..
+ " used, please use awful.mouse.resize.add_leave_callback(f, 'mouse.move')")
+ end
+
+ c = c or capi.client.focus
+
+ if not c
+ or c.fullscreen
+ or c.type == "desktop"
+ or c.type == "splash"
+ or c.type == "dock" then
+ return
+ end
+
+ -- Compute the offset
+ local coords = capi.mouse.coords()
+ local geo = aplace.centered(capi.mouse,{parent=c, pretend=true})
+
+ local offset = {
+ x = geo.x - coords.x,
+ y = geo.y - coords.y,
+ }
+
+ mouse.resize(c, "mouse.move", {
+ placement = aplace.under_mouse,
+ offset = offset,
+ snap = snap
+ })
+end
+
+mouse.client.dragtotag = { }
+
+--- Move a client to a tag by dragging it onto the left / right side of the screen.
+-- @deprecated awful.mouse.client.dragtotag.border
+-- @param c The client to move
+function mouse.client.dragtotag.border(c)
+ util.deprecate("Use awful.mouse.snap.drag_to_tag_enabled = true instead "..
+ "of awful.mouse.client.dragtotag.border(c). It will now be enabled.")
+
+ -- Enable drag to border
+ mouse.snap.drag_to_tag_enabled = true
+
+ return mouse.client.move(c)
+end
+
+--- Move the wibox under the cursor.
+-- @function awful.mouse.wibox.move
+--@tparam wibox w The wibox to move, or none to use that under the pointer
+function mouse.wibox.move(w)
+ w = w or mouse.wibox_under_pointer()
+ if not w then return end
+
+ if not w
+ or w.type == "desktop"
+ or w.type == "splash"
+ or w.type == "dock" then
+ return
+ end
+
+ -- Compute the offset
+ local coords = capi.mouse.coords()
+ local geo = aplace.centered(capi.mouse,{parent=w, pretend=true})
+
+ local offset = {
+ x = geo.x - coords.x,
+ y = geo.y - coords.y,
+ }
+
+ mouse.resize(w, "mouse.move", {
+ placement = aplace.under_mouse,
+ offset = offset
+ })
+end
+
+--- Get a client corner coordinates.
+-- @deprecated awful.mouse.client.corner
+-- @tparam[opt=client.focus] client c The client to get corner from, focused one by default.
+-- @tparam string corner The corner to use: auto, top_left, top_right, bottom_left,
+-- bottom_right, left, right, top bottom. Default is auto, and auto find the
+-- nearest corner.
+-- @treturn string The corner name
+-- @treturn number x The horizontal position
+-- @treturn number y The vertical position
+function mouse.client.corner(c, corner)
+ util.deprecate(
+ "Use awful.placement.closest_corner(mouse) or awful.placement[corner](mouse)"..
+ " instead of awful.mouse.client.corner"
+ )
+
+ c = c or capi.client.focus
+ if not c then return end
+
+ local ngeo = nil
+
+ if (not corner) or corner == "auto" then
+ ngeo, corner = aplace.closest_corner(mouse, {parent = c})
+ elseif corner and aplace[corner] then
+ ngeo = aplace[corner](mouse, {parent = c})
+ end
+
+ return corner, ngeo and ngeo.x or nil, ngeo and ngeo.y or nil
+end
+
+--- Resize a client.
+-- @function awful.mouse.client.resize
+-- @param c The client to resize, or the focused one by default.
+-- @tparam string corner The corner to grab on resize. Auto detected by default.
+-- @tparam[opt={}] table args A set of `awful.placement` arguments
+-- @treturn string The corner (or side) name
+function mouse.client.resize(c, corner, args)
+ c = c or capi.client.focus
+
+ if not c then return end
+
+ if c.fullscreen
+ or c.type == "desktop"
+ or c.type == "splash"
+ or c.type == "dock" then
+ return
+ end
+
+ -- Set some default arguments
+ local new_args = setmetatable(
+ {
+ include_sides = (not args) or args.include_sides ~= false
+ },
+ {
+ __index = args or {}
+ }
+ )
+
+ -- Move the mouse to the corner
+ if corner and aplace[corner] then
+ aplace[corner](capi.mouse, {parent=c})
+ else
+ local _
+ _, corner = aplace.closest_corner(capi.mouse, {
+ parent = c,
+ include_sides = new_args.include_sides ~= false,
+ })
+ end
+
+ new_args.corner = corner
+
+ mouse.resize(c, "mouse.resize", new_args)
+
+ return corner
+end
+
+--- Default handler for `request::geometry` signals with "mouse.resize" context.
+-- @signalhandler awful.mouse.resize_handler
+-- @tparam client c The client
+-- @tparam string context The context
+-- @tparam[opt={}] table hints The hints to pass to the handler
+function mouse.resize_handler(c, context, hints)
+ if hints and context and context:find("mouse.*") then
+ -- This handler only handle the floating clients. If the client is tiled,
+ -- then it let the layouts handle it.
+ local t = c.screen.selected_tag
+ local lay = t and t.layout or nil
+
+ if (lay and lay == layout.suit.floating) or c.floating then
+ c:geometry {
+ x = hints.x,
+ y = hints.y,
+ width = hints.width,
+ height = hints.height,
+ }
+ elseif lay and lay.resize_handler then
+ lay.resize_handler(c, context, hints)
+ end
+ end
+end
+
+-- Older layouts implement their own mousegrabber.
+-- @tparam client c The client
+-- @tparam table args Additional arguments
+-- @treturn boolean This return false when the resize need to be aborted
+mouse.resize.add_enter_callback(function(c, args) --luacheck: no unused args
+ if c.floating then return end
+
+ local l = c.screen.selected_tag and c.screen.selected_tag.layout or nil
+ if l == layout.suit.floating then return end
+
+ if l ~= layout.suit.floating and l.mouse_resize_handler then
+ capi.mousegrabber.stop()
+
+ local geo, corner = aplace.closest_corner(capi.mouse, {parent=c})
+
+ l.mouse_resize_handler(c, corner, geo.x, geo.y)
+
+ return false
+ end
+end, "mouse.resize")
+
+--- Get the client currently under the mouse cursor.
+-- @property current_client
+-- @tparam client|nil The client
+
+function mouse.object.get_current_client()
+ local obj = capi.mouse.object_under_pointer()
+ if type(obj) == "client" then
+ return obj
+ end
+end
+
+--- Get the wibox currently under the mouse cursor.
+-- @property current_wibox
+-- @tparam wibox|nil The wibox
+
+function mouse.object.get_current_wibox()
+ local obj = capi.mouse.object_under_pointer()
+ if type(obj) == "drawin" and obj.get_wibox then
+ return obj:get_wibox()
+ end
+end
+
+--- Get the widgets currently under the mouse cursor.
+--
+-- @property current_widgets
+-- @tparam nil|table list The widget list
+-- @treturn table The list of widgets.The first element is the biggest
+-- container while the last is the topmost widget. The table contains *x*, *y*,
+-- *width*, *height* and *widget*.
+-- @see wibox.find_widgets
+
+function mouse.object.get_current_widgets()
+ local w = mouse.object.get_current_wibox()
+ if w then
+ local geo, coords = w:geometry(), capi.mouse:coords()
+
+ local list = w:find_widgets(coords.x - geo.x, coords.y - geo.y)
+
+ local ret = {}
+
+ for k, v in ipairs(list) do
+ ret[k] = v.widget
+ end
+
+ return ret, list
+ end
+end
+
+--- Get the topmost widget currently under the mouse cursor.
+-- @property current_widget
+-- @tparam widget|nil widget The widget
+-- @treturn ?widget The widget
+-- @see wibox.find_widgets
+-- @see current_widget_geometry
+
+function mouse.object.get_current_widget()
+ local wdgs, geos = mouse.object.get_current_widgets()
+
+ if wdgs then
+ return wdgs[#wdgs], geos[#geos]
+ end
+end
+
+--- Get the current widget geometry.
+-- @property current_widget_geometry
+-- @tparam ?table The geometry.
+-- @see current_widget
+
+function mouse.object.get_current_widget_geometry()
+ local _, ret = mouse.object.get_current_widget()
+
+ return ret
+end
+
+--- Get the current widget geometries.
+-- @property current_widget_geometries
+-- @tparam ?table A list of geometry tables.
+-- @see current_widgets
+
+function mouse.object.get_current_widget_geometries()
+ local _, ret = mouse.object.get_current_widgets()
+
+ return ret
+end
+
+--- True if the left mouse button is pressed.
+-- @property is_left_mouse_button_pressed
+-- @param boolean
+
+--- True if the right mouse button is pressed.
+-- @property is_right_mouse_button_pressed
+-- @param boolean
+
+--- True if the middle mouse button is pressed.
+-- @property is_middle_mouse_button_pressed
+-- @param boolean
+
+for _, b in ipairs {"left", "right", "middle"} do
+ mouse.object["is_".. b .."_mouse_button_pressed"] = function()
+ return capi.mouse.coords().buttons[1]
+ end
+end
+
+capi.client.connect_signal("request::geometry", mouse.resize_handler)
+
+-- Set the cursor at startup
+capi.root.cursor("left_ptr")
+
+-- Implement the custom property handler
+local props = {}
+
+capi.mouse.set_newindex_miss_handler(function(_,key,value)
+ if mouse.object["set_"..key] then
+ mouse.object["set_"..key](value)
+ elseif not mouse.object["get_"..key] then
+ props[key] = value
+ else
+ -- If there is a getter, but no setter, then the property is read-only
+ error("Cannot set '" .. tostring(key) .. " because it is read-only")
+ end
+end)
+
+capi.mouse.set_index_miss_handler(function(_,key)
+ if mouse.object["get_"..key] then
+ return mouse.object["get_"..key]()
+ else
+ return props[key]
+ end
+end)
+
+--- Get or set the mouse coords.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_awful_mouse_coords.svg)
+--
+--**Usage example output**:
+--
+-- 235
+--
+--
+-- @usage
+-- -- Get the position
+--print(mouse.coords().x)
+-- -- Change the position
+--mouse.coords {
+-- x = 185,
+-- y = 10
+--}
+--
+-- @tparam[opt=nil] table coords_table None or a table with x and y keys as mouse
+-- coordinates.
+-- @tparam[opt=nil] integer coords_table.x The mouse horizontal position
+-- @tparam[opt=nil] integer coords_table.y The mouse vertical position
+-- @tparam[opt=false] boolean silent Disable mouse::enter or mouse::leave events that
+-- could be triggered by the pointer when moving.
+-- @treturn integer table.x The horizontal position
+-- @treturn integer table.y The vertical position
+-- @treturn table table.buttons Table containing the status of buttons, e.g. field [1] is true
+-- when button 1 is pressed.
+-- @function mouse.coords
+
+
+return mouse
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/mouse/resize.lua b/lib/awful/mouse/resize.lua
new file mode 100644
index 0000000..edd278d
--- /dev/null
+++ b/lib/awful/mouse/resize.lua
@@ -0,0 +1,229 @@
+---------------------------------------------------------------------------
+--- An extandable mouse resizing handler.
+--
+-- This module offer a resizing and moving mechanism for drawable such as
+-- clients and wiboxes.
+--
+-- @author Emmanuel Lepage Vallee &lt;elv1313@gmail.com&gt;
+-- @copyright 2016 Emmanuel Lepage Vallee
+-- @submodule mouse
+---------------------------------------------------------------------------
+
+local aplace = require("awful.placement")
+local capi = {mousegrabber = mousegrabber}
+local beautiful = require("beautiful")
+
+local module = {}
+
+local mode = "live"
+local req = "request::geometry"
+local callbacks = {enter={}, move={}, leave={}}
+
+local cursors = {
+ ["mouse.move" ] = "fleur",
+ ["mouse.resize" ] = "cross",
+ ["mouse.resize_left" ] = "sb_h_double_arrow",
+ ["mouse.resize_right" ] = "sb_h_double_arrow",
+ ["mouse.resize_top" ] = "sb_v_double_arrow",
+ ["mouse.resize_bottom" ] = "sb_v_double_arrow",
+ ["mouse.resize_top_left" ] = "top_left_corner",
+ ["mouse.resize_top_right" ] = "top_right_corner",
+ ["mouse.resize_bottom_left" ] = "bottom_left_corner",
+ ["mouse.resize_bottom_right"] = "bottom_right_corner",
+}
+
+--- The resize cursor name.
+-- @beautiful beautiful.cursor_mouse_resize
+-- @tparam[opt=cross] string cursor
+
+--- The move cursor name.
+-- @beautiful beautiful.cursor_mouse_move
+-- @tparam[opt=fleur] string cursor
+
+--- Set the resize mode.
+-- The available modes are:
+--
+-- * **live**: Resize the layout everytime the mouse move
+-- * **after**: Resize the layout only when the mouse is released
+--
+-- Some clients, such as XTerm, may lose information if resized too often.
+--
+-- @function awful.mouse.resize.set_mode
+-- @tparam string m The mode
+function module.set_mode(m)
+ assert(m == "live" or m == "after")
+ mode = m
+end
+
+--- Add an initialization callback.
+-- This callback will be executed before the mouse grabbing starts.
+-- @function awful.mouse.resize.add_enter_callback
+-- @tparam function cb The callback (or nil)
+-- @tparam[default=other] string context The callback context
+function module.add_enter_callback(cb, context)
+ context = context or "other"
+ callbacks.enter[context] = callbacks.enter[context] or {}
+ table.insert(callbacks.enter[context], cb)
+end
+
+--- Add a "move" callback.
+-- This callback is executed in "after" mode (see `set_mode`) instead of
+-- applying the operation.
+-- @function awful.mouse.resize.add_move_callback
+-- @tparam function cb The callback (or nil)
+-- @tparam[default=other] string context The callback context
+function module.add_move_callback(cb, context)
+ context = context or "other"
+ callbacks.move[context] = callbacks.move[context] or {}
+ table.insert(callbacks.move[context], cb)
+end
+
+--- Add a "leave" callback
+-- This callback is executed just before the `mousegrabber` stop
+-- @function awful.mouse.resize.add_leave_callback
+-- @tparam function cb The callback (or nil)
+-- @tparam[default=other] string context The callback context
+function module.add_leave_callback(cb, context)
+ context = context or "other"
+ callbacks.leave[context] = callbacks.leave[context] or {}
+ table.insert(callbacks.leave[context], cb)
+end
+
+-- Resize, the drawable.
+--
+-- Valid `args` are:
+--
+-- * *enter_callback*: A function called before the `mousegrabber` start
+-- * *move_callback*: A function called when the mouse move
+-- * *leave_callback*: A function called before the `mousegrabber` is released
+-- * *mode*: The resize mode
+--
+-- @function awful.mouse.resize
+-- @tparam client client A client
+-- @tparam[default=mouse.resize] string context The resizing context
+-- @tparam[opt={}] table args A set of `awful.placement` arguments
+
+local function handler(_, client, context, args) --luacheck: no unused_args
+ args = args or {}
+ context = context or "mouse.resize"
+
+ local placement = args.placement
+
+ if type(placement) == "string" and aplace[placement] then
+ placement = aplace[placement]
+ end
+
+ -- Extend the table with the default arguments
+ args = setmetatable(
+ {
+ placement = placement or aplace.resize_to_mouse,
+ mode = args.mode or mode,
+ pretend = true,
+ },
+ {__index = args or {}}
+ )
+
+ local geo
+
+ for _, cb in ipairs(callbacks.enter[context] or {}) do
+ geo = cb(client, args)
+
+ if geo == false then
+ return false
+ end
+ end
+
+ if args.enter_callback then
+ geo = args.enter_callback(client, args)
+
+ if geo == false then
+ return false
+ end
+ end
+
+ geo = nil
+
+ -- Select the cursor
+ local tcontext = context:gsub('[.]', '_')
+ local corner = args.corner and ("_".. args.corner) or ""
+
+ local cursor = beautiful["cursor_"..tcontext]
+ or cursors[context..corner]
+ or cursors[context]
+ or "fleur"
+
+ -- Execute the placement function and use request::geometry
+ capi.mousegrabber.run(function (_mouse)
+ if not client.valid then return end
+
+ -- Resize everytime the mouse move (default behavior)
+ if args.mode == "live" then
+ -- Get the new geometry
+ geo = setmetatable(args.placement(client, args),{__index=args})
+ end
+
+ -- Execute the move callbacks. This can be used to add features such as
+ -- snap or adding fancy graphical effects.
+ for _, cb in ipairs(callbacks.move[context] or {}) do
+ -- If something is returned, assume it is a modified geometry
+ geo = cb(client, geo, args) or geo
+
+ if geo == false then
+ return false
+ end
+ end
+
+ if args.move_callback then
+ geo = args.move_callback(client, geo, args)
+
+ if geo == false then
+ return false
+ end
+ end
+
+ -- In case it was modified
+ setmetatable(geo,{__index=args})
+
+ if args.mode == "live" then
+ -- Ask the resizing handler to resize the client
+ client:emit_signal( req, context, geo)
+ end
+
+ -- Quit when the button is released
+ for _,v in pairs(_mouse.buttons) do
+ if v then return true end
+ end
+
+ -- Only resize after the mouse is released, this avoid losing content
+ -- in resize sensitive apps such as XTerm or allow external modules
+ -- to implement custom resizing.
+ if args.mode == "after" then
+ -- Get the new geometry
+ geo = args.placement(client, args)
+
+ -- Ask the resizing handler to resize the client
+ client:emit_signal( req, context, geo)
+ end
+
+ geo = nil
+
+ for _, cb in ipairs(callbacks.leave[context] or {}) do
+ geo = cb(client, geo, args)
+ end
+
+ if args.leave_callback then
+ geo = args.leave_callback(client, geo, args)
+ end
+
+ if not geo then return false end
+
+ -- In case it was modified
+ setmetatable(geo,{__index=args})
+
+ client:emit_signal( req, context, geo)
+
+ return false
+ end, cursor)
+end
+
+return setmetatable(module, {__call=handler})
diff --git a/lib/awful/mouse/snap.lua b/lib/awful/mouse/snap.lua
new file mode 100644
index 0000000..048a679
--- /dev/null
+++ b/lib/awful/mouse/snap.lua
@@ -0,0 +1,266 @@
+---------------------------------------------------------------------------
+--- Mouse snapping related functions
+--
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2008 Julien Danjou
+-- @submodule mouse
+---------------------------------------------------------------------------
+
+local aclient = require("awful.client")
+local resize = require("awful.mouse.resize")
+local aplace = require("awful.placement")
+local wibox = require("wibox")
+local beautiful = require("beautiful")
+local color = require("gears.color")
+local shape = require("gears.shape")
+local cairo = require("lgi").cairo
+
+local capi = {
+ root = root,
+ mouse = mouse,
+ screen = screen,
+ client = client,
+ mousegrabber = mousegrabber,
+}
+
+local module = {
+ default_distance = 8
+}
+
+local placeholder_w = nil
+
+local function show_placeholder(geo)
+ if not geo then
+ if placeholder_w then
+ placeholder_w.visible = false
+ end
+ return
+ end
+
+ placeholder_w = placeholder_w or wibox {
+ ontop = true,
+ bg = color(beautiful.snap_bg or beautiful.bg_urgent or "#ff0000"),
+ }
+
+ placeholder_w:geometry(geo)
+
+ local img = cairo.ImageSurface(cairo.Format.A1, geo.width, geo.height)
+ local cr = cairo.Context(img)
+
+ cr:set_operator(cairo.Operator.CLEAR)
+ cr:set_source_rgba(0,0,0,1)
+ cr:paint()
+ cr:set_operator(cairo.Operator.SOURCE)
+ cr:set_source_rgba(1,1,1,1)
+
+ local line_width = beautiful.snap_border_width or 5
+ cr:set_line_width(beautiful.xresources.apply_dpi(line_width))
+
+ local f = beautiful.snap_shape or function()
+ cr:translate(line_width,line_width)
+ shape.rounded_rect(cr,geo.width-2*line_width,geo.height-2*line_width, 10)
+ end
+
+ f(cr, geo.width, geo.height)
+
+ cr:stroke()
+
+ placeholder_w.shape_bounding = img._native
+
+ placeholder_w.visible = true
+end
+
+local function build_placement(snap, axis)
+ return aplace.scale
+ + aplace[snap]
+ + (
+ axis and aplace["maximize_"..axis] or nil
+ )
+end
+
+local function detect_screen_edges(c, snap)
+ local coords = capi.mouse.coords()
+
+ local sg = c.screen.geometry
+
+ local v, h = nil
+
+ if math.abs(coords.x) <= snap + sg.x and coords.x >= sg.x then
+ h = "left"
+ elseif math.abs((sg.x + sg.width) - coords.x) <= snap then
+ h = "right"
+ end
+
+ if math.abs(coords.y) <= snap + sg.y and coords.y >= sg.y then
+ v = "top"
+ elseif math.abs((sg.y + sg.height) - coords.y) <= snap then
+ v = "bottom"
+ end
+
+ return v, h
+end
+
+local current_snap, current_axis = nil
+
+local function detect_areasnap(c, distance)
+ local old_snap = current_snap
+ local v, h = detect_screen_edges(c, distance)
+
+ if v and h then
+ current_snap = v.."_"..h
+ else
+ current_snap = v or h or nil
+ end
+
+ if old_snap == current_snap then return end
+
+ current_axis = ((v and not h) and "horizontally")
+ or ((h and not v) and "vertically")
+ or nil
+
+ -- Show the expected geometry outline
+ show_placeholder(
+ current_snap and build_placement(current_snap, current_axis)(c, {
+ to_percent = 0.5,
+ honor_workarea = true,
+ pretend = true
+ }) or nil
+ )
+
+end
+
+local function apply_areasnap(c, args)
+ if not current_snap then return end
+
+ -- Remove the move offset
+ args.offset = {}
+
+ placeholder_w.visible = false
+
+ return build_placement(current_snap, current_axis)(c,{
+ to_percent = 0.5,
+ honor_workarea = true,
+ })
+end
+
+local function snap_outside(g, sg, snap)
+ if g.x < snap + sg.x + sg.width and g.x > sg.x + sg.width then
+ g.x = sg.x + sg.width
+ elseif g.x + g.width < sg.x and g.x + g.width > sg.x - snap then
+ g.x = sg.x - g.width
+ end
+ if g.y < snap + sg.y + sg.height and g.y > sg.y + sg.height then
+ g.y = sg.y + sg.height
+ elseif g.y + g.height < sg.y and g.y + g.height > sg.y - snap then
+ g.y = sg.y - g.height
+ end
+ return g
+end
+
+local function snap_inside(g, sg, snap)
+ local edgev = 'none'
+ local edgeh = 'none'
+ if math.abs(g.x) < snap + sg.x and g.x > sg.x then
+ edgev = 'left'
+ g.x = sg.x
+ elseif math.abs((sg.x + sg.width) - (g.x + g.width)) < snap then
+ edgev = 'right'
+ g.x = sg.x + sg.width - g.width
+ end
+ if math.abs(g.y) < snap + sg.y and g.y > sg.y then
+ edgeh = 'top'
+ g.y = sg.y
+ elseif math.abs((sg.y + sg.height) - (g.y + g.height)) < snap then
+ edgeh = 'bottom'
+ g.y = sg.y + sg.height - g.height
+ end
+
+ -- What is the dominant dimension?
+ if g.width > g.height then
+ return g, edgeh
+ else
+ return g, edgev
+ end
+end
+
+--- Snap a client to the closest client or screen edge.
+-- @function awful.mouse.snap
+-- @param c The client to snap.
+-- @param snap The pixel to snap clients.
+-- @param x The client x coordinate.
+-- @param y The client y coordinate.
+-- @param fixed_x True if the client isn't allowed to move in the x direction.
+-- @param fixed_y True if the client isn't allowed to move in the y direction.
+function module.snap(c, snap, x, y, fixed_x, fixed_y)
+ snap = snap or module.default_distance
+ c = c or capi.client.focus
+ local cur_geom = c:geometry()
+ local geom = c:geometry()
+ geom.width = geom.width + (2 * c.border_width)
+ geom.height = geom.height + (2 * c.border_width)
+ local edge
+ geom.x = x or geom.x
+ geom.y = y or geom.y
+
+ geom, edge = snap_inside(geom, c.screen.geometry, snap)
+ geom = snap_inside(geom, c.screen.workarea, snap)
+
+ -- Allow certain windows to snap to the edge of the workarea.
+ -- Only allow docking to workarea for consistency/to avoid problems.
+ if c.dockable then
+ local struts = c:struts()
+ struts['left'] = 0
+ struts['right'] = 0
+ struts['top'] = 0
+ struts['bottom'] = 0
+ if edge ~= "none" and c.floating then
+ if edge == "left" or edge == "right" then
+ struts[edge] = cur_geom.width
+ elseif edge == "top" or edge == "bottom" then
+ struts[edge] = cur_geom.height
+ end
+ end
+ c:struts(struts)
+ end
+
+ for _, snapper in ipairs(aclient.visible(c.screen)) do
+ if snapper ~= c then
+ local snapper_geom = snapper:geometry()
+ snapper_geom.width = snapper_geom.width + (2 * snapper.border_width)
+ snapper_geom.height = snapper_geom.height + (2 * snapper.border_width)
+ geom = snap_outside(geom, snapper_geom, snap)
+ end
+ end
+
+ geom.width = geom.width - (2 * c.border_width)
+ geom.height = geom.height - (2 * c.border_width)
+
+ -- It's easiest to undo changes afterwards if they're not allowed
+ if fixed_x then geom.x = cur_geom.x end
+ if fixed_y then geom.y = cur_geom.y end
+
+ return geom
+end
+
+-- Enable edge snapping
+resize.add_move_callback(function(c, geo, args)
+ -- Screen edge snapping (areosnap)
+ if (module.edge_enabled ~= false)
+ and args and (args.snap == nil or args.snap) then
+ detect_areasnap(c, 16)
+ end
+
+ -- Snapping between clients
+ if (module.client_enabled ~= false)
+ and args and (args.snap == nil or args.snap) then
+ return module.snap(c, args.snap, geo.x, geo.y)
+ end
+end, "mouse.move")
+
+-- Apply the aerosnap
+resize.add_leave_callback(function(c, _, args)
+ if module.edge_enabled == false then return end
+ return apply_areasnap(c, args)
+end, "mouse.move")
+
+return setmetatable(module, {__call = function(_, ...) return module.snap(...) end})
diff --git a/lib/awful/placement.lua b/lib/awful/placement.lua
new file mode 100644
index 0000000..d288f49
--- /dev/null
+++ b/lib/awful/placement.lua
@@ -0,0 +1,1694 @@
+---------------------------------------------------------------------------
+--- Algorithms used to place various drawables.
+--
+-- The functions provided by this module all follow the same arguments
+-- conventions. This allow:
+--
+-- * To use them in various other module as
+-- [visitor objects](https://en.wikipedia.org/wiki/Visitor_pattern)
+-- * Turn each function into an API with various common customization parameters.
+-- * Re-use the same functions for the `mouse`, `client`s, `screen`s and `wibox`es
+--
+--
+-- <h3>Compositing</h3>
+--
+-- It is possible to compose placement function using the `+` or `*` operator:
+--
+--
+--
+--![Usage example](../images/AUTOGEN_awful_placement_compose.svg)
+--
+--
+-- -- 'right' will be replaced by 'left'
+-- local f = (awful.placement.right + awful.placement.left)
+-- f(client.focus)
+--
+--
+--
+--![Usage example](../images/AUTOGEN_awful_placement_compose2.svg)
+--
+--
+-- -- Simulate Windows 7 'edge snap' (also called aero snap) feature
+-- local axis = 'vertically'
+-- local f = awful.placement.scale
+-- + awful.placement.left
+-- + (axis and awful.placement['maximize_'..axis] or nil)
+-- local geo = f(client.focus, {honor_workarea=true, to_percent = 0.5})
+--
+-- <h3>Common arguments</h3>
+--
+-- **pretend** (*boolean*):
+--
+-- Do not apply the new geometry. This is useful if only the return values is
+-- necessary.
+--
+-- **honor_workarea** (*boolean*):
+--
+-- Take workarea into account when placing the drawable (default: false)
+--
+-- **honor_padding** (*boolean*):
+--
+-- Take the screen padding into account (see `screen.padding`)
+--
+-- **tag** (*tag*):
+--
+-- Use a tag geometry
+--
+-- **margins** (*number* or *table*):
+--
+-- A table with left, right, top, bottom keys or a number
+--
+-- **parent** (client, wibox, mouse or screen):
+--
+-- A parent drawable to use a base geometry
+--
+-- **bounding_rect** (table):
+--
+-- A bounding rectangle
+--
+-- **attach** (*boolean*):
+--
+-- When the parent geometry (like the screen) changes, re-apply the placement
+-- function. This will add a `detach_callback` function to the drawable. Call
+-- this to detach the function. This will be called automatically when a new
+-- attached function is set.
+--
+-- **offset** (*table or number*):
+--
+-- The offset(s) to apply to the new geometry.
+--
+-- **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.
+--
+-- **update_workarea** (*boolean*):
+--
+-- If *attach* is true, also update the screen workarea.
+--
+-- @author Emmanuel Lepage Vallee &lt;elv1313@gmail.com&gt;
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2008 Julien Danjou, Emmanuel Lepage Vallee 2016
+-- @module awful.placement
+---------------------------------------------------------------------------
+
+-- Grab environment we need
+local ipairs = ipairs
+local pairs = pairs
+local math = math
+local table = table
+local capi =
+{
+ screen = screen,
+ mouse = mouse,
+ client = client
+}
+local client = require("awful.client")
+local layout = require("awful.layout")
+local a_screen = require("awful.screen")
+local grect = require("gears.geometry").rectangle
+local util = require("awful.util")
+local cairo = require( "lgi" ).cairo
+local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
+
+local function get_screen(s)
+ return s and capi.screen[s]
+end
+
+local wrap_client = nil
+local placement
+
+-- Store function -> keys
+local reverse_align_map = {}
+
+-- Forward declarations
+local area_common
+local wibox_update_strut
+local attach
+
+--- Allow multiple placement functions to be daisy chained.
+-- This also allow the functions to be aware they are being chained and act
+-- upon the previous nodes results to avoid unnecessary processing or deduce
+-- extra paramaters/arguments.
+local function compose(...)
+ local queue = {}
+
+ local nodes = {...}
+
+ -- Allow placement.foo + (var == 42 and placement.bar)
+ if not nodes[2] then
+ return nodes[1]
+ end
+
+ -- nodes[1] == self, nodes[2] == other
+ for _, w in ipairs(nodes) do
+ -- Build an execution queue
+ if w.context and w.context == "compose" then
+ for _, elem in ipairs(w.queue or {}) do
+ table.insert(queue, elem)
+ end
+ else
+ table.insert(queue, w)
+ end
+ end
+
+ local ret
+ ret = wrap_client(function(d, args, ...)
+ local rets = {}
+ local last_geo = nil
+
+ -- As some functions may have to take into account results from
+ -- previously execued ones, add the `composition_results` hint.
+ args = setmetatable({composition_results=rets}, {__index=args})
+
+ -- Only apply the geometry once, not once per chain node, to do this,
+ -- Force the "pretend" argument and restore the original value for
+ -- the last node.
+ local attach_real = args.attach
+ args.pretend = true
+ args.attach = false
+ args.offset = {}
+
+ for k, f in ipairs(queue) do
+ if k == #queue then
+ -- Let them fallback to the parent table
+ args.pretend = nil
+ args.offset = nil
+ end
+
+ local r = {f(d, args, ...)}
+ last_geo = r[1] or last_geo
+ args.override_geometry = last_geo
+
+ -- Keep the return value, store one per context
+ if f.context then
+ -- When 2 composition queue are executed, merge the return values
+ if f.context == "compose" then
+ for k2,v in pairs(r) do
+ rets[k2] = v
+ end
+ else
+ rets[f.context] = r
+ end
+ end
+ end
+
+ if attach_real then
+ args.attach = true
+ attach(d, ret, args)
+ end
+
+ return last_geo, rets
+ end, "compose")
+
+ ret.queue = queue
+
+ return ret
+end
+
+wrap_client = function(f, context)
+ return setmetatable(
+ {
+ is_placement= true,
+ context = context,
+ },
+ {
+ __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)
+--
+placement = setmetatable({}, {
+ __index = placement_private,
+ __newindex = function(_, k, f)
+ placement_private[k] = wrap_client(f, k)
+ end
+})
+
+-- 3x3 matrix of the valid sides and corners
+local corners3x3 = {{"top_left" , "top" , "top_right" },
+ {"left" , nil , "right" },
+ {"bottom_left", "bottom" , "bottom_right"}}
+
+-- 2x2 matrix of the valid sides and corners
+local corners2x2 = {{"top_left" , "top_right" },
+ {"bottom_left", "bottom_right"}}
+
+-- Compute the new `x` and `y`.
+-- The workarea position need to be applied by the caller
+local align_map = {
+ top_left = function(_ , _ , _ , _ ) return {x=0 , y=0 } end,
+ top_right = function(sw, _ , dw, _ ) return {x=sw-dw , y=0 } end,
+ bottom_left = function(_ , sh, _ , dh) return {x=0 , y=sh-dh } end,
+ bottom_right = function(sw, sh, dw, dh) return {x=sw-dw , y=sh-dh } end,
+ left = function(_ , sh, _ , dh) return {x=0 , y=sh/2-dh/2} end,
+ right = function(sw, sh, dw, dh) return {x=sw-dw , y=sh/2-dh/2} end,
+ top = function(sw, _ , dw, _ ) return {x=sw/2-dw/2, y=0 } end,
+ bottom = function(sw, sh, dw, dh) return {x=sw/2-dw/2, y=sh-dh } end,
+ centered = function(sw, sh, dw, dh) return {x=sw/2-dw/2, y=sh/2-dh/2} end,
+ center_vertical = function(_ , sh, _ , dh) return {x= nil , y=sh-dh } end,
+ center_horizontal = function(sw, _ , dw, _ ) return {x=sw/2-dw/2, y= nil } end,
+}
+
+-- Some parameters to correctly compute the final size
+local resize_to_point_map = {
+ -- Corners
+ top_left = {p1= nil , p2={1,1}, x_only=false, y_only=false, align="bottom_right"},
+ top_right = {p1={0,1} , p2= nil , x_only=false, y_only=false, align="bottom_left" },
+ bottom_left = {p1= nil , p2={1,0}, x_only=false, y_only=false, align="top_right" },
+ bottom_right = {p1={0,0} , p2= nil , x_only=false, y_only=false, align="top_left" },
+
+ -- Sides
+ left = {p1= nil , p2={1,1}, x_only=true , y_only=false, align="top_right" },
+ right = {p1={0,0} , p2= nil , x_only=true , y_only=false, align="top_left" },
+ top = {p1= nil , p2={1,1}, x_only=false, y_only=true , align="bottom_left" },
+ bottom = {p1={0,0} , p2= nil , x_only=false, y_only=true , align="top_left" },
+}
+
+-- Outer position matrix
+-- 1=best case, 2=fallback
+local outer_positions = {
+ left1 = function(r, w, _) return {x=r.x-w , y=r.y }, "down" end,
+ left2 = function(r, w, h) return {x=r.x-w , y=r.y-h+r.height }, "up" end,
+ right1 = function(r, _, _) return {x=r.x , y=r.y }, "down" end,
+ right2 = function(r, _, h) return {x=r.x , y=r.y-h+r.height }, "up" end,
+ top1 = function(r, _, h) return {x=r.x , y=r.y-h }, "right" end,
+ top2 = function(r, w, h) return {x=r.x-w+r.width, y=r.y-h }, "left" end,
+ bottom1 = function(r, _, _) return {x=r.x , y=r.y }, "right" end,
+ bottom2 = function(r, w, _) return {x=r.x-w+r.width, y=r.y }, "left" end,
+}
+
+--- 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
+ data[d][reqtype].border_width = d.border_width
+end
+
+--- Get the margins and offset
+-- @tparam table args The arguments
+-- @treturn table The margins
+-- @treturn table The offsets
+local function get_decoration(args)
+ local offset = args.offset
+
+ -- Offset are "blind" values added to the output
+ offset = type(offset) == "number" and {
+ x = offset,
+ y = offset,
+ width = offset,
+ height = offset,
+ } or args.offset or {}
+
+ -- Margins are distances on each side to substract from the area`
+ local m = type(args.margins) == "table" and args.margins or {
+ left = args.margins or 0 , right = args.margins or 0,
+ top = args.margins or 0 , bottom = args.margins or 0
+ }
+
+ return m, offset
+end
+
+--- Apply some modifications before applying the new geometry.
+-- @tparam table new_geo The new geometry
+-- @tparam table args The common arguments
+-- @tparam boolean force Always ajust the geometry, even in pretent mode. This
+-- should only be used when returning the final geometry as it would otherwise
+-- mess the pipeline.
+-- @treturn table|nil The new geometry
+local function fix_new_geometry(new_geo, args, force)
+ if (args.pretend and not force) or not new_geo then return nil end
+
+ local m, offset = get_decoration(args)
+
+ return {
+ x = new_geo.x and (new_geo.x + (offset.x or 0) + (m.left or 0) ),
+ y = new_geo.y and (new_geo.y + (offset.y or 0) + (m.top or 0) ),
+ width = new_geo.width and math.max(
+ 1, (new_geo.width + (offset.width or 0) - (m.left or 0) - (m.right or 0) )
+ ),
+ height = new_geo.height and math.max(
+ 1, (new_geo.height + (offset.height or 0) - (m.top or 0) - (m.bottom or 0) )
+ ),
+ }
+end
+
+-- Get the area covered by a drawin.
+-- @param d The drawin
+-- @tparam[opt=nil] table new_geo A new geometry
+-- @tparam[opt=false] boolean ignore_border_width Ignore the border
+-- @tparam table args the method arguments
+-- @treturn The drawin's area.
+area_common = function(d, new_geo, ignore_border_width, args)
+ -- 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
+
+ -- When using the placement composition along with the "pretend"
+ -- option, it is necessary to keep a "virtual" geometry.
+ if args and args.override_geometry then
+ geometry = util.table.clone(args.override_geometry)
+ end
+
+ geometry.width = geometry.width + 2 * border
+ geometry.height = geometry.height + 2 * border
+ return geometry
+end
+
+--- Get (and optionally set) an object geometry.
+-- Some elements, such as `mouse` and `screen` don't have a `:geometry()`
+-- methods.
+-- @param obj An object
+-- @tparam table args the method arguments
+-- @tparam[opt=nil] table new_geo A new geometry to replace the existing one
+-- @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 = fix_new_geometry(new_geo, args)
+ and obj.coords(new_geo) or obj.coords()
+ return {x=coords.x, y=coords.y, width=0, height=0}
+ elseif obj.geometry then
+ local geo = obj.geometry
+
+ -- It is either a drawable or something that implement its API
+ if type(geo) == "function" then
+ local dgeo = area_common(
+ obj, fix_new_geometry(new_geo, args), ignore_border_width, args
+ )
+
+ -- Apply the margins
+ if args.margins then
+ local delta = get_decoration(args)
+
+ return {
+ x = dgeo.x - (delta.left or 0),
+ y = dgeo.y - (delta.top or 0),
+ width = dgeo.width + (delta.left or 0) + (delta.right or 0),
+ height = dgeo.height + (delta.top or 0) + (delta.bottom or 0),
+ }
+ end
+
+ return dgeo
+ end
+
+ -- It is a screen, it doesn't support setting new sizes.
+ return obj:get_bounding_geometry(args)
+ else
+ assert(false, "Invalid object")
+ end
+end
+
+--- Get the parent geometry from the standardized arguments API shared by all
+-- `awful.placement` methods.
+-- @param obj A screen or a drawable
+-- @tparam table args the method arguments
+-- @treturn table A table with *x*, *y*, *width* and *height*.
+local function get_parent_geometry(obj, args)
+ -- Didable override_geometry, context and other to avoid mutating the state
+ -- or using the wrong geo.
+
+ if args.bounding_rect then
+ return args.bounding_rect
+ elseif args.parent then
+ return geometry_common(args.parent, {})
+ elseif obj.screen then
+ return geometry_common(obj.screen, {
+ honor_padding = args.honor_padding,
+ honor_workarea = args.honor_workarea
+ })
+ else
+ return geometry_common(capi.screen[capi.mouse.screen], args)
+ end
+end
+
+--- Move a point into an area.
+-- This doesn't change the *width* and *height* values, allowing the target
+-- area to be smaller than the source one.
+-- @tparam table source The (larger) geometry to move `target` into
+-- @tparam table target The area to move into `source`
+-- @treturn table A table with *x* and *y* keys
+local function move_into_geometry(source, target)
+ local ret = {x = target.x, y = target.y}
+
+ -- Horizontally
+ if ret.x < source.x then
+ ret.x = source.x
+ elseif ret.x > source.x + source.width then
+ ret.x = source.x + source.width - 1
+ end
+
+ -- Vertically
+ if ret.y < source.y then
+ ret.y = source.y
+ elseif ret.y > source.y + source.height then
+ ret.y = source.y + source.height - 1
+ end
+
+ return ret
+end
+
+-- Update the workarea
+wibox_update_strut = function(d, position, args)
+ -- If the drawable isn't visible, remove the struts
+ if not d.visible then
+ d:struts { left = 0, right = 0, bottom = 0, top = 0 }
+ return
+ end
+
+ -- Detect horizontal or vertical drawables
+ local geo = area_common(d)
+ local vertical = geo.width < geo.height
+
+ -- Look into the `position` string to find the relevants sides to crop from
+ -- the workarea
+ local struts = { left = 0, right = 0, bottom = 0, top = 0 }
+
+ local m = get_decoration(args)
+
+ if vertical then
+ for _, v in ipairs {"right", "left"} do
+ if (not position) or position:match(v) then
+ struts[v] = geo.width + m[v]
+ end
+ end
+ else
+ for _, v in ipairs {"top", "bottom"} do
+ if (not position) or position:match(v) then
+ struts[v] = geo.height + m[v]
+ end
+ end
+ end
+
+ -- Update the workarea
+ d:struts(struts)
+end
+
+-- Pin a drawable to a placement function.
+-- Automatically update the position when the size change.
+-- All other arguments will be passed to the `position` function (if any)
+-- @tparam[opt=client.focus] drawable d A drawable (like `client`, `mouse`
+-- or `wibox`)
+-- @param position_f A position name (see `align`) or a position function
+-- @tparam[opt={}] table args Other arguments
+attach = function(d, position_f, args)
+ args = args or {}
+
+ if args.pretend then return end
+
+ if not args.attach then return end
+
+ -- Avoid a connection loop
+ args = setmetatable({attach=false}, {__index=args})
+
+ d = d or capi.client.focus
+ if not d then return end
+
+ if type(position_f) == "string" then
+ position_f = placement[position_f]
+ end
+
+ if not position_f then return end
+
+ -- If there is multiple attached function, there is an high risk of infinite
+ -- loop. While some combinaisons are harmless, other are very hard to debug.
+ --
+ -- Use the placement composition to build explicit multi step attached
+ -- placement functions.
+ if d.detach_callback then
+ d.detach_callback()
+ d.detach_callback = nil
+ end
+
+ local function tracker()
+ position_f(d, args)
+ end
+
+ d:connect_signal("property::width" , tracker)
+ d:connect_signal("property::height" , tracker)
+ d:connect_signal("property::border_width", tracker)
+
+ local function tracker_struts()
+ --TODO this is too fragile and doesn't work with all methods.
+ wibox_update_strut(d, d.position or reverse_align_map[position_f], args)
+ end
+
+ local parent = args.parent or d.screen
+
+ if args.update_workarea then
+ d:connect_signal("property::geometry" , tracker_struts)
+ d:connect_signal("property::visible" , tracker_struts)
+ capi.client.connect_signal("property::struts", tracker_struts)
+
+ tracker_struts()
+ elseif parent == d.screen then
+ if args.honor_workarea then
+ parent:connect_signal("property::workarea", tracker)
+ end
+
+ if args.honor_padding then
+ parent:connect_signal("property::padding", tracker)
+ end
+ end
+
+ -- If there is a parent drawable, screen, also track it.
+ -- Note that tracking the mouse is not supported
+ if parent and parent.connect_signal then
+ parent:connect_signal("property::geometry" , tracker)
+ end
+
+ -- Create a way to detach a placement function
+ function d.detach_callback()
+ d:disconnect_signal("property::width" , tracker)
+ d:disconnect_signal("property::height" , tracker)
+ d:disconnect_signal("property::border_width", tracker)
+ if parent then
+ parent:disconnect_signal("property::geometry" , tracker)
+
+ if parent == d.screen then
+ if args.honor_workarea then
+ parent:disconnect_signal("property::workarea", tracker)
+ end
+
+ if args.honor_padding then
+ parent:disconnect_signal("property::padding", tracker)
+ end
+ end
+ end
+
+ if args.update_workarea then
+ d:disconnect_signal("property::geometry" , tracker_struts)
+ d:disconnect_signal("property::visible" , tracker_struts)
+ capi.client.disconnect_signal("property::struts", tracker_struts)
+ end
+ end
+end
+
+-- Convert 2 points into a rectangle
+local function rect_from_points(p1x, p1y, p2x, p2y)
+ return {
+ x = p1x,
+ y = p1y,
+ width = p2x - p1x,
+ height = p2y - p1y,
+ }
+end
+
+-- Convert a rectangle and matrix info into a point
+local function rect_to_point(rect, corner_i, corner_j)
+ return {
+ x = rect.x + corner_i * math.floor(rect.width ),
+ y = rect.y + corner_j * math.floor(rect.height),
+ }
+end
+
+-- Create a pair of rectangles used to set the relative areas.
+-- v=vertical, h=horizontal
+local function get_cross_sections(abs_geo, mode)
+ if not mode or mode == "cursor" then
+ -- A 1px cross section centered around the mouse position
+ local coords = capi.mouse.coords()
+ return {
+ h = {
+ x = abs_geo.drawable_geo.x ,
+ y = coords.y ,
+ width = abs_geo.drawable_geo.width ,
+ height = 1 ,
+ },
+ v = {
+ x = coords.x ,
+ y = abs_geo.drawable_geo.y ,
+ width = 1 ,
+ height = abs_geo.drawable_geo.height,
+ }
+ }
+ elseif mode == "geometry" then
+ -- The widget geometry extended to reach the end of the drawable
+
+ return {
+ h = {
+ x = abs_geo.drawable_geo.x ,
+ y = abs_geo.y ,
+ width = abs_geo.drawable_geo.width ,
+ height = abs_geo.height ,
+ },
+ v = {
+ x = abs_geo.x ,
+ y = abs_geo.drawable_geo.y ,
+ width = abs_geo.width ,
+ height = abs_geo.drawable_geo.height,
+ }
+ }
+ elseif mode == "cursor_inside" then
+ -- A 1x1 rectangle centered around the mouse position
+
+ local coords = capi.mouse.coords()
+ coords.width,coords.height = 1,1
+ return {h=coords, v=coords}
+ elseif mode == "geometry_inside" then
+ -- The widget absolute geometry, unchanged
+
+ return {h=abs_geo, v=abs_geo}
+ end
+end
+
+-- When a rectangle is embedded into a bigger one, get the regions around
+-- the outline of the bigger rectangle closest to the smaller one (on each side)
+local function get_relative_regions(geo, mode, is_absolute)
+
+ -- Use the mouse position and the wibox/client under it
+ if not geo then
+ local draw = capi.mouse.current_wibox
+ geo = draw and draw:geometry() or capi.mouse.coords()
+ geo.drawable = draw
+ elseif is_absolute then
+ -- Some signals are a bit inconsistent in their arguments convention.
+ -- This little hack tries to mitigate the issue.
+
+ geo.drawable = geo -- is a wibox or client, geometry and object are one
+ -- and the same.
+ elseif (not geo.drawable) and geo.x and geo.width then
+ local coords = capi.mouse.coords()
+
+ -- Check if the mouse is in the rect
+ if coords.x > geo.x and coords.x < geo.x+geo.width and
+ coords.y > geo.y and coords.y < geo.y+geo.height then
+ geo.drawable = capi.mouse.current_wibox
+ end
+
+ -- Maybe there is a client
+ if (not geo.drawable) and capi.mouse.current_client then
+ geo.drawable = capi.mouse.current_client
+ end
+ end
+
+ -- Get the drawable geometry
+ local dpos = geo.drawable and (
+ geo.drawable.drawable and
+ geo.drawable.drawable:geometry()
+ or geo.drawable:geometry()
+ ) or {x=0, y=0}
+
+ -- Compute the absolute widget geometry
+ local abs_widget_geo = is_absolute and geo or {
+ x = dpos.x + geo.x ,
+ y = dpos.y + geo.y ,
+ width = geo.width ,
+ height = geo.height ,
+ drawable = geo.drawable ,
+ }
+
+ abs_widget_geo.drawable_geo = geo.drawable and dpos or geo
+
+ -- Get the point for comparison.
+ local center_point = mode:match("cursor") and capi.mouse.coords() or {
+ x = abs_widget_geo.x + abs_widget_geo.width / 2,
+ y = abs_widget_geo.y + abs_widget_geo.height / 2,
+ }
+
+ -- Get widget regions for both axis
+ local cs = get_cross_sections(abs_widget_geo, mode)
+
+ -- Get the 4 closest points from `center_point` around the wibox
+ local regions = {
+ left = {x = cs.h.x , y = cs.h.y },
+ right = {x = cs.h.x+cs.h.width, y = cs.h.y },
+ top = {x = cs.v.x , y = cs.v.y },
+ bottom = {x = cs.v.x , y = cs.v.y+cs.v.height},
+ }
+
+ -- Assume the section is part of a single screen until someone complains.
+ -- It is much faster to compute and getting it wrong probably has no side
+ -- effects.
+ local s = geo.drawable and geo.drawable.screen or a_screen.getbycoord(
+ center_point.x,
+ center_point.y
+ )
+
+ -- Compute the distance (dp) between the `center_point` and the sides.
+ -- This is only relevant for "cursor" and "cursor_inside" modes.
+ for _, v in pairs(regions) do
+ local dx, dy = v.x - center_point.x, v.y - center_point.y
+
+ v.distance = math.sqrt(dx*dx + dy*dy)
+ v.width = cs.v.width
+ v.height = cs.h.height
+ v.screen = capi.screen[s]
+ end
+
+ return regions
+end
+
+-- Check if the proposed geometry fits the screen
+local function fit_in_bounding(obj, geo, args)
+ local sgeo = get_parent_geometry(obj, args)
+ local region = cairo.Region.create_rectangle(cairo.RectangleInt(sgeo))
+
+ region:intersect(cairo.Region.create_rectangle(
+ cairo.RectangleInt(geo)
+ ))
+
+ local geo2 = region:get_rectangle(0)
+
+ -- If the geometry is the same then it fits, otherwise it will be cropped.
+ return geo2.width == geo.width and geo2.height == geo.height
+end
+
+--- Move a drawable to the closest corner of the parent geometry (such as the
+-- screen).
+--
+-- Valid arguments include the common ones and:
+--
+-- * **include_sides**: Also include the left, right, top and bottom positions
+--
+--
+--
+--![Usage example](../images/AUTOGEN_awful_placement_closest_mouse.svg)
+--
+--**Usage example output**:
+--
+-- Closest corner: top_left
+--
+--
+-- @usage
+-- -- Move the mouse to the closest corner of the focused client
+--awful.placement.closest_corner(mouse, {include_sides=true, parent=c})
+-- -- It is possible to emulate the mouse API to get the closest corner of
+-- -- random area
+--local _, corner = awful.placement.closest_corner(
+-- {coords=function() return {x = 100, y=100} end},
+-- {include_sides = true, bounding_rect = {x=0, y=0, width=200, height=200}}
+--)
+--print('Closest corner:', corner)
+-- @tparam[opt=client.focus] drawable d A drawable (like `client`, `mouse`
+-- or `wibox`)
+-- @tparam[opt={}] table args The arguments
+-- @treturn table The new geometry
+-- @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)
+ local dgeo = geometry_common(d, args)
+
+ local pos = move_into_geometry(sgeo, dgeo)
+
+ local corner_i, corner_j, n
+
+ -- Use the product of 3 to get the closest point in a NxN matrix
+ local function f(_n, mat)
+ n = _n
+ -- The +1 is required to avoid a rounding error when
+ -- pos.x == sgeo.x+sgeo.width
+ corner_i = -math.ceil( ( (sgeo.x - pos.x) * n) / (sgeo.width + 1))
+ corner_j = -math.ceil( ( (sgeo.y - pos.y) * n) / (sgeo.height + 1))
+ return mat[corner_j + 1][corner_i + 1]
+ end
+
+ -- Turn the area into a grid and snap to the cloest point. This size of the
+ -- grid will increase the accuracy. A 2x2 matrix only include the corners,
+ -- at 3x3, this include the sides too technically, a random size would work,
+ -- but without corner names.
+ local grid_size = args.include_sides and 3 or 2
+
+ -- If the point is in the center, use the closest corner
+ local corner = grid_size == 3 and f(3, corners3x3) or f(2, corners2x2)
+
+ -- Transpose the corner back to the original size
+ local new_args = setmetatable({position = corner}, {__index=args})
+ local ngeo = placement_private.align(d, new_args)
+
+ return fix_new_geometry(ngeo, args, true), corner
+end
+
+--- Place the client so no part of it will be outside the screen (workarea).
+--
+--
+--![Usage example](../images/AUTOGEN_awful_placement_no_offscreen.svg)
+--
+--**Usage example output**:
+--
+-- Before: x=-30, y=-30, width=100, height=100
+-- After: x=10, y=10, width=100, height=100
+--
+--
+-- @usage
+--awful.placement.no_offscreen(c)--, {honor_workarea=true, margins=40})
+-- @client c The client.
+-- @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))
+ local screen_geometry = screen.workarea
+
+ if geometry.x + geometry.width > screen_geometry.x + screen_geometry.width then
+ geometry.x = screen_geometry.x + screen_geometry.width - geometry.width
+ end
+ if geometry.x < screen_geometry.x then
+ geometry.x = screen_geometry.x
+ end
+
+ if geometry.y + geometry.height > screen_geometry.y + screen_geometry.height then
+ geometry.y = screen_geometry.y + screen_geometry.height - geometry.height
+ end
+ if geometry.y < screen_geometry.y then
+ geometry.y = screen_geometry.y
+ end
+
+ return c:geometry {
+ x = geometry.x,
+ y = geometry.y
+ }
+end
+
+--- Place the client where there's place available with minimum overlap.
+--
+--
+--![Usage example](../images/AUTOGEN_awful_placement_no_overlap.svg)
+--
+-- @usage
+--awful.placement.no_overlap(client.focus)
+--local x,y = screen[4].geometry.x, screen[4].geometry.y
+-- @param c The client.
+-- @treturn table The new geometry
+function placement.no_overlap(c)
+ c = c or capi.client.focus
+ local geometry = area_common(c)
+ local screen = get_screen(c.screen or a_screen.getbycoord(geometry.x, geometry.y))
+ local cls = client.visible(screen)
+ local curlay = layout.get()
+ local areas = { screen.workarea }
+ for _, cl in pairs(cls) do
+ if cl ~= c and cl.type ~= "desktop" and (cl.floating or curlay == layout.suit.floating) then
+ areas = grect.area_remove(areas, area_common(cl))
+ end
+ end
+
+ -- Look for available space
+ local found = false
+ local new = { x = geometry.x, y = geometry.y, width = 0, height = 0 }
+ for _, r in ipairs(areas) do
+ if r.width >= geometry.width
+ and r.height >= geometry.height
+ and r.width * r.height > new.width * new.height then
+ found = true
+ new = r
+ -- Check if the client's current position is available
+ -- and prefer that one (why move it around pointlessly?)
+ if geometry.x >= r.x
+ and geometry.y >= r.y
+ and geometry.x + geometry.width <= r.x + r.width
+ and geometry.y + geometry.height <= r.y + r.height then
+ new.x = geometry.x
+ new.y = geometry.y
+ end
+ end
+ end
+
+ -- We did not find an area with enough space for our size:
+ -- just take the biggest available one and go in.
+ -- This makes sure to have the whole screen's area in case it has been
+ -- removed.
+ if not found then
+ if #areas == 0 then
+ areas = { screen.workarea }
+ end
+ for _, r in ipairs(areas) do
+ if r.width * r.height > new.width * new.height then
+ new = r
+ end
+ end
+ end
+
+ -- Restore height and width
+ new.width = geometry.width
+ new.height = geometry.height
+
+ return c:geometry({ x = new.x, y = new.y })
+end
+
+--- Place the client under the mouse.
+--
+--
+--![Usage example](../images/AUTOGEN_awful_placement_under_mouse.svg)
+--
+-- @usage
+--awful.placement.under_mouse(client.focus)
+-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`)
+-- @tparam[opt={}] table args Other arguments
+-- @treturn table The new geometry
+function placement.under_mouse(d, args)
+ args = add_context(args, "under_mouse")
+ d = d or capi.client.focus
+
+ local m_coords = capi.mouse.coords()
+
+ local ngeo = geometry_common(d, args)
+ ngeo.x = math.floor(m_coords.x - ngeo.width / 2)
+ ngeo.y = math.floor(m_coords.y - ngeo.height / 2)
+
+ local bw = d.border_width or 0
+ ngeo.width = ngeo.width - 2*bw
+ ngeo.height = ngeo.height - 2*bw
+
+ geometry_common(d, args, ngeo)
+
+ return fix_new_geometry(ngeo, args, true)
+end
+
+--- Place the client next to the mouse.
+--
+-- It will place `c` next to the mouse pointer, trying the following positions
+-- in this order: right, left, above and below.
+--
+--
+--![Usage example](../images/AUTOGEN_awful_placement_next_to_mouse.svg)
+--
+-- @usage
+--awful.placement.next_to_mouse(client.focus)
+-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`)
+-- @tparam[opt={}] table args Other arguments
+-- @treturn table The new geometry
+function placement.next_to_mouse(d, args)
+ if type(args) == "number" then
+ util.deprecate(
+ "awful.placement.next_to_mouse offset argument is deprecated"..
+ " use awful.placement.next_to_mouse(c, {offset={x=...}})"
+ )
+ args = nil
+ end
+
+ local old_args = args or {}
+
+ args = add_context(args, "next_to_mouse")
+ d = d or capi.client.focus
+
+ local sgeo = get_parent_geometry(d, args)
+
+ args.pretend = true
+ args.parent = capi.mouse
+
+ local ngeo = placement.left(d, args)
+
+ if ngeo.x + ngeo.width > sgeo.x+sgeo.width then
+ ngeo = placement.right(d, args)
+ else
+ -- It is _next_ to mouse, not under_mouse
+ ngeo.x = ngeo.x+1
+ end
+
+ args.pretend = old_args.pretend
+
+ geometry_common(d, args, ngeo)
+
+ attach(d, placement.next_to_mouse, old_args)
+
+ return fix_new_geometry(ngeo, args, true)
+end
+
+--- Resize the drawable to the cursor.
+--
+-- Valid args:
+--
+-- * *axis*: The axis (vertical or horizontal). If none is
+-- specified, then the drawable will be resized on both axis.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_awful_placement_resize_to_mouse.svg)
+--
+-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`)
+-- @tparam[opt={}] table args Other arguments
+-- @treturn table The new geometry
+function placement.resize_to_mouse(d, args)
+ d = d or capi.client.focus
+ args = add_context(args, "resize_to_mouse")
+
+ local coords = capi.mouse.coords()
+ local ngeo = geometry_common(d, args)
+ local h_only = args.axis == "horizontal"
+ local v_only = args.axis == "vertical"
+
+ -- To support both growing and shrinking the drawable, it is necessary
+ -- to decide to use either "north or south" and "east or west" directions.
+ -- Otherwise, the result will always be 1x1
+ local _, closest_corner = placement.closest_corner(capi.mouse, {
+ parent = d,
+ pretend = true,
+ include_sides = args.include_sides or false,
+ })
+
+ -- Given "include_sides" wasn't set, it will always return a name
+ -- with the 2 axis. If only one axis is needed, adjust the result
+ if h_only then
+ closest_corner = closest_corner:match("left") or closest_corner:match("right")
+ elseif v_only then
+ closest_corner = closest_corner:match("top") or closest_corner:match("bottom")
+ end
+
+ -- Use p0 (mouse), p1 and p2 to create a rectangle
+ local pts = resize_to_point_map[closest_corner]
+ local p1 = pts.p1 and rect_to_point(ngeo, pts.p1[1], pts.p1[2]) or coords
+ local p2 = pts.p2 and rect_to_point(ngeo, pts.p2[1], pts.p2[2]) or coords
+
+ -- Create top_left and bottom_right points, convert to rectangle
+ ngeo = rect_from_points(
+ pts.y_only and ngeo.x or math.min(p1.x, p2.x),
+ pts.x_only and ngeo.y or math.min(p1.y, p2.y),
+ pts.y_only and ngeo.x + ngeo.width or math.max(p2.x, p1.x),
+ pts.x_only and ngeo.y + ngeo.height or math.max(p2.y, p1.y)
+ )
+
+ local bw = d.border_width or 0
+
+ for _, a in ipairs {"width", "height"} do
+ ngeo[a] = ngeo[a] - 2*bw
+ end
+
+ -- Now, correct the geometry by the given size_hints offset
+ if d.apply_size_hints then
+ local w, h = d:apply_size_hints(
+ ngeo.width,
+ ngeo.height
+ )
+ local offset = align_map[pts.align](w, h, ngeo.width, ngeo.height)
+ ngeo.x = ngeo.x - offset.x
+ ngeo.y = ngeo.y - offset.y
+ end
+
+ geometry_common(d, args, ngeo)
+
+ return fix_new_geometry(ngeo, args, true)
+end
+
+--- Move the drawable (client or wibox) `d` to a screen position or side.
+--
+-- Supported args.positions are:
+--
+-- * top_left
+-- * top_right
+-- * bottom_left
+-- * bottom_right
+-- * left
+-- * right
+-- * top
+-- * bottom
+-- * centered
+-- * center_vertical
+-- * center_horizontal
+--
+--
+--
+--![Usage example](../images/AUTOGEN_awful_placement_align.svg)
+--
+-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`)
+-- @tparam[opt={}] table args Other arguments
+-- @treturn table The new geometry
+function placement.align(d, args)
+ args = add_context(args, "align")
+ d = d or capi.client.focus
+
+ if not d or not args.position then return end
+
+ local sgeo = get_parent_geometry(d, args)
+ local dgeo = geometry_common(d, args)
+ local bw = d.border_width or 0
+
+ local pos = align_map[args.position](
+ sgeo.width ,
+ sgeo.height,
+ dgeo.width ,
+ dgeo.height
+ )
+
+ local ngeo = {
+ 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,
+ }
+
+ geometry_common(d, args, ngeo)
+
+ attach(d, placement[args.position], args)
+
+ return fix_new_geometry(ngeo, args, true)
+end
+
+-- Add the alias functions
+for k in pairs(align_map) do
+ placement[k] = function(d, args)
+ args = add_context(args, k)
+ args.position = k
+ return placement_private.align(d, args)
+ end
+ reverse_align_map[placement[k]] = k
+end
+
+-- Add the documentation for align alias
+
+---
+-- Align a client to the top left of the parent area.
+--
+--![Usage example](../images/AUTOGEN_awful_placement_top_left.svg)
+-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`)
+-- @tparam[opt={}] table args Other arguments')
+-- @treturn table The new geometry
+-- @name top_left
+-- @class function
+--
+-- @usage
+--awful.placement.top_left(client.focus)
+
+---
+-- Align a client to the top right of the parent area.
+--
+--![Usage example](../images/AUTOGEN_awful_placement_top_right.svg)
+-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`)
+-- @tparam[opt={}] table args Other arguments')
+-- @treturn table The new geometry
+-- @name top_right
+-- @class function
+--
+-- @usage
+--awful.placement.top_right(client.focus)
+
+---
+-- Align a client to the bottom left of the parent area.
+--
+--![Usage example](../images/AUTOGEN_awful_placement_bottom_left.svg)
+-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`)
+-- @tparam[opt={}] table args Other arguments')
+-- @treturn table The new geometry
+-- @name bottom_left
+-- @class function
+--
+-- @usage
+--awful.placement.bottom_left(client.focus)
+
+---
+-- Align a client to the bottom right of the parent area.
+--
+--![Usage example](../images/AUTOGEN_awful_placement_bottom_right.svg)
+-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`)
+-- @tparam[opt={}] table args Other arguments')
+-- @treturn table The new geometry
+-- @name bottom_right
+-- @class function
+--
+-- @usage
+--awful.placement.bottom_right(client.focus)
+
+---
+-- Align a client to the left of the parent area.
+--
+--![Usage example](../images/AUTOGEN_awful_placement_left.svg)
+-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`)
+-- @tparam[opt={}] table args Other arguments')
+-- @treturn table The new geometry
+-- @name left
+-- @class function
+--
+-- @usage
+--awful.placement.left(client.focus)
+
+---
+-- Align a client to the right of the parent area.
+--
+--![Usage example](../images/AUTOGEN_awful_placement_right.svg)
+-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`)
+-- @tparam[opt={}] table args Other arguments')
+-- @treturn table The new geometry
+-- @name right
+-- @class function
+--
+-- @usage
+--awful.placement.right(client.focus)
+
+---
+-- Align a client to the top of the parent area.
+--
+--![Usage example](../images/AUTOGEN_awful_placement_top.svg)
+-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`)
+-- @tparam[opt={}] table args Other arguments')
+-- @treturn table The new geometry
+-- @name top
+-- @class function
+--
+-- @usage
+--awful.placement.top(client.focus)
+--assert(c.x == screen[1].geometry.width/2-40/2-c.border_width)
+
+---
+-- Align a client to the bottom of the parent area.
+--
+--![Usage example](../images/AUTOGEN_awful_placement_bottom.svg)
+-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`)
+-- @tparam[opt={}] table args Other arguments')
+-- @treturn table The new geometry
+-- @name bottom
+-- @class function
+--
+-- @usage
+--awful.placement.bottom(client.focus)
+
+---
+-- Align a client to the center of the parent area.
+--
+--![Usage example](../images/AUTOGEN_awful_placement_centered.svg)
+-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`)
+-- @tparam[opt={}] table args Other arguments')
+-- @treturn table The new geometry
+-- @name centered
+-- @class function
+--
+-- @usage
+--awful.placement.centered(client.focus)
+
+---
+-- Align a client to the vertical center of the parent area.
+--
+--![Usage example](../images/AUTOGEN_awful_placement_center_vertical.svg)
+-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`)
+-- @tparam[opt={}] table args Other arguments')
+-- @name center_vertical
+-- @class function
+--
+-- @usage
+--awful.placement.center_vertical(client.focus)
+
+---
+-- Align a client to the horizontal center left of the parent area.
+--
+--![Usage example](../images/AUTOGEN_awful_placement_center_horizontal.svg)
+-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`)
+-- @tparam[opt={}] table args Other arguments')
+-- @treturn table The new geometry
+-- @name center_horizontal
+-- @class function
+--
+-- @usage
+--awful.placement.center_horizontal(client.focus)
+
+--- Stretch a drawable in a specific direction.
+-- Valid args:
+--
+-- * **direction**: The stretch direction (*left*, *right*, *up*, *down*) or
+-- a table with multiple directions.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_awful_placement_stretch.svg)
+--
+-- @tparam[opt=client.focus] drawable d A drawable (like `client` or `wibox`)
+-- @tparam[opt={}] table args The arguments
+-- @treturn table The new geometry
+function placement.stretch(d, args)
+ args = add_context(args, "stretch")
+
+ d = d or capi.client.focus
+ if not d or not args.direction then return end
+
+ -- 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
+ args.direction = dir
+ placement_private.stretch(dir, args)
+ end
+ return
+ end
+
+ local sgeo = get_parent_geometry(d, args)
+ local dgeo = geometry_common(d, args)
+ local ngeo = geometry_common(d, args, nil, true)
+ local bw = d.border_width or 0
+
+ if args.direction == "left" then
+ ngeo.x = sgeo.x
+ ngeo.width = dgeo.width + (dgeo.x - ngeo.x)
+ elseif args.direction == "right" then
+ ngeo.width = sgeo.width - ngeo.x - 2*bw
+ elseif args.direction == "up" then
+ ngeo.y = sgeo.y
+ ngeo.height = dgeo.height + (dgeo.y - ngeo.y)
+ elseif args.direction == "down" then
+ ngeo.height = sgeo.height - dgeo.y - 2*bw
+ else
+ assert(false)
+ end
+
+ -- Avoid negative sizes if args.parent isn't compatible
+ ngeo.width = math.max(args.minimim_width or 1, ngeo.width )
+ ngeo.height = math.max(args.minimim_height or 1, ngeo.height)
+
+ geometry_common(d, args, ngeo)
+
+ attach(d, placement["stretch_"..args.direction], args)
+
+ return fix_new_geometry(ngeo, args, true)
+end
+
+-- Add the alias functions
+for _,v in ipairs {"left", "right", "up", "down"} do
+ placement["stretch_"..v] = function(d, args)
+ args = add_context(args, "stretch_"..v)
+ args.direction = v
+ return placement_private.stretch(d, args)
+ end
+end
+
+---
+-- Stretch the drawable to the left of the parent area.
+--
+--![Usage example](../images/AUTOGEN_awful_placement_stretch_left.svg)
+-- @tparam drawable d A drawable (like `client` or `wibox`)
+-- @tparam[opt={}] table args Other arguments
+-- @treturn table The new geometry
+-- @name stretch_left
+-- @class function
+--
+-- @usage
+--placement.stretch_left(client.focus)
+
+---
+-- Stretch the drawable to the right of the parent area.
+--
+--![Usage example](../images/AUTOGEN_awful_placement_stretch_right.svg)
+-- @tparam drawable d A drawable (like `client` or `wibox`)
+-- @tparam[opt={}] table args Other arguments
+-- @treturn table The new geometry
+-- @name stretch_right
+-- @class function
+--
+-- @usage
+--placement.stretch_right(client.focus)
+
+---
+-- Stretch the drawable to the top of the parent area.
+--
+--![Usage example](../images/AUTOGEN_awful_placement_stretch_up.svg)
+-- @tparam drawable d A drawable (like `client` or `wibox`)
+-- @tparam[opt={}] table args Other arguments
+-- @treturn table The new geometry
+-- @name stretch_up
+-- @class function
+--
+-- @usage
+--placement.stretch_up(client.focus)
+
+---
+-- Stretch the drawable to the bottom of the parent area.
+--
+--![Usage example](../images/AUTOGEN_awful_placement_stretch_down.svg)
+-- @tparam drawable d A drawable (like `client` or `wibox`)
+-- @tparam[opt={}] table args Other arguments
+-- @treturn table The new geometry
+-- @name stretch_down
+-- @class function
+--
+-- @usage
+--placement.stretch_down(client.focus)
+
+--- Maximize a drawable horizontally, vertically or both.
+-- Valid args:
+--
+-- * *axis*:The axis (vertical or horizontal). If none is
+-- specified, then the drawable will be maximized on both axis.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_awful_placement_maximize.svg)
+--
+-- @tparam[opt=client.focus] drawable d A drawable (like `client` or `wibox`)
+-- @tparam[opt={}] table args The arguments
+-- @treturn table The new geometry
+function placement.maximize(d, args)
+ args = add_context(args, "maximize")
+ d = d or capi.client.focus
+
+ if not d then return end
+
+ local sgeo = get_parent_geometry(d, args)
+ local ngeo = geometry_common(d, args, nil, true)
+ local bw = d.border_width or 0
+
+ if (not args.axis) or args.axis :match "vertical" then
+ 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
+ ngeo.width = sgeo.width - 2*bw
+ end
+
+ geometry_common(d, args, ngeo)
+
+ attach(d, placement.maximize, args)
+
+ return fix_new_geometry(ngeo, args, true)
+end
+
+-- Add the alias functions
+for _, v in ipairs {"vertically", "horizontally"} do
+ placement["maximize_"..v] = function(d2, args)
+ args = add_context(args, "maximize_"..v)
+ args.axis = v
+ return placement_private.maximize(d2, args)
+ end
+end
+
+---
+-- Vetically maximize the drawable in the parent area.
+--
+--![Usage example](../images/AUTOGEN_awful_placement_maximize_vertically.svg)
+-- @tparam drawable d A drawable (like `client` or `wibox`)
+-- @tparam[opt={}] table args Other arguments')
+-- @name maximize_vertically
+-- @class function
+--
+-- @usage
+--placement.maximize_vertically(c)
+
+---
+-- Horizontally maximize the drawable in the parent area.
+--
+--![Usage example](../images/AUTOGEN_awful_placement_maximize_horizontally.svg)
+-- @tparam drawable d A drawable (like `client` or `wibox`)
+-- @tparam[opt={}] table args Other arguments')
+-- @name maximize_horizontally
+-- @class function
+--
+-- @usage
+--placement.maximize_horizontally(c)
+
+--- Scale the drawable by either a relative or absolute percent.
+--
+-- Valid args:
+--
+-- **to_percent** : A number between 0 and 1. It represent a percent related to
+-- the parent geometry.
+-- **by_percent** : A number between 0 and 1. It represent a percent related to
+-- the current size.
+-- **direction**: Nothing or "left", "right", "up", "down".
+--
+-- @tparam[opt=client.focus] drawable d A drawable (like `client` or `wibox`)
+-- @tparam[opt={}] table args The arguments
+-- @treturn table The new geometry
+function placement.scale(d, args)
+ args = add_context(args, "scale_to_percent")
+ d = d or capi.client.focus
+
+ local to_percent = args.to_percent
+ local by_percent = args.by_percent
+
+ local percent = to_percent or by_percent
+
+ local direction = args.direction
+
+ local sgeo = get_parent_geometry(d, args)
+ local ngeo = geometry_common(d, args, nil)
+
+ local old_area = {width = ngeo.width, height = ngeo.height}
+
+ if (not direction) or direction == "left" or direction == "right" then
+ ngeo.width = (to_percent and sgeo or ngeo).width*percent
+
+ if direction == "left" then
+ ngeo.x = ngeo.x - (ngeo.width - old_area.width)
+ end
+ end
+
+ if (not direction) or direction == "up" or direction == "down" then
+ ngeo.height = (to_percent and sgeo or ngeo).height*percent
+
+ if direction == "up" then
+ ngeo.y = ngeo.y - (ngeo.height - old_area.height)
+ end
+ end
+
+ local bw = d.border_width or 0
+ ngeo.width = ngeo.width - 2*bw
+ ngeo.height = ngeo.height - 2*bw
+
+ geometry_common(d, args, ngeo)
+
+ attach(d, placement.maximize, args)
+
+ return fix_new_geometry(ngeo, args, true)
+end
+
+--- Move a drawable to a relative position next to another one.
+--
+-- The `args.preferred_positions` look like this:
+--
+-- {"top", "right", "left", "bottom"}
+--
+-- In that case, if there is room on the top of the geomtry, then it will have
+-- priority, followed by all the others, in order.
+--
+-- @tparam drawable d A wibox or client
+-- @tparam table args
+-- @tparam string args.mode The mode
+-- @tparam string args.preferred_positions The preferred positions (in order)
+-- @tparam string args.geometry A geometry inside the other drawable
+-- @treturn table The new geometry
+-- @treturn string The choosen position
+-- @treturn string The choosen direction
+function placement.next_to(d, args)
+ args = add_context(args, "next_to")
+ d = d or capi.client.focus
+
+ local preferred_positions = {}
+
+ if #(args.preferred_positions or {}) then
+ for k, v in ipairs(args.preferred_positions) do
+ preferred_positions[v] = k
+ end
+ end
+
+ local dgeo = geometry_common(d, args)
+ local pref_idx, pref_name = 99, nil
+ local mode,wgeo = args.mode
+
+ if args.geometry then
+ mode = "geometry"
+ wgeo = args.geometry
+ else
+ local pos = capi.mouse.current_widget_geometry
+
+ if pos then
+ wgeo, mode = pos, "cursor"
+ elseif capi.mouse.current_client then
+ wgeo, mode = capi.mouse.current_client:geometry(), "cursor"
+ end
+ end
+
+ if not wgeo then return end
+
+ -- See get_relative_regions comments
+ local is_absolute = wgeo.ontop ~= nil
+
+ local regions = get_relative_regions(wgeo, mode, is_absolute)
+
+ -- Check each possible slot around the drawable (8 total), see what fits
+ -- and order them by preferred_positions
+ local does_fit = {}
+ for k,v in pairs(regions) do
+ local geo, dir = outer_positions[k.."1"](v, dgeo.width, dgeo.height)
+ geo.width, geo.height = dgeo.width, dgeo.height
+ local fit = fit_in_bounding(v.screen, geo, args)
+
+ -- Try the other compatible geometry
+ if not fit then
+ geo, dir = outer_positions[k.."2"](v, dgeo.width, dgeo.height)
+ geo.width, geo.height = dgeo.width, dgeo.height
+ fit = fit_in_bounding(v.screen, geo, args)
+ end
+
+ does_fit[k] = fit and {geo, dir} or nil
+
+ if fit and preferred_positions[k] and preferred_positions[k] < pref_idx then
+ pref_idx = preferred_positions[k]
+ pref_name = k
+ end
+
+ -- No need to continue
+ if fit and preferred_positions[k] == 1 then break end
+ end
+
+ local pos_name = pref_name or next(does_fit)
+ local ngeo, dir = unpack(does_fit[pos_name] or {}) --FIXME why does this happen
+
+ geometry_common(d, args, ngeo)
+
+ attach(d, placement.next_to, args)
+
+ return fix_new_geometry(ngeo, args, true), pos_name, dir
+end
+
+--- 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.border_width = memento.border_width
+
+ d:geometry(memento)
+ return true
+end
+
+return placement
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/prompt.lua b/lib/awful/prompt.lua
new file mode 100644
index 0000000..9a86475
--- /dev/null
+++ b/lib/awful/prompt.lua
@@ -0,0 +1,777 @@
+---------------------------------------------------------------------------
+--- Prompt module for awful.
+--
+-- By default, `rc.lua` will create one `awful.widget.prompt` per screen called
+-- `mypromptbox`. It is used for both the command execution (`mod4+r`) and
+-- Lua prompt (`mod4+x`). It can be re-used for random inputs using:
+--
+-- -- Create a shortcut function
+-- local function echo_test()
+-- awful.prompt.run {
+-- prompt = "Echo: ",
+-- textbox = mouse.screen.mypromptbox.widget,
+-- exe_callback = function(input)
+-- if not input or #input == 0 then return end
+-- naughty.notify{ text = "The input was: "..input }
+-- end
+-- }
+-- end
+--
+-- -- Then **IN THE globalkeys TABLE** add a new shortcut
+-- awful.key({ modkey }, "e", echo_test,
+-- {description = "Echo a string", group = "custom"}),
+--
+-- Note that this assumes an `rc.lua` file based on the default one. The way
+-- to access the screen prompt may vary.
+--
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2008 Julien Danjou
+-- @module awful.prompt
+---------------------------------------------------------------------------
+
+-- Grab environment we need
+local assert = assert
+local io = io
+local table = table
+local math = math
+local ipairs = ipairs
+local pcall = pcall
+local capi =
+{
+ selection = selection
+}
+local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
+local keygrabber = require("awful.keygrabber")
+local util = require("awful.util")
+local beautiful = require("beautiful")
+local akey = require("awful.key")
+
+local prompt = {}
+
+--- Private data
+local data = {}
+data.history = {}
+
+local search_term = nil
+local function itera (inc,a, i)
+ i = i + inc
+ local v = a[i]
+ if v then return i,v end
+end
+
+--- Load history file in history table
+-- @param id The data.history identifier which is the path to the filename.
+-- @param[opt] max The maximum number of entries in file.
+local function history_check_load(id, max)
+ if id and id ~= "" and not data.history[id] then
+ data.history[id] = { max = 50, table = {} }
+
+ if max then
+ data.history[id].max = max
+ end
+
+ local f = io.open(id, "r")
+ if not f then return end
+
+ -- Read history file
+ for line in f:lines() do
+ if util.table.hasitem(data.history[id].table, line) == nil then
+ table.insert(data.history[id].table, line)
+ if #data.history[id].table >= data.history[id].max then
+ break
+ end
+ end
+ end
+ f:close()
+ end
+end
+
+local function is_word_char(c)
+ if string.find("[{[(,.:;_-+=@/ ]", c) then
+ return false
+ else
+ return true
+ end
+end
+
+local function cword_start(s, pos)
+ local i = pos
+ if i > 1 then
+ i = i - 1
+ end
+ while i >= 1 and not is_word_char(s:sub(i, i)) do
+ i = i - 1
+ end
+ while i >= 1 and is_word_char(s:sub(i, i)) do
+ i = i - 1
+ end
+ if i <= #s then
+ i = i + 1
+ end
+ return i
+end
+
+local function cword_end(s, pos)
+ local i = pos
+ while i <= #s and not is_word_char(s:sub(i, i)) do
+ i = i + 1
+ end
+ while i <= #s and is_word_char(s:sub(i, i)) do
+ i = i + 1
+ end
+ return i
+end
+
+--- Save history table in history file
+-- @param id The data.history identifier
+local function history_save(id)
+ if data.history[id] then
+ local f = io.open(id, "w")
+ if not f then
+ local i = 0
+ for d in id:gmatch(".-/") do
+ i = i + #d
+ end
+ util.mkdir(id:sub(1, i - 1))
+ f = assert(io.open(id, "w"))
+ end
+ for i = 1, math.min(#data.history[id].table, data.history[id].max) do
+ f:write(data.history[id].table[i] .. "\n")
+ end
+ f:close()
+ end
+end
+
+--- Return the number of items in history table regarding the id
+-- @param id The data.history identifier
+-- @return the number of items in history table, -1 if history is disabled
+local function history_items(id)
+ if data.history[id] then
+ return #data.history[id].table
+ else
+ return -1
+ end
+end
+
+--- Add an entry to the history file
+-- @param id The data.history identifier
+-- @param command The command to add
+local function history_add(id, command)
+ if data.history[id] and command ~= "" then
+ local index = util.table.hasitem(data.history[id].table, command)
+ if index == nil then
+ table.insert(data.history[id].table, command)
+
+ -- Do not exceed our max_cmd
+ if #data.history[id].table > data.history[id].max then
+ table.remove(data.history[id].table, 1)
+ end
+
+ history_save(id)
+ else
+ -- Bump this command to the end of history
+ table.remove(data.history[id].table, index)
+ table.insert(data.history[id].table, command)
+ history_save(id)
+ end
+ end
+end
+
+
+--- Draw the prompt text with a cursor.
+-- @tparam table args The table of arguments.
+-- @field text The text.
+-- @field font The font.
+-- @field prompt The text prefix.
+-- @field text_color The text color.
+-- @field cursor_color The cursor color.
+-- @field cursor_pos The cursor position.
+-- @field cursor_ul The cursor underline style.
+-- @field selectall If true cursor is rendered on the entire text.
+local function prompt_text_with_cursor(args)
+ local char, spacer, text_start, text_end, ret
+ local text = args.text or ""
+ local _prompt = args.prompt or ""
+ local underline = args.cursor_ul or "none"
+
+ if args.selectall then
+ if #text == 0 then char = " " else char = util.escape(text) end
+ spacer = " "
+ text_start = ""
+ text_end = ""
+ elseif #text < args.cursor_pos then
+ char = " "
+ spacer = ""
+ text_start = util.escape(text)
+ text_end = ""
+ else
+ char = util.escape(text:sub(args.cursor_pos, args.cursor_pos))
+ spacer = " "
+ text_start = util.escape(text:sub(1, args.cursor_pos - 1))
+ text_end = util.escape(text:sub(args.cursor_pos + 1))
+ end
+
+ local cursor_color = util.ensure_pango_color(args.cursor_color)
+ local text_color = util.ensure_pango_color(args.text_color)
+
+ ret = _prompt .. text_start .. "<span background=\"" .. cursor_color ..
+ "\" foreground=\"" .. text_color .. "\" underline=\"" .. underline ..
+ "\">" .. char .. "</span>" .. text_end .. spacer
+ return ret
+end
+
+--- Run a prompt in a box.
+--
+-- The following readline keyboard shortcuts are implemented as expected:
+-- <kbd>CTRL+A</kbd>, <kbd>CTRL+B</kbd>, <kbd>CTRL+C</kbd>, <kbd>CTRL+D</kbd>,
+-- <kbd>CTRL+E</kbd>, <kbd>CTRL+J</kbd>, <kbd>CTRL+M</kbd>, <kbd>CTRL+F</kbd>,
+-- <kbd>CTRL+H</kbd>, <kbd>CTRL+K</kbd>, <kbd>CTRL+U</kbd>, <kbd>CTRL+W</kbd>,
+-- <kbd>CTRL+BACKSPACE</kbd>, <kbd>SHIFT+INSERT</kbd>, <kbd>HOME</kbd>,
+-- <kbd>END</kbd> and arrow keys.
+--
+-- The following shortcuts implement additional history manipulation commands
+-- where the search term is defined as the substring of the command from first
+-- character to cursor position.
+--
+-- * <kbd>CTRL+R</kbd>: reverse history search, matches any history entry
+-- containing search term.
+-- * <kbd>CTRL+S</kbd>: forward history search, matches any history entry
+-- containing search term.
+-- * <kbd>CTRL+UP</kbd>: ZSH up line or search, matches any history entry
+-- starting with search term.
+-- * <kbd>CTRL+DOWN</kbd>: ZSH down line or search, matches any history
+-- entry starting with search term.
+-- * <kbd>CTRL+DELETE</kbd>: delete the currently visible history entry from
+-- history file. This does not delete new commands or history entries under
+-- user editing.
+--
+-- @tparam[opt={}] table args A table with optional arguments
+-- @tparam[opt] gears.color args.fg_cursor
+-- @tparam[opt] gears.color args.bg_cursor
+-- @tparam[opt] gears.color args.ul_cursor
+-- @tparam[opt] widget args.prompt
+-- @tparam[opt] string args.text
+-- @tparam[opt] boolean args.selectall
+-- @tparam[opt] string args.font
+-- @tparam[opt] boolean args.autoexec
+-- @tparam widget args.textbox The textbox to use for the prompt.
+-- @tparam function args.exe_callback The callback function to call with command as argument
+-- when finished.
+-- @tparam function args.completion_callback The callback function to call to get completion.
+-- @tparam[opt] string args.history_path File path where the history should be
+-- saved, set nil to disable history
+-- @tparam[opt] function args.history_max Set the maximum entries in history
+-- file, 50 by default
+-- @tparam[opt] function args.done_callback The callback function to always call
+-- without arguments, regardless of whether the prompt was cancelled.
+-- @tparam[opt] function args.changed_callback The callback function to call
+-- with command as argument when a command was changed.
+-- @tparam[opt] function args.keypressed_callback The callback function to call
+-- with mod table, key and command as arguments when a key was pressed.
+-- @tparam[opt] function args.keyreleased_callback The callback function to call
+-- with mod table, key and command as arguments when a key was pressed.
+-- @tparam[opt] table args.hooks The "hooks" argument uses a syntax similar to
+-- `awful.key`. It will call a function for the matching modifiers + key.
+-- It receives the command (widget text/input) as an argument.
+-- If the callback returns a command, this will be passed to the
+-- `exe_callback`, otherwise nothing gets executed by default, and the hook
+-- needs to handle it.
+-- hooks = {
+-- -- Apply startup notification properties with Shift-Return.
+-- {{"Shift" }, "Return", function(command)
+-- awful.screen.focused().mypromptbox:spawn_and_handle_error(
+-- command, {floating=true})
+-- end},
+-- -- Override default behavior of "Return": launch commands prefixed
+-- -- with ":" in a terminal.
+-- {{}, "Return", function(command)
+-- if command:sub(1,1) == ":" then
+-- return terminal .. ' -e ' .. command:sub(2)
+-- end
+-- return command
+-- end}
+-- }
+-- @param textbox The textbox to use for the prompt. [**DEPRECATED**]
+-- @param exe_callback The callback function to call with command as argument
+-- when finished. [**DEPRECATED**]
+-- @param completion_callback The callback function to call to get completion.
+-- [**DEPRECATED**]
+-- @param[opt] history_path File path where the history should be
+-- saved, set nil to disable history [**DEPRECATED**]
+-- @param[opt] history_max Set the maximum entries in history
+-- file, 50 by default [**DEPRECATED**]
+-- @param[opt] done_callback The callback function to always call
+-- without arguments, regardless of whether the prompt was cancelled.
+-- [**DEPRECATED**]
+-- @param[opt] changed_callback The callback function to call
+-- with command as argument when a command was changed. [**DEPRECATED**]
+-- @param[opt] keypressed_callback The callback function to call
+-- with mod table, key and command as arguments when a key was pressed.
+-- [**DEPRECATED**]
+-- @see gears.color
+function prompt.run(args, textbox, exe_callback, completion_callback,
+ history_path, history_max, done_callback,
+ changed_callback, keypressed_callback)
+ local grabber
+ local theme = beautiful.get()
+ if not args then args = {} end
+ local command = args.text or ""
+ local command_before_comp
+ local cur_pos_before_comp
+ local prettyprompt = args.prompt or ""
+ local inv_col = args.fg_cursor or theme.fg_focus or "black"
+ local cur_col = args.bg_cursor or theme.bg_focus or "white"
+ local cur_ul = args.ul_cursor
+ local text = args.text or ""
+ local font = args.font or theme.font
+ local selectall = args.selectall
+ local hooks = {}
+
+ -- A function with 9 parameters deserve to die
+ if textbox then
+ util.deprecate("Use args.textbox instead of the textbox parameter")
+ end
+ if exe_callback then
+ util.deprecate(
+ "Use args.exe_callback instead of the exe_callback parameter"
+ )
+ end
+ if completion_callback then
+ util.deprecate(
+ "Use args.completion_callback instead of the completion_callback parameter"
+ )
+ end
+ if history_path then
+ util.deprecate(
+ "Use args.history_path instead of the history_path parameter"
+ )
+ end
+ if history_max then
+ util.deprecate(
+ "Use args.history_max instead of the history_max parameter"
+ )
+ end
+ if done_callback then
+ util.deprecate(
+ "Use args.done_callback instead of the done_callback parameter"
+ )
+ end
+ if changed_callback then
+ util.deprecate(
+ "Use args.changed_callback instead of the changed_callback parameter"
+ )
+ end
+ if keypressed_callback then
+ util.deprecate(
+ "Use args.keypressed_callback instead of the keypressed_callback parameter"
+ )
+ end
+
+ -- This function has already an absurd number of parameters, allow them
+ -- to be set using the args to avoid a "nil, nil, nil, nil, foo" scenario
+ keypressed_callback = keypressed_callback or args.keypressed_callback
+ changed_callback = changed_callback or args.changed_callback
+ done_callback = done_callback or args.done_callback
+ history_max = history_max or args.history_max
+ history_path = history_path or args.history_path
+ completion_callback = completion_callback or args.completion_callback
+ exe_callback = exe_callback or args.exe_callback
+ textbox = textbox or args.textbox
+
+ search_term=nil
+
+ history_check_load(history_path, history_max)
+ local history_index = history_items(history_path) + 1
+ -- The cursor position
+ local cur_pos = (selectall and 1) or text:wlen() + 1
+ -- The completion element to use on completion request.
+ local ncomp = 1
+ if not textbox then
+ return
+ end
+
+ -- Build the hook map
+ for _,v in ipairs(args.hooks or {}) do
+ if #v == 3 then
+ local _,key,callback = unpack(v)
+ if type(callback) == "function" then
+ hooks[key] = hooks[key] or {}
+ hooks[key][#hooks[key]+1] = v
+ else
+ assert("The hook's 3rd parameter has to be a function.")
+ end
+ else
+ assert("The hook has to have 3 parameters.")
+ end
+ end
+
+ textbox:set_font(font)
+ textbox:set_markup(prompt_text_with_cursor{
+ text = text, text_color = inv_col, cursor_color = cur_col,
+ cursor_pos = cur_pos, cursor_ul = cur_ul, selectall = selectall,
+ prompt = prettyprompt })
+
+ local function exec(cb, command_to_history)
+ textbox:set_markup("")
+ history_add(history_path, command_to_history)
+ keygrabber.stop(grabber)
+ if cb then cb(command) end
+ if done_callback then done_callback() end
+ end
+
+ -- Update textbox
+ local function update()
+ textbox:set_font(font)
+ textbox:set_markup(prompt_text_with_cursor{
+ text = command, text_color = inv_col, cursor_color = cur_col,
+ cursor_pos = cur_pos, cursor_ul = cur_ul, selectall = selectall,
+ prompt = prettyprompt })
+ end
+
+ grabber = keygrabber.run(
+ function (modifiers, key, event)
+ -- Convert index array to hash table
+ local mod = {}
+ for _, v in ipairs(modifiers) do mod[v] = true end
+
+ if event ~= "press" then
+ if args.keyreleased_callback then
+ args.keyreleased_callback(mod, key, command)
+ end
+
+ return
+ end
+
+ -- Call the user specified callback. If it returns true as
+ -- the first result then return from the function. Treat the
+ -- second and third results as a new command and new prompt
+ -- to be set (if provided)
+ if keypressed_callback then
+ local user_catched, new_command, new_prompt =
+ keypressed_callback(mod, key, command)
+ if new_command or new_prompt then
+ if new_command then
+ command = new_command
+ end
+ if new_prompt then
+ prettyprompt = new_prompt
+ end
+ update()
+ end
+ if user_catched then
+ if changed_callback then
+ changed_callback(command)
+ end
+ return
+ end
+ end
+
+ local filtered_modifiers = {}
+
+ -- User defined cases
+ if hooks[key] then
+ -- Remove caps and num lock
+ for _, m in ipairs(modifiers) do
+ if not util.table.hasitem(akey.ignore_modifiers, m) then
+ table.insert(filtered_modifiers, m)
+ end
+ end
+
+ for _,v in ipairs(hooks[key]) do
+ if #filtered_modifiers == #v[1] then
+ local match = true
+ for _,v2 in ipairs(v[1]) do
+ match = match and mod[v2]
+ end
+ if match then
+ local cb
+ local ret = v[3](command)
+ local original_command = command
+ if ret then
+ command = ret
+ cb = exe_callback
+ else
+ -- No callback.
+ cb = function() end
+ end
+ exec(cb, original_command)
+ return
+ end
+ end
+ end
+ end
+
+ -- Get out cases
+ if (mod.Control and (key == "c" or key == "g"))
+ or (not mod.Control and key == "Escape") then
+ keygrabber.stop(grabber)
+ textbox:set_markup("")
+ history_save(history_path)
+ if done_callback then done_callback() end
+ return false
+ elseif (mod.Control and (key == "j" or key == "m"))
+ or (not mod.Control and key == "Return")
+ or (not mod.Control and key == "KP_Enter") then
+ exec(exe_callback, command)
+ -- We already unregistered ourselves so we don't want to return
+ -- true, otherwise we may unregister someone else.
+ return
+ end
+
+ -- Control cases
+ if mod.Control then
+ selectall = nil
+ if key == "a" then
+ cur_pos = 1
+ elseif key == "b" then
+ if cur_pos > 1 then
+ cur_pos = cur_pos - 1
+ end
+ elseif key == "d" then
+ if cur_pos <= #command then
+ command = command:sub(1, cur_pos - 1) .. command:sub(cur_pos + 1)
+ end
+ elseif key == "p" then
+ if history_index > 1 then
+ history_index = history_index - 1
+
+ command = data.history[history_path].table[history_index]
+ cur_pos = #command + 2
+ end
+ elseif key == "n" then
+ if history_index < history_items(history_path) then
+ history_index = history_index + 1
+
+ command = data.history[history_path].table[history_index]
+ cur_pos = #command + 2
+ elseif history_index == history_items(history_path) then
+ history_index = history_index + 1
+
+ command = ""
+ cur_pos = 1
+ end
+ elseif key == "e" then
+ cur_pos = #command + 1
+ elseif key == "r" then
+ search_term = search_term or command:sub(1, cur_pos - 1)
+ for i,v in (function(a,i) return itera(-1,a,i) end), data.history[history_path].table, history_index do
+ if v:find(search_term,1,true) ~= nil then
+ command=v
+ history_index=i
+ cur_pos=#command+1
+ break
+ end
+ end
+ elseif key == "s" then
+ search_term = search_term or command:sub(1, cur_pos - 1)
+ for i,v in (function(a,i) return itera(1,a,i) end), data.history[history_path].table, history_index do
+ if v:find(search_term,1,true) ~= nil then
+ command=v
+ history_index=i
+ cur_pos=#command+1
+ break
+ end
+ end
+ elseif key == "f" then
+ if cur_pos <= #command then
+ cur_pos = cur_pos + 1
+ end
+ elseif key == "h" then
+ if cur_pos > 1 then
+ command = command:sub(1, cur_pos - 2) .. command:sub(cur_pos)
+ cur_pos = cur_pos - 1
+ end
+ elseif key == "k" then
+ command = command:sub(1, cur_pos - 1)
+ elseif key == "u" then
+ command = command:sub(cur_pos, #command)
+ cur_pos = 1
+ elseif key == "Up" then
+ search_term = command:sub(1, cur_pos - 1) or ""
+ for i,v in (function(a,i) return itera(-1,a,i) end), data.history[history_path].table, history_index do
+ if v:find(search_term,1,true) == 1 then
+ command=v
+ history_index=i
+ break
+ end
+ end
+ elseif key == "Down" then
+ search_term = command:sub(1, cur_pos - 1) or ""
+ for i,v in (function(a,i) return itera(1,a,i) end), data.history[history_path].table, history_index do
+ if v:find(search_term,1,true) == 1 then
+ command=v
+ history_index=i
+ break
+ end
+ end
+ elseif key == "w" or key == "BackSpace" then
+ local wstart = 1
+ local wend = 1
+ local cword_start_pos = 1
+ local cword_end_pos = 1
+ while wend < cur_pos do
+ wend = command:find("[{[(,.:;_-+=@/ ]", wstart)
+ if not wend then wend = #command + 1 end
+ if cur_pos >= wstart and cur_pos <= wend + 1 then
+ cword_start_pos = wstart
+ cword_end_pos = cur_pos - 1
+ break
+ end
+ wstart = wend + 1
+ end
+ command = command:sub(1, cword_start_pos - 1) .. command:sub(cword_end_pos + 1)
+ cur_pos = cword_start_pos
+ elseif key == "Delete" then
+ -- delete from history only if:
+ -- we are not dealing with a new command
+ -- the user has not edited an existing entry
+ if command == data.history[history_path].table[history_index] then
+ table.remove(data.history[history_path].table, history_index)
+ if history_index <= history_items(history_path) then
+ command = data.history[history_path].table[history_index]
+ cur_pos = #command + 2
+ elseif history_index > 1 then
+ history_index = history_index - 1
+
+ command = data.history[history_path].table[history_index]
+ cur_pos = #command + 2
+ else
+ command = ""
+ cur_pos = 1
+ end
+ end
+ end
+ elseif mod.Mod1 or mod.Mod3 then
+ if key == "b" then
+ cur_pos = cword_start(command, cur_pos)
+ elseif key == "f" then
+ cur_pos = cword_end(command, cur_pos)
+ elseif key == "d" then
+ command = command:sub(1, cur_pos - 1) .. command:sub(cword_end(command, cur_pos))
+ elseif key == "BackSpace" then
+ local wstart = cword_start(command, cur_pos)
+ command = command:sub(1, wstart - 1) .. command:sub(cur_pos)
+ cur_pos = wstart
+ end
+ else
+ if completion_callback then
+ if key == "Tab" or key == "ISO_Left_Tab" then
+ if key == "ISO_Left_Tab" then
+ if ncomp == 1 then return end
+ if ncomp == 2 then
+ command = command_before_comp
+ textbox:set_font(font)
+ textbox:set_markup(prompt_text_with_cursor{
+ text = command_before_comp, text_color = inv_col, cursor_color = cur_col,
+ cursor_pos = cur_pos, cursor_ul = cur_ul, selectall = selectall,
+ prompt = prettyprompt })
+ return
+ end
+
+ ncomp = ncomp - 2
+ elseif ncomp == 1 then
+ command_before_comp = command
+ cur_pos_before_comp = cur_pos
+ end
+ local matches
+ command, cur_pos, matches = completion_callback(command_before_comp, cur_pos_before_comp, ncomp)
+ ncomp = ncomp + 1
+ key = ""
+ -- execute if only one match found and autoexec flag set
+ if matches and #matches == 1 and args.autoexec then
+ exec(exe_callback)
+ return
+ end
+ else
+ ncomp = 1
+ end
+ end
+
+ -- Typin cases
+ if mod.Shift and key == "Insert" then
+ local selection = capi.selection()
+ if selection then
+ -- Remove \n
+ local n = selection:find("\n")
+ if n then
+ selection = selection:sub(1, n - 1)
+ end
+ command = command:sub(1, cur_pos - 1) .. selection .. command:sub(cur_pos)
+ cur_pos = cur_pos + #selection
+ end
+ elseif key == "Home" then
+ cur_pos = 1
+ elseif key == "End" then
+ cur_pos = #command + 1
+ elseif key == "BackSpace" then
+ if cur_pos > 1 then
+ command = command:sub(1, cur_pos - 2) .. command:sub(cur_pos)
+ cur_pos = cur_pos - 1
+ end
+ elseif key == "Delete" then
+ command = command:sub(1, cur_pos - 1) .. command:sub(cur_pos + 1)
+ elseif key == "Left" then
+ cur_pos = cur_pos - 1
+ elseif key == "Right" then
+ cur_pos = cur_pos + 1
+ elseif key == "Up" then
+ if history_index > 1 then
+ history_index = history_index - 1
+
+ command = data.history[history_path].table[history_index]
+ cur_pos = #command + 2
+ end
+ elseif key == "Down" then
+ if history_index < history_items(history_path) then
+ history_index = history_index + 1
+
+ command = data.history[history_path].table[history_index]
+ cur_pos = #command + 2
+ elseif history_index == history_items(history_path) then
+ history_index = history_index + 1
+
+ command = ""
+ cur_pos = 1
+ end
+ else
+ -- wlen() is UTF-8 aware but #key is not,
+ -- so check that we have one UTF-8 char but advance the cursor of # position
+ if key:wlen() == 1 then
+ if selectall then command = "" end
+ command = command:sub(1, cur_pos - 1) .. key .. command:sub(cur_pos)
+ cur_pos = cur_pos + #key
+ end
+ end
+ if cur_pos < 1 then
+ cur_pos = 1
+ elseif cur_pos > #command + 1 then
+ cur_pos = #command + 1
+ end
+ selectall = nil
+ end
+
+ local success = pcall(update)
+ while not success do
+ -- TODO UGLY HACK TODO
+ -- Setting the text failed. Most likely reason is that the user
+ -- entered a multibyte character and pressed backspace which only
+ -- removed the last byte. Let's remove another byte.
+ if cur_pos <= 1 then
+ -- No text left?!
+ break
+ end
+
+ command = command:sub(1, cur_pos - 2) .. command:sub(cur_pos)
+ cur_pos = cur_pos - 1
+ success = pcall(update)
+ end
+
+ if changed_callback then
+ changed_callback(command)
+ end
+ end)
+end
+
+return prompt
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/remote.lua b/lib/awful/remote.lua
new file mode 100644
index 0000000..7967f2f
--- /dev/null
+++ b/lib/awful/remote.lua
@@ -0,0 +1,47 @@
+---------------------------------------------------------------------------
+--- Remote control module allowing usage of awesome-client.
+--
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2009 Julien Danjou
+-- @module awful.remote
+---------------------------------------------------------------------------
+
+-- Grab environment we need
+require("awful.dbus")
+local load = loadstring or load -- luacheck: globals loadstring (compatibility with Lua 5.1)
+local tostring = tostring
+local ipairs = ipairs
+local table = table
+local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
+local dbus = dbus
+local type = type
+
+if dbus then
+ dbus.connect_signal("org.awesomewm.awful.Remote", function(data, code)
+ if data.member == "Eval" then
+ local f, e = load(code)
+ if f then
+ local results = { f() }
+ local retvals = {}
+ for _, v in ipairs(results) do
+ local t = type(v)
+ if t == "boolean" then
+ table.insert(retvals, "b")
+ table.insert(retvals, v)
+ elseif t == "number" then
+ table.insert(retvals, "d")
+ table.insert(retvals, v)
+ else
+ table.insert(retvals, "s")
+ table.insert(retvals, tostring(v))
+ end
+ end
+ return unpack(retvals)
+ elseif e then
+ return "s", e
+ end
+ end
+ end)
+end
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/rules.lua b/lib/awful/rules.lua
new file mode 100644
index 0000000..dc44e00
--- /dev/null
+++ b/lib/awful/rules.lua
@@ -0,0 +1,545 @@
+---------------------------------------------------------------------------
+--- Apply rules to clients at startup.
+--
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2009 Julien Danjou
+-- @module awful.rules
+---------------------------------------------------------------------------
+
+-- Grab environment we need
+local client = client
+local awesome = awesome
+local screen = screen
+local table = table
+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 protected_call = require("gears.protected_call")
+
+local rules = {}
+
+--[[--
+This is the global rules table.
+
+You should fill this table with your rule and properties to apply.
+For example, if you want to set xterm maximized at startup, you can add:
+
+ { rule = { class = "xterm" },
+ properties = { maximized_vertical = true, maximized_horizontal = true } }
+
+If you want to set mplayer floating at startup, you can add:
+
+ { rule = { name = "MPlayer" },
+ properties = { floating = true } }
+
+If you want to put Firefox on a specific tag at startup, you can add:
+
+ { rule = { instance = "firefox" },
+ properties = { tag = mytagobject } }
+
+Alternatively, you can specify the tag by name:
+
+ { rule = { instance = "firefox" },
+ properties = { tag = "3" } }
+
+If you want to put Thunderbird on a specific screen at startup, use:
+
+ { rule = { instance = "Thunderbird" },
+ properties = { screen = 1 } }
+
+Assuming that your X11 server supports the RandR extension, you can also specify
+the screen by name:
+
+ { rule = { instance = "Thunderbird" },
+ properties = { screen = "VGA1" } }
+
+If you want to put Emacs on a specific tag at startup, and immediately switch
+to that tag you can add:
+
+ { rule = { class = "Emacs" },
+ properties = { tag = mytagobject, switchtotag = true } }
+
+If you want to apply a custom callback to execute when a rule matched,
+for example to pause playing music from mpd when you start dosbox, you
+can add:
+
+ { rule = { class = "dosbox" },
+ callback = function(c)
+ awful.spawn('mpc pause')
+ end }
+
+Note that all "rule" entries need to match. If any of the entry does not
+match, the rule won't be applied.
+
+If a client matches multiple rules, they are applied in the order they are
+put in this global rules table. If the value of a rule is a string, then the
+match function is used to determine if the client matches the rule.
+
+If the value of a property is a function, that function gets called and
+function's return value is used for the property.
+
+To match multiple clients to a rule one need to use slightly different
+syntax:
+
+ { rule_any = { class = { "MPlayer", "Nitrogen" }, instance = { "xterm" } },
+ properties = { floating = true } }
+
+To match multiple clients with an exception one can couple `rules.except` or
+`rules.except_any` with the rules:
+
+ { rule = { class = "Firefox" },
+ except = { instance = "Navigator" },
+ properties = {floating = true},
+ },
+
+ { rule_any = { class = { "Pidgin", "Xchat" } },
+ except_any = { role = { "conversation" } },
+ properties = { tag = "1" }
+ }
+
+ { rule = {},
+ except_any = { class = { "Firefox", "Vim" } },
+ properties = { floating = true }
+ }
+]]--
+rules.rules = {}
+
+--- Check if a client matches a rule.
+-- @client c The client.
+-- @tab rule The rule to check.
+-- @treturn bool True if it matches, false otherwise.
+function rules.match(c, rule)
+ if not rule then return false end
+ for field, value in pairs(rule) do
+ if c[field] then
+ if type(c[field]) == "string" then
+ if not c[field]:match(value) and c[field] ~= value then
+ return false
+ end
+ elseif c[field] ~= value then
+ return false
+ end
+ else
+ return false
+ end
+ end
+ return true
+end
+
+--- Check if a client matches any part of a rule.
+-- @client c The client.
+-- @tab rule The rule to check.
+-- @treturn bool True if at least one rule is matched, false otherwise.
+function rules.match_any(c, rule)
+ if not rule then return false end
+ for field, values in pairs(rule) do
+ if c[field] then
+ for _, value in ipairs(values) do
+ if c[field] == value then
+ return true
+ elseif type(c[field]) == "string" and c[field]:match(value) then
+ return true
+ end
+ end
+ end
+ end
+ return false
+end
+
+--- Does a given rule entry match a client?
+-- @client c The client.
+-- @tab entry Rule entry (with keys `rule`, `rule_any`, `except` and/or
+-- `except_any`).
+-- @treturn bool
+function rules.matches(c, entry)
+ return (rules.match(c, entry.rule) or rules.match_any(c, entry.rule_any)) and
+ (not rules.match(c, entry.except) and not rules.match_any(c, entry.except_any))
+end
+
+--- Get list of matching rules for a client.
+-- @client c The client.
+-- @tab _rules The rules to check. List with "rule", "rule_any", "except" and
+-- "except_any" keys.
+-- @treturn table The list of matched rules.
+function rules.matching_rules(c, _rules)
+ local result = {}
+ for _, entry in ipairs(_rules) do
+ if (rules.matches(c, entry)) then
+ table.insert(result, entry)
+ end
+ end
+ return result
+end
+
+--- Check if a client matches a given set of rules.
+-- @client c The client.
+-- @tab _rules The rules to check. List of tables with `rule`, `rule_any`,
+-- `except` and `except_any` keys.
+-- @treturn bool True if at least one rule is matched, false otherwise.
+function rules.matches_list(c, _rules)
+ for _, entry in ipairs(_rules) do
+ if (rules.matches(c, entry)) then
+ return true
+ end
+ end
+ return false
+end
+
+--- Apply awful.rules.rules to a client.
+-- @client c The client.
+function rules.apply(c)
+
+ local props = {}
+ local callbacks = {}
+
+ for _, entry in ipairs(rules.matching_rules(c, rules.rules)) do
+ if entry.properties then
+ for property, value in pairs(entry.properties) do
+ props[property] = value
+ end
+ end
+ if entry.callback then
+ table.insert(callbacks, entry.callback)
+ end
+ end
+
+ 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, props)
+-- -- do something
+-- end
+--
+-- By default, the table has the following functions:
+--
+-- * geometry
+-- * switchtotag
+--
+-- @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, props)
+-- -- do something
+-- end
+--
+-- By default, the table has the following functions:
+--
+-- * tag
+-- * new_tag
+--
+-- @tfield table awful.rules.high_priority_properties
+rules.high_priority_properties = {}
+
+--- Delayed properties.
+-- Properties applied after all other categories.
+-- @tfield table awful.rules.delayed_properties
+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,size_hints_honor=true
+}
+
+function rules.high_priority_properties.tag(c, value, props)
+ if value then
+ if type(value) == "string" then
+ value = atag.find_by_name(c.screen, value)
+ end
+
+ -- In case the tag has been forced to another screen, move the client
+ if c.screen ~= value.screen then
+ c.screen = value.screen
+ props.screen = value.screen -- In case another rule query it
+ 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
+
+--- Create a new tag based on a rule.
+-- @tparam client c The client
+-- @tparam boolean|function|string value The value.
+-- @tparam table props The properties.
+-- @treturn tag The new tag
+function rules.high_priority_properties.new_tag(c, value, props)
+ 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. Set the right screen, but
+ -- avoid editing the original table
+ local values = value.screen and value or util.table.clone(value)
+ values.screen = values.screen or c.screen
+
+ t = atag.add(value.name or c.class or "N/A", values)
+
+ -- In case the tag has been forced to another screen, move the client
+ c.screen = t.screen
+ props.screen = t.screen -- In case another rule query it
+ else
+ assert(false)
+ end
+
+ add_to_tag(c, t)
+
+ 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
+
+function rules.extra_properties.tags(c, value, props)
+ local current = c:tags()
+
+ local tags, s = {}, nil
+
+ for _, t in ipairs(value) do
+ if type(t) == "string" then
+ t = atag.find_by_name(c.screen, t)
+ end
+
+ if t and ((not s) or t.screen == s) then
+ table.insert(tags, t)
+ s = s or t.screen
+ end
+ end
+
+ if s and s ~= c.screen then
+ c.screen = s
+ props.screen = s -- In case another rule query it
+ end
+
+ if #current == 0 or (value[1] and value[1].screen ~= current[1].screen) then
+ c:tags(tags)
+ else
+ c:tags(util.table.merge(current, tags))
+ end
+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)
+ -- 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
+
+ -- 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
+
+ -- 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 ~= nil 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)]
+ 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
+ local value = props[prop]
+
+ if value ~= nil then
+ if type(value) == "function" then
+ value = value(c, props)
+ end
+
+ handler(c, value, props)
+ 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
+
+ -- 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)
+ 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
+
+ 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, props)
+ elseif type(c[property]) == "function" then
+ c[property](c, value)
+ else
+ c[property] = value
+ end
+ end
+ end
+
+ -- Apply all callbacks.
+ if callbacks then
+ for _, callback in pairs(callbacks) do
+ protected_call(callback, c)
+ end
+ end
+
+ -- Apply the delayed properties
+ for prop, handler in pairs(rules.delayed_properties) do
+ if not force_ignore[prop] then
+ local value = props[prop]
+
+ if value ~= nil then
+ if type(value) == "function" then
+ value = value(c, props)
+ end
+
+ handler(c, value, props)
+ end
+ 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})
+ end
+end
+
+function rules.completed_with_payload_callback(c, props, callbacks)
+ rules.execute(c, props, callbacks)
+end
+
+client.connect_signal("spawn::completed_with_payload", rules.completed_with_payload_callback)
+
+client.connect_signal("manage", rules.apply)
+
+return rules
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/screen.lua b/lib/awful/screen.lua
new file mode 100644
index 0000000..e36e622
--- /dev/null
+++ b/lib/awful/screen.lua
@@ -0,0 +1,477 @@
+---------------------------------------------------------------------------
+--- Screen module for awful
+--
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2008 Julien Danjou
+-- @module screen
+---------------------------------------------------------------------------
+
+-- Grab environment we need
+local capi =
+{
+ mouse = mouse,
+ screen = screen,
+ client = client,
+ awesome = awesome,
+}
+local util = require("awful.util")
+local object = require("gears.object")
+local grect = require("gears.geometry").rectangle
+
+local function get_screen(s)
+ return s and capi.screen[s]
+end
+
+-- we use require("awful.client") inside functions to prevent circular dependencies.
+local client
+
+local screen = {object={}}
+
+local data = {}
+data.padding = {}
+
+--- Take an input geometry and substract/add a delta.
+-- @tparam table geo A geometry (width, height, x, y) table.
+-- @tparam table delta A delta table (top, bottom, x, y).
+-- @treturn table A geometry (width, height, x, y) table.
+local function apply_geometry_ajustments(geo, delta)
+ return {
+ x = geo.x + (delta.left or 0),
+ y = geo.y + (delta.top or 0),
+ width = geo.width - (delta.left or 0) - (delta.right or 0),
+ height = geo.height - (delta.top or 0) - (delta.bottom or 0),
+ }
+end
+
+--- Get the square distance between a `screen` and a point.
+-- @deprecated awful.screen.getdistance_sq
+-- @param s Screen
+-- @param x X coordinate of point
+-- @param y Y coordinate of point
+-- @return The squared distance of the screen to the provided point.
+-- @see screen.get_square_distance
+function screen.getdistance_sq(s, x, y)
+ util.deprecate("Use s:get_square_distance(x, y) instead of awful.screen.getdistance_sq")
+ return screen.object.get_square_distance(s, x, y)
+end
+
+--- Get the square distance between a `screen` and a point.
+-- @function screen.get_square_distance
+-- @tparam number x X coordinate of point
+-- @tparam number y Y coordinate of point
+-- @treturn number The squared distance of the screen to the provided point.
+function screen.object.get_square_distance(self, x, y)
+ return grect.get_square_distance(get_screen(self).geometry, x, y)
+end
+
+--- Return the screen index corresponding to the given (pixel) coordinates.
+--
+-- The number returned can be used as an index into the global
+-- `screen` table/object.
+-- @function awful.screen.getbycoord
+-- @tparam number x The x coordinate
+-- @tparam number y The y coordinate
+-- @treturn ?number The screen index
+function screen.getbycoord(x, y)
+ local s, sgeos = capi.screen.primary, {}
+ for scr in capi.screen do
+ sgeos[scr] = scr.geometry
+ end
+ s = grect.get_closest_by_coord(sgeos, x, y) or s
+ return s and s.index
+end
+
+--- Move the focus to a screen.
+--
+-- This moves the mouse pointer to the last known position on the new screen,
+-- or keeps its position relative to the current focused screen.
+-- @function awful.screen.focus
+-- @screen _screen Screen number (defaults / falls back to mouse.screen).
+function screen.focus(_screen)
+ client = client or require("awful.client")
+ if type(_screen) == "number" and _screen > capi.screen.count() then _screen = screen.focused() end
+ _screen = get_screen(_screen)
+
+ -- screen and pos for current screen
+ local s = get_screen(capi.mouse.screen)
+ local pos
+
+ if not _screen.mouse_per_screen then
+ -- This is the first time we enter this screen,
+ -- keep relative mouse position on the new screen.
+ pos = capi.mouse.coords()
+ local relx = (pos.x - s.geometry.x) / s.geometry.width
+ local rely = (pos.y - s.geometry.y) / s.geometry.height
+
+ pos.x = _screen.geometry.x + relx * _screen.geometry.width
+ pos.y = _screen.geometry.y + rely * _screen.geometry.height
+ else
+ -- restore mouse position
+ pos = _screen.mouse_per_screen
+ end
+
+ -- save pointer position of current screen
+ s.mouse_per_screen = capi.mouse.coords()
+
+ -- move cursor without triggering signals mouse::enter and mouse::leave
+ capi.mouse.coords(pos, true)
+
+ local c = client.focus.history.get(_screen, 0)
+ if c then
+ c:emit_signal("request::activate", "screen.focus", {raise=false})
+ end
+end
+
+--- Move the focus to a screen in a specific direction.
+--
+-- This moves the mouse pointer to the last known position on the new screen,
+-- or keeps its position relative to the current focused screen.
+-- @function awful.screen.focus_bydirection
+-- @param dir The direction, can be either "up", "down", "left" or "right".
+-- @param _screen Screen.
+function screen.focus_bydirection(dir, _screen)
+ local sel = get_screen(_screen or screen.focused())
+ if sel then
+ local geomtbl = {}
+ for s in capi.screen do
+ geomtbl[s] = s.geometry
+ end
+ local target = grect.get_in_direction(dir, geomtbl, sel.geometry)
+ if target then
+ return screen.focus(target)
+ end
+ end
+end
+
+--- Move the focus to a screen relative to the current one,
+--
+-- This moves the mouse pointer to the last known position on the new screen,
+-- or keeps its position relative to the current focused screen.
+--
+-- @function awful.screen.focus_relative
+-- @tparam int offset Value to add to the current focused screen index. 1 to
+-- focus the next one, -1 to focus the previous one.
+function screen.focus_relative(offset)
+ return screen.focus(util.cycle(capi.screen.count(),
+ screen.focused().index + offset))
+end
+
+--- Get or set the screen padding.
+--
+-- @deprecated awful.screen.padding
+-- @param _screen The screen object to change the padding on
+-- @param[opt=nil] padding The padding, a table with 'top', 'left', 'right' and/or
+-- 'bottom' or a number value to apply set the same padding on all sides. Can be
+-- nil if you only want to retrieve padding
+-- @treturn table A table with left, right, top and bottom number values.
+-- @see padding
+function screen.padding(_screen, padding)
+ util.deprecate("Use _screen.padding = value instead of awful.screen.padding")
+ if padding then
+ screen.object.set_padding(_screen, padding)
+ end
+ return screen.object.get_padding(_screen)
+end
+
+--- The screen padding.
+--
+-- This adds a "buffer" section on each side of the screen.
+--
+-- **Signal:**
+--
+-- * *property::padding*
+--
+-- @property padding
+-- @param table
+-- @tfield integer table.left The padding on the left.
+-- @tfield integer table.right The padding on the right.
+-- @tfield integer table.top The padding on the top.
+-- @tfield integer table.bottom The padding on the bottom.
+
+function screen.object.get_padding(self)
+ local p = data.padding[self] or {}
+ -- Create a copy to avoid accidental mutation and nil values.
+ return {
+ left = p.left or 0,
+ right = p.right or 0,
+ top = p.top or 0,
+ bottom = p.bottom or 0,
+ }
+end
+
+function screen.object.set_padding(self, padding)
+ if type(padding) == "number" then
+ padding = {
+ left = padding,
+ right = padding,
+ top = padding,
+ bottom = padding,
+ }
+ end
+
+ self = get_screen(self)
+ if padding then
+ data.padding[self] = padding
+ self:emit_signal("padding")
+ end
+end
+
+--- Get the preferred screen in the context of a client.
+--
+-- This is exactly the same as `awful.screen.focused` except that it avoids
+-- clients being moved when Awesome is restarted.
+-- This is used in the default `rc.lua` to ensure clients get assigned to the
+-- focused screen by default.
+-- @tparam client c A client.
+-- @treturn screen The preferred screen.
+function screen.preferred(c)
+ return capi.awesome.startup and c.screen or screen.focused()
+end
+
+--- The defaults arguments for `awful.screen.focused`.
+-- @tfield[opt=nil] table awful.screen.default_focused_args
+
+--- Get the focused screen.
+--
+-- It is possible to set `awful.screen.default_focused_args` to override the
+-- default settings.
+--
+-- @function awful.screen.focused
+-- @tparam[opt] table args
+-- @tparam[opt=false] boolean args.client Use the client screen instead of the
+-- mouse screen.
+-- @tparam[opt=true] boolean args.mouse Use the mouse screen
+-- @treturn ?screen The focused screen object, or `nil` in case no screen is
+-- present currently.
+function screen.focused(args)
+ args = args or screen.default_focused_args or {}
+ return get_screen(
+ args.client and capi.client.focus and capi.client.focus.screen or capi.mouse.screen
+ )
+end
+
+--- Get a placement bounding geometry.
+--
+-- This method computes the different variants of the "usable" screen geometry.
+--
+-- @function screen.get_bounding_geometry
+-- @tparam[opt={}] table args The arguments
+-- @tparam[opt=false] boolean args.honor_padding Whether to honor the screen's padding.
+-- @tparam[opt=false] boolean args.honor_workarea Whether to honor the screen's workarea.
+-- @tparam[opt] int|table args.margins Apply some margins on the output.
+-- This can either be a number or a table with *left*, *right*, *top*
+-- and *bottom* keys.
+-- @tag[opt] args.tag Use this tag's screen.
+-- @tparam[opt] drawable args.parent A parent drawable to use as base geometry.
+-- @tab[opt] args.bounding_rect A bounding rectangle. This parameter is
+-- incompatible with `honor_workarea`.
+-- @treturn table A table with *x*, *y*, *width* and *height*.
+-- @usage local geo = screen:get_bounding_geometry {
+-- honor_padding = true,
+-- honor_workarea = true,
+-- margins = {
+-- left = 20,
+-- },
+-- }
+function screen.object.get_bounding_geometry(self, args)
+ args = args or {}
+
+ -- If the tag has a geometry, assume it is right
+ if args.tag then
+ self = args.tag.screen
+ end
+
+ self = get_screen(self or capi.mouse.screen)
+
+ local geo = args.bounding_rect or (args.parent and args.parent:geometry()) or
+ self[args.honor_workarea and "workarea" or "geometry"]
+
+ if (not args.parent) and (not args.bounding_rect) and args.honor_padding then
+ local padding = self.padding
+ geo = apply_geometry_ajustments(geo, padding)
+ end
+
+ if args.margins then
+ geo = apply_geometry_ajustments(geo,
+ type(args.margins) == "table" and args.margins or {
+ left = args.margins, right = args.margins,
+ top = args.margins, bottom = args.margins,
+ }
+ )
+ end
+ return geo
+end
+
+--- Get the list of visible clients for the screen.
+--
+-- Minimized and unmanaged clients are not included in this list as they are
+-- technically not on the screen.
+--
+-- The clients on tags that are currently not visible are not part of this list.
+--
+-- @property clients
+-- @param table The clients list, ordered from top to bottom.
+-- @see all_clients
+-- @see hidden_clients
+-- @see client.get
+
+function screen.object.get_clients(s)
+ local cls = capi.client.get(s, true)
+ local vcls = {}
+ for _, c in pairs(cls) do
+ if c:isvisible() then
+ table.insert(vcls, c)
+ end
+ end
+ return vcls
+end
+
+--- Get the list of clients assigned to the screen but not currently visible.
+--
+-- This includes minimized clients and clients on hidden tags.
+--
+-- @property hidden_clients
+-- @param table The clients list, ordered from top to bottom.
+-- @see clients
+-- @see all_clients
+-- @see client.get
+
+function screen.object.get_hidden_clients(s)
+ local cls = capi.client.get(s, true)
+ local vcls = {}
+ for _, c in pairs(cls) do
+ if not c:isvisible() then
+ table.insert(vcls, c)
+ end
+ end
+ return vcls
+end
+
+--- Get all clients assigned to the screen.
+--
+-- @property all_clients
+-- @param table The clients list, ordered from top to bottom.
+-- @see clients
+-- @see hidden_clients
+-- @see client.get
+
+function screen.object.get_all_clients(s)
+ return capi.client.get(s, true)
+end
+
+--- Get the list of tiled clients for the screen.
+--
+-- Same as `clients`, but excluding:
+--
+-- * fullscreen clients
+-- * maximized clients
+-- * floating clients
+--
+-- @property tiled_clients
+-- @param table The clients list, ordered from top to bottom.
+
+function screen.object.get_tiled_clients(s)
+ local clients = s.clients
+ local tclients = {}
+ -- Remove floating clients
+ for _, c in pairs(clients) do
+ if not c.floating
+ and not c.fullscreen
+ and not c.maximized_vertical
+ and not c.maximized_horizontal then
+ table.insert(tclients, c)
+ end
+ end
+ return tclients
+end
+
+--- Call a function for each existing and created-in-the-future screen.
+--
+-- @function awful.screen.connect_for_each_screen
+-- @tparam function func The function to call.
+-- @screen func.screen The screen.
+function screen.connect_for_each_screen(func)
+ for s in capi.screen do
+ func(s)
+ end
+ capi.screen.connect_signal("added", func)
+end
+
+--- Undo the effect of connect_for_each_screen.
+-- @function awful.screen.disconnect_for_each_screen
+-- @tparam function func The function that should no longer be called.
+function screen.disconnect_for_each_screen(func)
+ capi.screen.disconnect_signal("added", func)
+end
+
+--- A list of all tags on the screen.
+--
+-- This property is read only, use `tag.screen`, `awful.tag.add`,
+-- `awful.tag.new` or `t:delete()` to alter this list.
+--
+-- @property tags
+-- @param table
+-- @treturn table A table with all available tags.
+
+function screen.object.get_tags(s, unordered)
+ local tags = {}
+
+ for _, t in ipairs(root.tags()) do
+ if get_screen(t.screen) == s then
+ table.insert(tags, t)
+ end
+ end
+
+ -- Avoid infinite loop and save some time.
+ if not unordered then
+ table.sort(tags, function(a, b)
+ return (a.index or math.huge) < (b.index or math.huge)
+ end)
+ end
+ return tags
+end
+
+--- A list of all selected tags on the screen.
+-- @property selected_tags
+-- @param table
+-- @treturn table A table with all selected tags.
+-- @see tag.selected
+-- @see client.to_selected_tags
+
+function screen.object.get_selected_tags(s)
+ local tags = screen.object.get_tags(s, true)
+
+ local vtags = {}
+ for _, t in pairs(tags) do
+ if t.selected then
+ vtags[#vtags + 1] = t
+ end
+ end
+ return vtags
+end
+
+--- The first selected tag.
+-- @property selected_tag
+-- @param table
+-- @treturn ?tag The first selected tag or nil.
+-- @see tag.selected
+-- @see selected_tags
+
+function screen.object.get_selected_tag(s)
+ return screen.object.get_selected_tags(s)[1]
+end
+
+
+--- When the tag history changed.
+-- @signal tag::history::update
+
+-- Extend the luaobject
+object.properties(capi.screen, {
+ getter_class = screen.object,
+ setter_class = screen.object,
+ auto_emit = true,
+})
+
+return screen
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/spawn.lua b/lib/awful/spawn.lua
new file mode 100644
index 0000000..bcfb68d
--- /dev/null
+++ b/lib/awful/spawn.lua
@@ -0,0 +1,421 @@
+---------------------------------------------------------------------------
+--- Spawning of programs.
+--
+-- This module provides methods to start programs and supports startup
+-- notifications, which allows for callbacks and applying properties to the
+-- program after it has been launched. This requires currently that the
+-- applicaton supports them.
+--
+-- **Rules of thumb when a shell is needed**:
+--
+-- * A shell is required when the commands contain `&&`, `;`, `||`, `&` or
+-- any other unix shell language syntax
+-- * When shell variables are defined as part of the command
+-- * When the command is a shell alias
+--
+-- Note that a shell is **not** a terminal emulator. A terminal emulator is
+-- something like XTerm, Gnome-terminal or Konsole. A shell is something like
+-- `bash`, `zsh`, `busybox sh` or `Debian ash`.
+--
+-- If you wish to open a process in a terminal window, check that your terminal
+-- emulator supports the common `-e` option. If it does, then something like
+-- this should work:
+--
+-- awful.spawn(terminal.." -e my_command")
+--
+-- Note that some terminals, such as rxvt-unicode (urxvt) support full commands
+-- using quotes, while other terminal emulators require to use quoting.
+--
+-- **Understanding clients versus PID versus commands versus class**:
+--
+-- A *process* has a *PID* (process identifier). It can have 0, 1 or many
+-- *window*s.
+--
+-- A *command* if what is used to start *process*(es). It has no direct relation
+-- with *process*, *client* or *window*. When a command is executed, it will
+-- usually start a *process* which keeps running until it exits. This however is
+-- not always the case as some applications use scripts as command and others
+-- use various single-instance mechanisms (usually client/server) and merge
+-- with an existing process.
+--
+-- A *client* corresponds to a *window*. It is owned by a process. It can have
+-- both a parent and one or many children. A *client* has a *class*, an
+-- *instance*, a *role*, and a *type*. See `client.class`, `client.instance`,
+-- `client.role` and `client.type` for more information about these properties.
+--
+-- **The startup notification protocol**:
+--
+-- The startup notification protocol is an optional specification implemented
+-- by X11 applications to bridge the chain of knowledge between the moment a
+-- program is launched to the moment its window (client) is shown. It can be
+-- found [on the FreeDesktop.org website](https://www.freedesktop.org/wiki/Specifications/startup-notification-spec/).
+--
+-- Awesome has support for the various events that are part of the protocol, but
+-- the most useful is the identifier, usually identified by its `SNID` acronym in
+-- the documentation. It isn't usually necessary to even know it exists, as it
+-- is all done automatically. However, if more control is required, the
+-- identifier can be specified by an environment variable called
+-- `DESKTOP_STARTUP_ID`. For example, let us consider execution of the following
+-- command:
+--
+-- DESKTOP_STARTUP_ID="something_TIME$(date '+%s')" my_command
+--
+-- This should (if the program correctly implements the protocol) result in
+-- `c.startup_id` to at least match `something`.
+-- This identifier can then be used in `awful.rules` to configure the client.
+--
+-- Awesome can automatically set the `DESKTOP_STARTUP_ID` variable. This is used
+-- by `awful.spawn` to specify additional rules for the startup. For example:
+--
+-- awful.spawn("urxvt -e maxima -name CALCULATOR", {
+-- floating = true,
+-- tag = mouse.screen.selected_tag,
+-- placement = awful.placement.bottom_right,
+-- })
+--
+-- This can also be used from the command line:
+--
+-- awesome-client 'awful=require("awful");
+-- awful.spawn("urxvt -e maxima -name CALCULATOR", {
+-- floating = true,
+-- tag = mouse.screen.selected_tag,
+-- placement = awful.placement.bottom_right,
+-- })'
+--
+-- **Getting a command's output**:
+--
+-- First, do **not** use `io.popen` **ever**. It is synchronous. Synchronous
+-- functions **block everything** until they are done. All visual applications
+-- lock (as Awesome no longer responds), you will probably lose some keyboard
+-- and mouse events and will have higher latency when playing games. This is
+-- also true when reading files synchronously, but this is another topic.
+--
+-- Awesome provides a few ways to get output from commands. One is to use the
+-- `Gio` libraries directly. This is usually very complicated, but gives a lot
+-- of control on the command execution.
+--
+-- This modules provides `with_line_callback` and `easy_async` for convenience.
+-- First, lets add this bash command to `rc.lua`:
+--
+-- local noisy = [[bash -c '
+-- for I in $(seq 1 5); do
+-- date
+-- echo err >&2
+-- sleep 2
+-- done
+-- ']]
+--
+-- It prints a bunch of junk on the standard output (*stdout*) and error
+-- (*stderr*) streams. This command would block Awesome for 10 seconds if it
+-- were executed synchronously, but will not block it at all using the
+-- asynchronous functions.
+--
+-- `with_line_callback` will execute the callbacks every time a new line is
+-- printed by the command:
+--
+-- awful.spawn.with_line_callback(noisy, {
+-- stdout = function(line)
+-- naughty.notify { text = "LINE:"..line }
+-- end,
+-- stderr = function(line)
+-- naughty.notify { text = "ERR:"..line}
+-- end,
+-- })
+--
+-- If only the full output is needed, then `easy_async` is the right choice:
+--
+-- awful.spawn.easy_async(noisy, function(stdout, stderr, reason, exit_code)
+-- naughty.notify { text = stdout }
+-- end)
+--
+-- **Default applications**:
+--
+-- If the intent is to open a file/document, then it is recommended to use the
+-- following standard command. The default application will be selected
+-- according to the [Shared MIME-info Database](https://specifications.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-latest.html)
+-- specification. The `xdg-utils` package provided by most distributions
+-- includes the `xdg-open` command:
+--
+-- awful.spawn({"xdg-open", "/path/to/file"})
+--
+-- Awesome **does not** manage, modify or otherwise influence the database
+-- for default applications. For information about how to do this, consult the
+-- [ARCH Linux Wiki](https://wiki.archlinux.org/index.php/default_applications).
+--
+-- If you wish to change how the default applications behave, then consult the
+-- [Desktop Entry](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html)
+-- specification.
+--
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @author Emmanuel Lepage Vallee &lt;elv1313@gmail.com&gt;
+-- @copyright 2008 Julien Danjou
+-- @copyright 2014 Emmanuel Lepage Vallee
+-- @module awful.spawn
+---------------------------------------------------------------------------
+
+local capi =
+{
+ awesome = awesome,
+ mouse = mouse,
+ client = client,
+}
+local lgi = require("lgi")
+local Gio = lgi.Gio
+local GLib = lgi.GLib
+local util = require("awful.util")
+local protected_call = require("gears.protected_call")
+
+local spawn = {}
+
+
+local end_of_file
+do
+ -- API changes, bug fixes and lots of fun. Figure out how a EOF is signalled.
+ local input
+ if not pcall(function()
+ -- No idea when this API changed, but some versions expect a string,
+ -- others a table with some special(?) entries
+ input = Gio.DataInputStream.new(Gio.MemoryInputStream.new_from_data(""))
+ end) then
+ input = Gio.DataInputStream.new(Gio.MemoryInputStream.new_from_data({}))
+ end
+ local line, length = input:read_line()
+ if not line then
+ -- Fixed in 2016: NULL on the C side is transformed to nil in Lua
+ end_of_file = function(arg)
+ return not arg
+ end
+ elseif tostring(line) == "" and #line ~= length then
+ -- "Historic" behaviour for end-of-file:
+ -- - NULL is turned into an empty string
+ -- - The length variable is not initialized
+ -- It's highly unlikely that the uninitialized variable has value zero.
+ -- Use this hack to detect EOF.
+ end_of_file = function(arg1, arg2)
+ return #arg1 ~= arg2
+ end
+ else
+ assert(tostring(line) == "", "Cannot determine how to detect EOF")
+ -- The above uninitialized variable was fixed and thus length is
+ -- always 0 when line is NULL in C. We cannot tell apart an empty line and
+ -- EOF in this case.
+ require("gears.debug").print_warning("Cannot reliably detect EOF on an "
+ .. "GIOInputStream with this LGI version")
+ end_of_file = function(arg)
+ return tostring(arg) == ""
+ end
+ end
+end
+
+spawn.snid_buffer = {}
+
+function spawn.on_snid_callback(c)
+ local entry = spawn.snid_buffer[c.startup_id]
+ if entry then
+ local props = entry[1]
+ local callback = entry[2]
+ c:emit_signal("spawn::completed_with_payload", props, callback)
+ spawn.snid_buffer[c.startup_id] = nil
+ end
+end
+
+function spawn.on_snid_cancel(id)
+ if spawn.snid_buffer[id] then
+ spawn.snid_buffer[id] = nil
+ end
+end
+
+--- Spawn a program, and optionally apply properties and/or run a callback.
+--
+-- Applying properties or running a callback requires the program/client to
+-- support startup notifications.
+--
+-- See `awful.rules.execute` for more details about the format of `sn_rules`.
+--
+-- @tparam string|table cmd The command.
+-- @tparam[opt=true] table|boolean sn_rules A table of properties to be applied
+-- after startup; `false` to disable startup notifications.
+-- @tparam[opt] function callback A callback function to be run after startup.
+-- @treturn[1] integer The forked PID.
+-- @treturn[1] ?string The startup notification ID, if `sn` is not false, or
+-- a `callback` is provided.
+-- @treturn[2] string Error message.
+function spawn.spawn(cmd, sn_rules, callback)
+ if cmd and cmd ~= "" then
+ local enable_sn = (sn_rules ~= false or callback)
+ enable_sn = not not enable_sn -- Force into a boolean.
+ local pid, snid = capi.awesome.spawn(cmd, enable_sn)
+ -- The snid will be nil in case of failure
+ if snid then
+ sn_rules = type(sn_rules) ~= "boolean" and sn_rules or {}
+ spawn.snid_buffer[snid] = { sn_rules, { callback } }
+ end
+ return pid, snid
+ end
+ -- For consistency
+ return "Error: No command to execute"
+end
+
+--- Spawn a program using the shell.
+-- This calls `cmd` with `$SHELL -c` (via `awful.util.shell`).
+-- @tparam string cmd The command.
+function spawn.with_shell(cmd)
+ if cmd and cmd ~= "" then
+ cmd = { util.shell, "-c", cmd }
+ return capi.awesome.spawn(cmd, false)
+ end
+end
+
+--- Spawn a program and asynchronously capture its output line by line.
+-- @tparam string|table cmd The command.
+-- @tab callbacks Table containing callbacks that should be invoked on
+-- various conditions.
+-- @tparam[opt] function callbacks.stdout Function that is called with each
+-- line of output on stdout, e.g. `stdout(line)`.
+-- @tparam[opt] function callbacks.stderr Function that is called with each
+-- line of output on stderr, e.g. `stderr(line)`.
+-- @tparam[opt] function callbacks.output_done Function to call when no more
+-- output is produced.
+-- @tparam[opt] function callbacks.exit Function to call when the spawned
+-- process exits. This function gets the exit reason and code as its
+-- arguments.
+-- The reason can be "exit" or "signal".
+-- For "exit", the second argument is the exit code.
+-- For "signal", the second argument is the signal causing process
+-- termination.
+-- @treturn[1] Integer the PID of the forked process.
+-- @treturn[2] string Error message.
+function spawn.with_line_callback(cmd, callbacks)
+ local stdout_callback, stderr_callback, done_callback, exit_callback =
+ callbacks.stdout, callbacks.stderr, callbacks.output_done, callbacks.exit
+ local have_stdout, have_stderr = stdout_callback ~= nil, stderr_callback ~= nil
+ local pid, _, stdin, stdout, stderr = capi.awesome.spawn(cmd,
+ false, false, have_stdout, have_stderr, exit_callback)
+ if type(pid) == "string" then
+ -- Error
+ return pid
+ end
+
+ local done_before = false
+ local function step_done()
+ if have_stdout and have_stderr and not done_before then
+ done_before = true
+ return
+ end
+ if done_callback then
+ done_callback()
+ end
+ end
+ if have_stdout then
+ spawn.read_lines(Gio.UnixInputStream.new(stdout, true),
+ stdout_callback, step_done, true)
+ end
+ if have_stderr then
+ spawn.read_lines(Gio.UnixInputStream.new(stderr, true),
+ stderr_callback, step_done, true)
+ end
+ assert(stdin == nil)
+ return pid
+end
+
+--- Asynchronously spawn a program and capture its output.
+-- (wraps `spawn.with_line_callback`).
+-- @tparam string|table cmd The command.
+-- @tab callback Function with the following arguments
+-- @tparam string callback.stdout Output on stdout.
+-- @tparam string callback.stderr Output on stderr.
+-- @tparam string callback.exitreason Exit Reason.
+-- The reason can be "exit" or "signal".
+-- @tparam integer callback.exitcode Exit code.
+-- For "exit" reason it's the exit code.
+-- For "signal" reason — the signal causing process termination.
+-- @treturn[1] Integer the PID of the forked process.
+-- @treturn[2] string Error message.
+-- @see spawn.with_line_callback
+function spawn.easy_async(cmd, callback)
+ local stdout = ''
+ local stderr = ''
+ local exitcode, exitreason
+ local function parse_stdout(str)
+ stdout = stdout .. str .. "\n"
+ end
+ local function parse_stderr(str)
+ stderr = stderr .. str .. "\n"
+ end
+ local function done_callback()
+ return callback(stdout, stderr, exitreason, exitcode)
+ end
+ local exit_callback_fired = false
+ local output_done_callback_fired = false
+ local function exit_callback(reason, code)
+ exitcode = code
+ exitreason = reason
+ exit_callback_fired = true
+ if output_done_callback_fired then
+ return done_callback()
+ end
+ end
+ local function output_done_callback()
+ output_done_callback_fired = true
+ if exit_callback_fired then
+ return done_callback()
+ end
+ end
+ return spawn.with_line_callback(
+ cmd, {
+ stdout=parse_stdout,
+ stderr=parse_stderr,
+ exit=exit_callback,
+ output_done=output_done_callback
+ })
+end
+
+--- Read lines from a Gio input stream
+-- @tparam Gio.InputStream input_stream The input stream to read from.
+-- @tparam function line_callback Function that is called with each line
+-- read, e.g. `line_callback(line_from_stream)`.
+-- @tparam[opt] function done_callback Function that is called when the
+-- operation finishes (e.g. due to end of file).
+-- @tparam[opt=false] boolean close Should the stream be closed after end-of-file?
+function spawn.read_lines(input_stream, line_callback, done_callback, close)
+ local stream = Gio.DataInputStream.new(input_stream)
+ local function done()
+ if close then
+ stream:close()
+ end
+ if done_callback then
+ protected_call(done_callback)
+ end
+ end
+ local start_read, finish_read
+ start_read = function()
+ stream:read_line_async(GLib.PRIORITY_DEFAULT, nil, finish_read)
+ end
+ finish_read = function(obj, res)
+ local line, length = obj:read_line_finish(res)
+ if type(length) ~= "number" then
+ -- Error
+ print("Error in awful.spawn.read_lines:", tostring(length))
+ done()
+ elseif end_of_file(line, length) then
+ -- End of file
+ done()
+ else
+ -- Read a line
+ -- This needs tostring() for older lgi versions which returned
+ -- "GLib.Bytes" instead of Lua strings (I guess)
+ protected_call(line_callback, tostring(line))
+
+ -- Read the next line
+ start_read()
+ end
+ end
+ start_read()
+end
+
+capi.awesome.connect_signal("spawn::canceled" , spawn.on_snid_cancel )
+capi.awesome.connect_signal("spawn::timeout" , spawn.on_snid_cancel )
+capi.client.connect_signal ("manage" , spawn.on_snid_callback )
+
+return setmetatable(spawn, { __call = function(_, ...) return spawn.spawn(...) end })
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/startup_notification.lua b/lib/awful/startup_notification.lua
new file mode 100644
index 0000000..5f1c123
--- /dev/null
+++ b/lib/awful/startup_notification.lua
@@ -0,0 +1,53 @@
+---------------------------------------------------------------------------
+--- Startup notification module for awful
+--
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2009 Julien Danjou
+-- @module awful.startup_notification
+---------------------------------------------------------------------------
+
+-- Grab environment we need
+local ipairs = ipairs
+local table = table
+local capi =
+{
+ awesome = awesome,
+ root = root
+}
+
+local app_starting = {}
+
+local cursor_waiting = "watch"
+
+local function update_cursor()
+ if #app_starting > 0 then
+ capi.root.cursor(cursor_waiting)
+ else
+ capi.root.cursor("left_ptr")
+ end
+end
+
+local function unregister_event(event_id)
+ for k, v in ipairs(app_starting) do
+ if v == event_id then
+ table.remove(app_starting, k)
+ update_cursor()
+ break
+ end
+ end
+end
+
+local function register_event(event_id)
+ table.insert(app_starting, event_id)
+ update_cursor()
+end
+
+local function unregister_hook(event) unregister_event(event.id) end
+local function register_hook(event) register_event(event.id) end
+
+capi.awesome.connect_signal("spawn::initiated", register_hook)
+capi.awesome.connect_signal("spawn::canceled", unregister_hook)
+capi.awesome.connect_signal("spawn::completed", unregister_hook)
+capi.awesome.connect_signal("spawn::timeout", unregister_hook)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/tag.lua b/lib/awful/tag.lua
new file mode 100644
index 0000000..dbf3a60
--- /dev/null
+++ b/lib/awful/tag.lua
@@ -0,0 +1,1505 @@
+---------------------------------------------------------------------------
+--- Useful functions for tag manipulation.
+--
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2008 Julien Danjou
+-- @module tag
+---------------------------------------------------------------------------
+
+-- Grab environment we need
+local util = require("awful.util")
+local ascreen = require("awful.screen")
+local beautiful = require("beautiful")
+local object = require("gears.object")
+local timer = require("gears.timer")
+local pairs = pairs
+local ipairs = ipairs
+local table = table
+local setmetatable = setmetatable
+local capi =
+{
+ tag = tag,
+ screen = screen,
+ mouse = mouse,
+ client = client,
+ root = root
+}
+
+local function get_screen(s)
+ return s and capi.screen[s]
+end
+
+local tag = {object = {}, mt = {} }
+
+-- Private data
+local data = {}
+data.history = {}
+
+-- History functions
+tag.history = {}
+tag.history.limit = 20
+
+-- Default values
+local defaults = {}
+
+-- The gap between clients (in points).
+defaults.gap = 0
+
+-- The default gap_count.
+defaults.gap_single_client = true
+
+-- The default master fill policy.
+defaults.master_fill_policy = "expand"
+
+-- The default master width factor.
+defaults.master_width_factor = 0.5
+
+-- The default master count.
+defaults.master_count = 1
+
+-- The default column count.
+defaults.column_count = 1
+
+-- screen.tags depend on index, it cannot be used by awful.tag
+local function raw_tags(scr)
+ local tmp_tags = {}
+ for _, t in ipairs(root.tags()) do
+ if get_screen(t.screen) == scr then
+ table.insert(tmp_tags, t)
+ end
+ end
+
+ return tmp_tags
+end
+
+--- The number of elements kept in the history.
+-- @tfield integer awful.tag.history.limit
+-- @tparam[opt=20] integer limit
+
+--- The tag index.
+--
+-- The index is the position as shown in the `awful.widget.taglist`.
+--
+-- **Signal:**
+--
+-- * *property::index*
+--
+-- @property index
+-- @param integer
+-- @treturn number The tag index.
+
+function tag.object.set_index(self, idx)
+ local scr = get_screen(tag.getproperty(self, "screen"))
+
+ -- screen.tags cannot be used as it depend on index
+ local tmp_tags = raw_tags(scr)
+
+ -- sort the tags by index
+ table.sort(tmp_tags, function(a, b)
+ local ia, ib = tag.getproperty(a, "index"), tag.getproperty(b, "index")
+ return (ia or math.huge) < (ib or math.huge)
+ end)
+
+ if (not idx) or (idx < 1) or (idx > #tmp_tags) then
+ return
+ end
+
+ local rm_index = nil
+
+ for i, t in ipairs(tmp_tags) do
+ if t == self then
+ table.remove(tmp_tags, i)
+ rm_index = i
+ break
+ end
+ end
+
+ table.insert(tmp_tags, idx, self)
+ for i = idx < rm_index and idx or rm_index, #tmp_tags do
+ local tmp_tag = tmp_tags[i]
+ tag.object.set_screen(tmp_tag, scr)
+ tag.setproperty(tmp_tag, "index", i)
+ end
+end
+
+function tag.object.get_index(query_tag)
+
+ local idx = tag.getproperty(query_tag, "index")
+
+ if idx then return idx end
+
+ -- Get an unordered list of tags
+ local tags = raw_tags(query_tag.screen)
+
+ -- Too bad, lets compute it
+ for i, t in ipairs(tags) do
+ if t == query_tag then
+ tag.setproperty(t, "index", i)
+ return i
+ end
+ end
+end
+
+--- Move a tag to an absolute position in the screen[]:tags() table.
+-- @deprecated awful.tag.move
+-- @param new_index Integer absolute position in the table to insert.
+-- @param target_tag The tag that should be moved. If null, the currently
+-- selected tag is used.
+-- @see index
+function tag.move(new_index, target_tag)
+ util.deprecate("Use t.index = new_index instead of awful.tag.move")
+
+ target_tag = target_tag or ascreen.focused().selected_tag
+ tag.object.set_index(target_tag, new_index)
+end
+
+--- Swap 2 tags
+-- @function tag.swap
+-- @param tag2 The second tag
+-- @see client.swap
+function tag.object.swap(self, tag2)
+ local idx1, idx2 = tag.object.get_index(self), tag.object.get_index(tag2)
+ local scr2, scr1 = tag.getproperty(tag2, "screen"), tag.getproperty(self, "screen")
+
+ -- If they are on the same screen, avoid recomputing the whole table
+ -- for nothing.
+ if scr1 == scr2 then
+ tag.setproperty(self, "index", idx2)
+ tag.setproperty(tag2, "index", idx1)
+ else
+ tag.object.set_screen(tag2, scr1)
+ tag.object.set_index (tag2, idx1)
+ tag.object.set_screen(self, scr2)
+ tag.object.set_index (self, idx2)
+ end
+end
+
+--- Swap 2 tags
+-- @deprecated awful.tag.swap
+-- @see tag.swap
+-- @param tag1 The first tag
+-- @param tag2 The second tag
+function tag.swap(tag1, tag2)
+ util.deprecate("Use t:swap(tag2) instead of awful.tag.swap")
+
+ tag.object.swap(tag1, tag2)
+end
+
+--- Add a tag.
+--
+-- This function allow to create tags from a set of properties:
+--
+-- local t = awful.tag.add("my new tag", {
+-- screen = screen.primary,
+-- layout = awful.layout.suit.max,
+-- })
+--
+-- @function awful.tag.add
+-- @param name The tag name, a string
+-- @param props The tags inital properties, a table
+-- @return The created tag
+-- @see tag.delete
+function tag.add(name, props)
+ local properties = props or {}
+
+ -- Be sure to set the screen before the tag is activated to avoid function
+ -- connected to property::activated to be called without a valid tag.
+ -- set properties cannot be used as this has to be set before the first
+ -- signal is sent
+ properties.screen = get_screen(properties.screen or ascreen.focused())
+ -- Index is also required
+ properties.index = properties.index or #raw_tags(properties.screen)+1
+
+ local newtag = capi.tag{ name = name }
+
+ -- Start with a fresh property table to avoid collisions with unsupported data
+ newtag.data.awful_tag_properties = {screen=properties.screen, index=properties.index}
+
+ newtag.activated = true
+
+ for k, v in pairs(properties) do
+ -- `rawget` doesn't work on userdata, `:clients()` is the only relevant
+ -- entry.
+ if k == "clients" or tag.object[k] then
+ newtag[k](newtag, v)
+ else
+ newtag[k] = v
+ end
+ end
+
+ return newtag
+end
+
+--- Create a set of tags and attach it to a screen.
+-- @function awful.tag.new
+-- @param names The tag name, in a table
+-- @param screen The tag screen, or 1 if not set.
+-- @param layout The layout or layout table to set for this tags by default.
+-- @return A table with all created tags.
+function tag.new(names, screen, layout)
+ screen = get_screen(screen or 1)
+ local tags = {}
+ for id, name in ipairs(names) do
+ table.insert(tags, id, tag.add(name, {screen = screen,
+ layout = (layout and layout[id]) or
+ layout}))
+ -- Select the first tag.
+ if id == 1 then
+ tags[id].selected = true
+ end
+ end
+
+ return tags
+end
+
+--- Find a suitable fallback tag.
+-- @function awful.tag.find_fallback
+-- @param screen The screen to look for a tag on. [awful.screen.focused()]
+-- @param invalids A table of tags we consider unacceptable. [selectedlist(scr)]
+function tag.find_fallback(screen, invalids)
+ local scr = screen or ascreen.focused()
+ local t = invalids or scr.selected_tags
+
+ for _, v in pairs(scr.tags) do
+ if not util.table.hasitem(t, v) then return v end
+ end
+end
+
+--- Delete a tag.
+--
+-- To delete the current tag:
+--
+-- mouse.screen.selected_tag:delete()
+--
+-- @function tag.delete
+-- @see awful.tag.add
+-- @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.
+-- 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, force)
+ -- abort if the taf isn't currently activated
+ if not self.activated then return false end
+
+ local target_scr = get_screen(tag.getproperty(self, "screen"))
+ local tags = target_scr.tags
+ local idx = tag.object.get_index(self)
+ local ntags = #tags
+
+ -- We can't use the target tag as a fallback.
+ if fallback_tag == self then return false end
+
+ -- No fallback_tag provided, try and get one.
+ if fallback_tag == nil then
+ fallback_tag = tag.find_fallback(target_scr, {self})
+ end
+
+ -- Abort if we would have un-tagged clients.
+ local clients = self:clients()
+ if #clients > 0 and fallback_tag == nil then return false end
+
+ -- Move the clients we can off of this tag.
+ for _, c in pairs(clients) do
+ local nb_tags = #c:tags()
+
+ -- If a client has only this tag, or stickied clients with
+ -- nowhere to go, abort.
+ 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
+ c:tags({fallback_tag})
+ end
+ end
+
+ -- delete the tag
+ self.data.awful_tag_properties.screen = nil
+ self.activated = false
+
+ -- Update all indexes
+ for i=idx+1, #tags do
+ tag.setproperty(tags[i], "index", i-1)
+ end
+
+ -- If no tags are visible (and we did not delete the lasttag), try and
+ -- view one. The > 1 is because ntags is no longer synchronized with the
+ -- current count.
+ if target_scr.selected_tag == nil and ntags > 1 then
+ tag.history.restore(target_scr, 1)
+ if target_scr.selected_tag == nil then
+ local other_tag = tags[tags[1] == self and 2 or 1]
+ if other_tag then
+ other_tag.selected = true
+ end
+ end
+ end
+
+ return true
+end
+
+--- Delete a tag.
+-- @deprecated awful.tag.delete
+-- @see tag.delete
+-- @param target_tag Optional tag object to delete. [selected()]
+-- @param fallback_tag Tag to assign stickied tags to. [~selected()]
+-- @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.delete(target_tag, fallback_tag)
+ util.deprecate("Use t:delete(fallback_tag) instead of awful.tag.delete")
+
+ return tag.object.delete(target_tag, fallback_tag)
+end
+
+--- Update the tag history.
+-- @function awful.tag.history.update
+-- @param obj Screen object.
+function tag.history.update(obj)
+ local s = get_screen(obj)
+ local curtags = s.selected_tags
+ -- create history table
+ if not data.history[s] then
+ data.history[s] = {}
+ else
+ if data.history[s].current then
+ -- Check that the list is not identical
+ local identical = #data.history[s].current == #curtags
+ if identical then
+ for idx, _tag in ipairs(data.history[s].current) do
+ if curtags[idx] ~= _tag then
+ identical = false
+ break
+ end
+ end
+ end
+
+ -- Do not update history the table are identical
+ if identical then return end
+ end
+
+ -- Limit history
+ if #data.history[s] >= tag.history.limit then
+ for i = tag.history.limit, #data.history[s] do
+ data.history[s][i] = nil
+ end
+ end
+ end
+
+ -- store previously selected tags in the history table
+ table.insert(data.history[s], 1, data.history[s].current)
+ data.history[s].previous = data.history[s][1]
+ -- store currently selected tags
+ data.history[s].current = setmetatable(curtags, { __mode = 'v' })
+end
+
+--- Revert tag history.
+-- @function awful.tag.history.restore
+-- @param screen The screen.
+-- @param idx Index in history. Defaults to "previous" which is a special index
+-- toggling between last two selected sets of tags. Number (eg 1) will go back
+-- to the given index in history.
+function tag.history.restore(screen, idx)
+ local s = get_screen(screen or ascreen.focused())
+ local i = idx or "previous"
+ local sel = s.selected_tags
+ -- do nothing if history empty
+ if not data.history[s] or not data.history[s][i] then return end
+ -- if all tags been deleted, try next entry
+ if #data.history[s][i] == 0 then
+ if i == "previous" then i = 0 end
+ tag.history.restore(s, i + 1)
+ return
+ end
+ -- deselect all tags
+ tag.viewnone(s)
+ -- select tags from the history entry
+ for _, t in ipairs(data.history[s][i]) do
+ if t.activated and t.screen then
+ t.selected = true
+ end
+ end
+ -- update currently selected tags table
+ data.history[s].current = data.history[s][i]
+ -- store previously selected tags
+ data.history[s].previous = setmetatable(sel, { __mode = 'v' })
+ -- remove the reverted history entry
+ if i ~= "previous" then table.remove(data.history[s], i) end
+
+ s:emit_signal("tag::history::update")
+end
+
+--- Get a list of all tags on a screen
+-- @deprecated awful.tag.gettags
+-- @tparam screen s Screen
+-- @return A table with all available tags
+-- @see screen.tags
+function tag.gettags(s)
+ util.deprecate("Use s.tags instead of awful.tag.gettags")
+
+ s = get_screen(s)
+
+ return s and s.tags or {}
+end
+
+--- Find a tag by name
+-- @tparam[opt] screen s The screen of the tag
+-- @tparam string name The name of the tag
+-- @return The tag found, or `nil`
+function tag.find_by_name(s, name)
+ local tags = s and s.tags or root.tags()
+ for _, t in ipairs(tags) do
+ if name == t.name then
+ return t
+ end
+ end
+end
+
+--- The tag screen.
+--
+-- **Signal:**
+--
+-- * *property::screen*
+--
+-- @property screen
+-- @param screen
+-- @see screen
+
+function tag.object.set_screen(t, s)
+
+ s = get_screen(s or ascreen.focused())
+ local sel = tag.selected
+ local old_screen = get_screen(tag.getproperty(t, "screen"))
+
+ if s == old_screen then return end
+
+ -- Keeping the old index make very little sense when changing screen
+ tag.setproperty(t, "index", nil)
+
+ -- Change the screen
+ tag.setproperty(t, "screen", s)
+ tag.setproperty(t, "index", #s:get_tags(true))
+
+ -- Make sure the client's screen matches its tags
+ for _,c in ipairs(t:clients()) do
+ c.screen = s --Move all clients
+ c:tags({t})
+ end
+
+ -- Update all indexes
+ for i,t2 in ipairs(old_screen.tags) do
+ tag.setproperty(t2, "index", i)
+ end
+
+ -- Restore the old screen history if the tag was selected
+ if sel then
+ tag.history.restore(old_screen, 1)
+ end
+end
+
+--- Set a tag's screen
+-- @deprecated awful.tag.setscreen
+-- @see screen
+-- @param s Screen
+-- @param t tag object
+function tag.setscreen(s, t)
+ -- For API consistency, the arguments have been swapped for Awesome 3.6
+ -- this method is already deprecated, so be silent and swap the args
+ if type(t) == "number" then
+ s, t = t, s
+ end
+
+ util.deprecate("Use t.screen = s instead of awful.tag.setscreen(t, s)")
+
+ tag.object.set_screen(t, s)
+end
+
+--- Get a tag's screen
+-- @deprecated awful.tag.getscreen
+-- @see screen
+-- @param[opt] t tag object
+-- @return Screen number
+function tag.getscreen(t)
+ util.deprecate("Use t.screen instead of awful.tag.getscreen(t)")
+
+ -- A new getter is not required
+
+ t = t or ascreen.focused().selected_tag
+ local prop = tag.getproperty(t, "screen")
+ return prop and prop.index
+end
+
+--- Return a table with all visible tags
+-- @deprecated awful.tag.selectedlist
+-- @param s Screen.
+-- @return A table with all selected tags.
+-- @see screen.selected_tags
+function tag.selectedlist(s)
+ util.deprecate("Use s.selected_tags instead of awful.tag.selectedlist")
+
+ s = get_screen(s or ascreen.focused())
+
+ return s.selected_tags
+end
+
+--- Return only the first visible tag.
+-- @deprecated awful.tag.selected
+-- @param s Screen.
+-- @see screen.selected_tag
+function tag.selected(s)
+ util.deprecate("Use s.selected_tag instead of awful.tag.selected")
+
+ s = get_screen(s or ascreen.focused())
+
+ return s.selected_tag
+end
+
+--- The default master width factor
+--
+-- @beautiful beautiful.master_width_factor
+-- @param number (default: 0.5)
+-- @see master_width_factor
+-- @see gap
+
+--- The tag master width factor.
+--
+-- The master width factor is one of the 5 main properties used to configure
+-- the `layout`. Each layout interpret (or ignore) this property differenly.
+--
+-- See the layout suit documentation for information about how the master width
+-- factor is used.
+--
+-- **Signal:**
+--
+-- * *property::mwfact* (deprecated)
+-- * *property::master_width_factor*
+--
+-- @property master_width_factor
+-- @param number Between 0 and 1
+-- @see master_count
+-- @see column_count
+-- @see master_fill_policy
+-- @see gap
+
+function tag.object.set_master_width_factor(t, mwfact)
+ if mwfact >= 0 and mwfact <= 1 then
+ tag.setproperty(t, "mwfact", mwfact)
+ tag.setproperty(t, "master_width_factor", mwfact)
+ end
+end
+
+function tag.object.get_master_width_factor(t)
+ return tag.getproperty(t, "master_width_factor")
+ or beautiful.master_width_factor
+ or defaults.master_width_factor
+end
+
+--- Set master width factor.
+-- @deprecated awful.tag.setmwfact
+-- @see master_fill_policy
+-- @see master_width_factor
+-- @param mwfact Master width factor.
+-- @param t The tag to modify, if null tag.selected() is used.
+function tag.setmwfact(mwfact, t)
+ util.deprecate("Use t.master_width_factor = mwfact instead of awful.tag.setmwfact")
+
+ tag.object.get_master_width_factor(t or ascreen.focused().selected_tag, mwfact)
+end
+
+--- Increase master width factor.
+-- @function awful.tag.incmwfact
+-- @see master_width_factor
+-- @param add Value to add to master width factor.
+-- @param t The tag to modify, if null tag.selected() is used.
+function tag.incmwfact(add, t)
+ t = t or t or ascreen.focused().selected_tag
+ tag.object.set_master_width_factor(t, tag.object.get_master_width_factor(t) + add)
+end
+
+--- Get master width factor.
+-- @deprecated awful.tag.getmwfact
+-- @see master_width_factor
+-- @see master_fill_policy
+-- @param[opt] t The tag.
+function tag.getmwfact(t)
+ util.deprecate("Use t.master_width_factor instead of awful.tag.getmwfact")
+
+ return tag.object.get_master_width_factor(t or ascreen.focused().selected_tag)
+end
+
+--- An ordered list of layouts.
+-- `awful.tag.layout` Is usually defined in `rc.lua`. It store the list of
+-- layouts used when selecting the previous and next layouts. This is the
+-- default:
+--
+-- -- Table of layouts to cover with awful.layout.inc, order matters.
+-- awful.layout.layouts = {
+-- awful.layout.suit.floating,
+-- awful.layout.suit.tile,
+-- awful.layout.suit.tile.left,
+-- awful.layout.suit.tile.bottom,
+-- awful.layout.suit.tile.top,
+-- awful.layout.suit.fair,
+-- awful.layout.suit.fair.horizontal,
+-- awful.layout.suit.spiral,
+-- awful.layout.suit.spiral.dwindle,
+-- awful.layout.suit.max,
+-- awful.layout.suit.max.fullscreen,
+-- awful.layout.suit.magnifier,
+-- awful.layout.suit.corner.nw,
+-- -- awful.layout.suit.corner.ne,
+-- -- awful.layout.suit.corner.sw,
+-- -- awful.layout.suit.corner.se,
+-- }
+--
+-- @field awful.tag.layouts
+
+--- The tag client layout.
+--
+-- This property hold the layout. A layout can be either stateless or stateful.
+-- Stateless layouts are used by default by Awesome. They tile clients without
+-- any other overhead. They take an ordered list of clients and place them on
+-- the screen. Stateful layouts create an object instance for each tags and
+-- can store variables and metadata. Because of this, they are able to change
+-- over time and be serialized (saved).
+--
+-- Both types of layouts have valid usage scenarios.
+--
+-- **Stateless layouts:**
+--
+-- These layouts are stored in `awful.layout.suit`. They expose a table with 2
+-- fields:
+--
+-- * **name** (*string*): The layout name. This should be unique.
+-- * **arrange** (*function*): The function called when the clients need to be
+-- placed. The only parameter is a table or arguments returned by
+-- `awful.layout.parameters`
+--
+-- **Stateful layouts:**
+--
+-- The stateful layouts API is the same as stateless, but they are a function
+-- returining a layout instead of a layout itself. They also should have an
+-- `is_dynamic = true` property. If they don't, `awful.tag` will create a new
+-- instance everytime the layout is set. If they do, the instance will be
+-- cached and re-used.
+--
+-- **Signal:**
+--
+-- * *property::layout*
+--
+-- @property layout
+-- @see awful.tag.layouts
+-- @tparam layout|function layout A layout table or a constructor function
+-- @return The layout
+
+function tag.object.set_layout(t, layout)
+ -- Check if the signature match a stateful layout
+ if type(layout) == "function" or (
+ type(layout) == "table"
+ and getmetatable(layout)
+ and getmetatable(layout).__call
+ ) then
+ if not t.dynamic_layout_cache then
+ t.dynamic_layout_cache = {}
+ end
+
+ local instance = t.dynamic_layout_cache[layout] or layout(t)
+
+ -- Always make sure the layout is notified it is enabled
+ if tag.getproperty(t, "screen").selected_tag == t and instance.wake_up then
+ instance:wake_up()
+ end
+
+ -- Avoid creating the same layout twice, use layout:reset() to reset
+ if instance.is_dynamic then
+ t.dynamic_layout_cache[layout] = instance
+ end
+
+ layout = instance
+ end
+
+ tag.setproperty(t, "layout", layout)
+
+ return layout
+end
+
+--- Set layout.
+-- @deprecated awful.tag.setlayout
+-- @see layout
+-- @param layout a layout table or a constructor function
+-- @param t The tag to modify
+-- @return The layout
+function tag.setlayout(layout, t)
+ util.deprecate("Use t.layout = layout instead of awful.tag.setlayout")
+
+ return tag.object.set_layout(t, layout)
+end
+
+--- Define if the tag must be deleted when the last client is untagged.
+--
+-- This is useful to create "throw-away" tags for operation like 50/50
+-- side-by-side views.
+--
+-- local t = awful.tag.add("Temporary", {
+-- screen = client.focus.screen,
+-- volatile = true,
+-- clients = {
+-- client.focus,
+-- awful.client.focus.history.get(client.focus.screen, 1)
+-- }
+-- }
+--
+-- **Signal:**
+--
+-- * *property::volatile*
+--
+-- @property volatile
+-- @param boolean
+
+-- Volatile accessors are implicit
+
+--- Set if the tag must be deleted when the last client is untagged
+-- @deprecated awful.tag.setvolatile
+-- @see volatile
+-- @tparam boolean volatile If the tag must be deleted when the last client is untagged
+-- @param t The tag to modify, if null tag.selected() is used.
+function tag.setvolatile(volatile, t)
+ util.deprecate("Use t.volatile = volatile instead of awful.tag.setvolatile")
+
+ tag.setproperty(t, "volatile", volatile)
+end
+
+--- Get if the tag must be deleted when the last client closes
+-- @deprecated awful.tag.getvolatile
+-- @see volatile
+-- @param t The tag to modify, if null tag.selected() is used.
+-- @treturn boolean If the tag will be deleted when the last client is untagged
+function tag.getvolatile(t)
+ util.deprecate("Use t.volatile instead of awful.tag.getvolatile")
+
+ return tag.getproperty(t, "volatile") or false
+end
+
+--- The default gap.
+--
+-- @beautiful beautiful.useless_gap
+-- @param number (default: 0)
+-- @see gap
+-- @see gap_single_client
+
+--- The gap (spacing, also called `useless_gap`) between clients.
+--
+-- This property allow to waste space on the screen in the name of style,
+-- unicorns and readability.
+--
+-- **Signal:**
+--
+-- * *property::useless_gap*
+--
+-- @property gap
+-- @param number The value has to be greater than zero.
+-- @see gap_single_client
+
+function tag.object.set_gap(t, useless_gap)
+ if useless_gap >= 0 then
+ tag.setproperty(t, "useless_gap", useless_gap)
+ end
+end
+
+function tag.object.get_gap(t)
+ return tag.getproperty(t, "useless_gap")
+ or beautiful.useless_gap
+ or defaults.gap
+end
+
+--- Set the spacing between clients
+-- @deprecated awful.tag.setgap
+-- @see gap
+-- @param useless_gap The spacing between clients
+-- @param t The tag to modify, if null tag.selected() is used.
+function tag.setgap(useless_gap, t)
+ util.deprecate("Use t.gap = useless_gap instead of awful.tag.setgap")
+
+ tag.object.set_gap(t or ascreen.focused().selected_tag, useless_gap)
+end
+
+--- Increase the spacing between clients
+-- @function awful.tag.incgap
+-- @see gap
+-- @param add Value to add to the spacing between clients
+-- @param t The tag to modify, if null tag.selected() is used.
+function tag.incgap(add, t)
+ t = t or t or ascreen.focused().selected_tag
+ tag.object.set_gap(t, tag.object.get_gap(t) + add)
+end
+
+--- Enable gaps for a single client.
+--
+-- @beautiful beautiful.gap_single_client
+-- @param boolean (default: true)
+-- @see gap
+-- @see gap_single_client
+
+--- Enable gaps for a single client.
+--
+-- **Signal:**
+--
+-- * *property::gap\_single\_client*
+--
+-- @property gap_single_client
+-- @param boolean Enable gaps for a single client
+
+function tag.object.set_gap_single_client(t, gap_single_client)
+ tag.setproperty(t, "gap_single_client", gap_single_client == true)
+end
+
+function tag.object.get_gap_single_client(t)
+ local val = tag.getproperty(t, "gap_single_client")
+ if val ~= nil then
+ return val
+ end
+ val = beautiful.gap_single_client
+ if val ~= nil then
+ return val
+ end
+ return defaults.gap_single_client
+end
+
+--- Get the spacing between clients.
+-- @deprecated awful.tag.getgap
+-- @see gap
+-- @tparam[opt=tag.selected()] tag t The tag.
+-- @tparam[opt] int numclients Number of (tiled) clients. Passing this will
+-- return 0 for a single client. You can override this function to change
+-- this behavior.
+function tag.getgap(t, numclients)
+ util.deprecate("Use t.gap instead of awful.tag.getgap")
+
+ if numclients == 1 then
+ return 0
+ end
+
+ return tag.object.get_gap(t or ascreen.focused().selected_tag)
+end
+
+--- The default fill policy.
+--
+-- ** Possible values**:
+--
+-- * *expand*: Take all the space
+-- * *master_width_factor*: Only take the ratio defined by the
+-- `master_width_factor`
+--
+-- @beautiful beautiful.master_fill_policy
+-- @param string (default: "expand")
+-- @see master_fill_policy
+
+--- Set size fill policy for the master client(s).
+--
+-- ** Possible values**:
+--
+-- * *expand*: Take all the space
+-- * *master_width_factor*: Only take the ratio defined by the
+-- `master_width_factor`
+--
+-- **Signal:**
+--
+-- * *property::master_fill_policy*
+--
+-- @property master_fill_policy
+-- @param string "expand" or "master_width_factor"
+
+function tag.object.get_master_fill_policy(t)
+ return tag.getproperty(t, "master_fill_policy")
+ or beautiful.master_fill_policy
+ or defaults.master_fill_policy
+end
+
+--- Set size fill policy for the master client(s)
+-- @deprecated awful.tag.setmfpol
+-- @see master_fill_policy
+-- @tparam string policy Can be set to
+-- "expand" (fill all the available workarea) or
+-- "master_width_factor" (fill only an area inside the master width factor)
+-- @tparam[opt=tag.selected()] tag t The tag to modify
+function tag.setmfpol(policy, t)
+ util.deprecate("Use t.master_fill_policy = policy instead of awful.tag.setmfpol")
+
+ t = t or ascreen.focused().selected_tag
+ tag.setproperty(t, "master_fill_policy", policy)
+end
+
+--- Toggle size fill policy for the master client(s)
+-- between "expand" and "master_width_factor".
+-- @function awful.tag.togglemfpol
+-- @see master_fill_policy
+-- @tparam tag t The tag to modify, if null tag.selected() is used.
+function tag.togglemfpol(t)
+ t = t or ascreen.focused().selected_tag
+
+ if tag.getmfpol(t) == "expand" then
+ tag.setproperty(t, "master_fill_policy", "master_width_factor")
+ else
+ tag.setproperty(t, "master_fill_policy", "expand")
+ end
+end
+
+--- Get size fill policy for the master client(s)
+-- @deprecated awful.tag.getmfpol
+-- @see master_fill_policy
+-- @tparam[opt=tag.selected()] tag t The tag
+-- @treturn string Possible values are
+-- "expand" (fill all the available workarea, default one) or
+-- "master_width_factor" (fill only an area inside the master width factor)
+function tag.getmfpol(t)
+ util.deprecate("Use t.master_fill_policy instead of awful.tag.getmfpol")
+
+ t = t or ascreen.focused().selected_tag
+ return tag.getproperty(t, "master_fill_policy")
+ or beautiful.master_fill_policy
+ or defaults.master_fill_policy
+end
+
+--- The default number of master windows.
+--
+-- @beautiful beautiful.master_count
+-- @param integer (default: 1)
+-- @see master_count
+
+--- Set the number of master windows.
+--
+-- **Signal:**
+--
+-- * *property::nmaster* (deprecated)
+-- * *property::master_count* (deprecated)
+--
+-- @property master_count
+-- @param integer nmaster Only positive values are accepted
+
+function tag.object.set_master_count(t, nmaster)
+ if nmaster >= 0 then
+ tag.setproperty(t, "nmaster", nmaster)
+ tag.setproperty(t, "master_count", nmaster)
+ end
+end
+
+function tag.object.get_master_count(t)
+ return tag.getproperty(t, "master_count")
+ or beautiful.master_count
+ or defaults.master_count
+end
+
+---
+-- @deprecated awful.tag.setnmaster
+-- @see master_count
+-- @param nmaster The number of master windows.
+-- @param[opt] t The tag.
+function tag.setnmaster(nmaster, t)
+ util.deprecate("Use t.master_count = nmaster instead of awful.tag.setnmaster")
+
+ tag.object.set_master_count(t or ascreen.focused().selected_tag, nmaster)
+end
+
+--- Get the number of master windows.
+-- @deprecated awful.tag.getnmaster
+-- @see master_count
+-- @param[opt] t The tag.
+function tag.getnmaster(t)
+ util.deprecate("Use t.master_count instead of awful.tag.setnmaster")
+
+ t = t or ascreen.focused().selected_tag
+ return tag.getproperty(t, "master_count") or 1
+end
+
+--- Increase the number of master windows.
+-- @function awful.tag.incnmaster
+-- @see master_count
+-- @param add Value to add to number of master windows.
+-- @param[opt] t The tag to modify, if null tag.selected() is used.
+-- @tparam[opt=false] boolean sensible Limit nmaster based on the number of
+-- visible tiled windows?
+function tag.incnmaster(add, t, sensible)
+ t = t or ascreen.focused().selected_tag
+
+ if sensible then
+ local screen = get_screen(tag.getproperty(t, "screen"))
+ local ntiled = #screen.tiled_clients
+
+ local nmaster = tag.object.get_master_count(t)
+ if nmaster > ntiled then
+ nmaster = ntiled
+ end
+
+ local newnmaster = nmaster + add
+ if newnmaster > ntiled then
+ newnmaster = ntiled
+ end
+ tag.object.set_master_count(t, newnmaster)
+ else
+ tag.object.set_master_count(t, tag.object.get_master_count(t) + add)
+ end
+end
+
+--- Set the tag icon.
+--
+-- **Signal:**
+--
+-- * *property::icon*
+--
+-- @property icon
+-- @tparam path|surface icon The icon
+
+-- accessors are implicit.
+
+--- Set the tag icon
+-- @deprecated awful.tag.seticon
+-- @see icon
+-- @param icon the icon to set, either path or image object
+-- @param _tag the tag
+function tag.seticon(icon, _tag)
+ util.deprecate("Use t.icon = icon instead of awful.tag.seticon")
+
+ _tag = _tag or ascreen.focused().selected_tag
+ tag.setproperty(_tag, "icon", icon)
+end
+
+--- Get the tag icon
+-- @deprecated awful.tag.geticon
+-- @see icon
+-- @param _tag the tag
+function tag.geticon(_tag)
+ util.deprecate("Use t.icon instead of awful.tag.geticon")
+
+ _tag = _tag or ascreen.focused().selected_tag
+ return tag.getproperty(_tag, "icon")
+end
+
+--- The default number of columns.
+--
+-- @beautiful beautiful.column_count
+-- @param integer (default: 1)
+-- @see column_count
+
+--- Set the number of columns.
+--
+-- **Signal:**
+--
+-- * *property::ncol* (deprecated)
+-- * *property::column_count*
+--
+-- @property column_count
+-- @tparam integer ncol Has to be greater than 1
+
+function tag.object.set_column_count(t, ncol)
+ if ncol >= 1 then
+ tag.setproperty(t, "ncol", ncol)
+ tag.setproperty(t, "column_count", ncol)
+ end
+end
+
+function tag.object.get_column_count(t)
+ return tag.getproperty(t, "column_count")
+ or beautiful.column_count
+ or defaults.column_count
+end
+
+--- Set number of column windows.
+-- @deprecated awful.tag.setncol
+-- @see column_count
+-- @param ncol The number of column.
+-- @param t The tag to modify, if null tag.selected() is used.
+function tag.setncol(ncol, t)
+ util.deprecate("Use t.column_count = new_index instead of awful.tag.setncol")
+
+ t = t or ascreen.focused().selected_tag
+ if ncol >= 1 then
+ tag.setproperty(t, "ncol", ncol)
+ tag.setproperty(t, "column_count", ncol)
+ end
+end
+
+--- Get number of column windows.
+-- @deprecated awful.tag.getncol
+-- @see column_count
+-- @param[opt] t The tag.
+function tag.getncol(t)
+ util.deprecate("Use t.column_count instead of awful.tag.getncol")
+
+ t = t or ascreen.focused().selected_tag
+ return tag.getproperty(t, "column_count") or 1
+end
+
+--- Increase number of column windows.
+-- @function awful.tag.incncol
+-- @param add Value to add to number of column windows.
+-- @param[opt] t The tag to modify, if null tag.selected() is used.
+-- @tparam[opt=false] boolean sensible Limit column_count based on the number
+-- of visible tiled windows?
+function tag.incncol(add, t, sensible)
+ t = t or ascreen.focused().selected_tag
+
+ if sensible then
+ local screen = get_screen(tag.getproperty(t, "screen"))
+ local ntiled = #screen.tiled_clients
+ local nmaster = tag.object.get_master_count(t)
+ local nsecondary = ntiled - nmaster
+
+ local ncol = tag.object.get_column_count(t)
+ if ncol > nsecondary then
+ ncol = nsecondary
+ end
+
+ local newncol = ncol + add
+ if newncol > nsecondary then
+ newncol = nsecondary
+ end
+
+ tag.object.set_column_count(t, newncol)
+ else
+ tag.object.set_column_count(t, tag.object.get_column_count(t) + add)
+ end
+end
+
+--- View no tag.
+-- @function awful.tag.viewnone
+-- @tparam[opt] int|screen screen The screen.
+function tag.viewnone(screen)
+ screen = screen or ascreen.focused()
+ local tags = screen.tags
+ for _, t in pairs(tags) do
+ t.selected = false
+ end
+end
+
+--- View a tag by its taglist index.
+--
+-- This is equivalent to `screen.tags[i]:view_only()`
+-- @function awful.tag.viewidx
+-- @see screen.tags
+-- @param i The **relative** index to see.
+-- @param[opt] screen The screen.
+function tag.viewidx(i, screen)
+ screen = get_screen(screen or ascreen.focused())
+ local tags = screen.tags
+ local showntags = {}
+ for _, t in ipairs(tags) do
+ if not tag.getproperty(t, "hide") then
+ table.insert(showntags, t)
+ end
+ end
+ local sel = screen.selected_tag
+ tag.viewnone(screen)
+ for k, t in ipairs(showntags) do
+ if t == sel then
+ showntags[util.cycle(#showntags, k + i)].selected = true
+ end
+ end
+ screen:emit_signal("tag::history::update")
+end
+
+--- Get a tag's index in the gettags() table.
+-- @deprecated awful.tag.getidx
+-- @see index
+-- @param query_tag The tag object to find. [selected()]
+-- @return The index of the tag, nil if the tag is not found.
+function tag.getidx(query_tag)
+ util.deprecate("Use t.index instead of awful.tag.getidx")
+
+ return tag.object.get_index(query_tag or ascreen.focused().selected_tag)
+end
+
+--- View next tag. This is the same as tag.viewidx(1).
+-- @function awful.tag.viewnext
+-- @param screen The screen.
+function tag.viewnext(screen)
+ return tag.viewidx(1, screen)
+end
+
+--- View previous tag. This is the same a tag.viewidx(-1).
+-- @function awful.tag.viewprev
+-- @param screen The screen.
+function tag.viewprev(screen)
+ return tag.viewidx(-1, screen)
+end
+
+--- View only a tag.
+-- @function tag.view_only
+-- @see selected
+function tag.object.view_only(self)
+ local tags = self.screen.tags
+ -- First, untag everyone except the viewed tag.
+ for _, _tag in pairs(tags) do
+ if _tag ~= self then
+ _tag.selected = false
+ end
+ end
+ -- Then, set this one to selected.
+ -- We need to do that in 2 operations so we avoid flickering and several tag
+ -- selected at the same time.
+ self.selected = true
+ capi.screen[self.screen]:emit_signal("tag::history::update")
+end
+
+--- View only a tag.
+-- @deprecated awful.tag.viewonly
+-- @see tag.view_only
+-- @param t The tag object.
+function tag.viewonly(t)
+ util.deprecate("Use t:view_only() instead of awful.tag.viewonly")
+
+ tag.object.view_only(t)
+end
+
+--- View only a set of tags.
+-- @function awful.tag.viewmore
+-- @param tags A table with tags to view only.
+-- @param[opt] screen The screen of the tags.
+function tag.viewmore(tags, screen)
+ screen = get_screen(screen or ascreen.focused())
+ local screen_tags = screen.tags
+ for _, _tag in ipairs(screen_tags) do
+ if not util.table.hasitem(tags, _tag) then
+ _tag.selected = false
+ end
+ end
+ for _, _tag in ipairs(tags) do
+ _tag.selected = true
+ end
+ screen:emit_signal("tag::history::update")
+end
+
+--- Toggle selection of a tag
+-- @function awful.tag.viewtoggle
+-- @see selected
+-- @tparam tag t Tag to be toggled
+function tag.viewtoggle(t)
+ t.selected = not t.selected
+ capi.screen[tag.getproperty(t, "screen")]:emit_signal("tag::history::update")
+end
+
+--- Get tag data table.
+--
+-- Do not use.
+--
+-- @deprecated awful.tag.getdata
+-- @tparam tag _tag The tag.
+-- @return The data table.
+function tag.getdata(_tag)
+ return _tag.data.awful_tag_properties
+end
+
+--- Get a tag property.
+--
+-- Use `_tag.prop` directly.
+--
+-- @deprecated awful.tag.getproperty
+-- @tparam tag _tag The tag.
+-- @tparam string prop The property name.
+-- @return The property.
+function tag.getproperty(_tag, prop)
+ if not _tag then return end -- FIXME: Turn this into an error?
+ if _tag.data.awful_tag_properties then
+ return _tag.data.awful_tag_properties[prop]
+ end
+end
+
+--- Set a tag property.
+-- This properties are internal to awful. Some are used to draw taglist, or to
+-- handle layout, etc.
+--
+-- Use `_tag.prop = value`
+--
+-- @deprecated awful.tag.setproperty
+-- @param _tag The tag.
+-- @param prop The property name.
+-- @param value The value.
+function tag.setproperty(_tag, prop, value)
+ if not _tag.data.awful_tag_properties then
+ _tag.data.awful_tag_properties = {}
+ end
+
+ if _tag.data.awful_tag_properties[prop] ~= value then
+ _tag.data.awful_tag_properties[prop] = value
+ _tag:emit_signal("property::" .. prop)
+ end
+end
+
+--- Tag a client with the set of current tags.
+-- @deprecated awful.tag.withcurrent
+-- @param c The client to tag.
+function tag.withcurrent(c)
+ util.deprecate("Use c:to_selected_tags() instead of awful.tag.selectedlist")
+
+ -- It can't use c:to_selected_tags() because awful.tag is loaded before
+ -- awful.client
+
+ local tags = {}
+ for _, t in ipairs(c:tags()) do
+ if get_screen(tag.getproperty(t, "screen")) == get_screen(c.screen) then
+ table.insert(tags, t)
+ end
+ end
+ if #tags == 0 then
+ tags = c.screen.selected_tags
+ end
+ if #tags == 0 then
+ tags = c.screen.tags
+ end
+ if #tags ~= 0 then
+ c:tags(tags)
+ end
+end
+
+local function attached_connect_signal_screen(screen, sig, func)
+ screen = get_screen(screen)
+ capi.tag.connect_signal(sig, function(_tag)
+ if get_screen(tag.getproperty(_tag, "screen")) == screen then
+ func(_tag)
+ end
+ end)
+end
+
+--- Add a signal to all attached tags and all tags that will be attached in the
+-- future. When a tag is detached from the screen, its signal is removed.
+--
+-- @function awful.tag.attached_connect_signal
+-- @screen The screen concerned, or all if nil.
+-- @tparam[opt] string Signal
+-- @tparam[opt] function Callback
+function tag.attached_connect_signal(screen, ...)
+ if screen then
+ attached_connect_signal_screen(screen, ...)
+ else
+ capi.tag.connect_signal(...)
+ end
+end
+
+-- Register standard signals.
+capi.client.connect_signal("property::screen", function(c)
+ -- First, the delayed timer is necessary to avoid a race condition with
+ -- awful.rules. It is also messing up the tags before the user have a chance
+ -- to set them manually.
+ timer.delayed_call(function()
+ local tags, new_tags = c:tags(), {}
+
+ for _, t in ipairs(tags) do
+ if t.screen == c.screen then
+ table.insert(new_tags, t)
+ end
+ end
+
+ if #new_tags == 0 then
+ c:emit_signal("request::tag", nil, {reason="screen"})
+ elseif #new_tags < #tags then
+ c:tags(new_tags)
+ end
+ end)
+end)
+
+-- Keep track of the number of urgent clients.
+local function update_urgent(t, modif)
+ local count = tag.getproperty(t, "urgent_count") or 0
+ count = (count + modif) >= 0 and (count + modif) or 0
+ tag.setproperty(t, "urgent" , count > 0)
+ tag.setproperty(t, "urgent_count", count )
+end
+
+-- Update the urgent counter when a client is tagged.
+local function client_tagged(c, t)
+ if c.urgent then
+ update_urgent(t, 1)
+ end
+end
+
+-- Update the urgent counter when a client is untagged.
+local function client_untagged(c, t)
+ if c.urgent then
+ update_urgent(t, -1)
+ end
+
+ if #t:clients() == 0 and tag.getproperty(t, "volatile") then
+ tag.object.delete(t)
+ end
+end
+
+-- Count the urgent clients.
+local function urgent_callback(c)
+ for _,t in ipairs(c:tags()) do
+ update_urgent(t, c.urgent and 1 or -1)
+ end
+end
+
+capi.client.connect_signal("property::urgent", urgent_callback)
+capi.client.connect_signal("untagged", client_untagged)
+capi.client.connect_signal("tagged", client_tagged)
+capi.tag.connect_signal("request::select", tag.object.view_only)
+
+--- True when a tagged client is urgent
+-- @signal property::urgent
+-- @see client.urgent
+
+--- The number of urgent tagged clients
+-- @signal property::urgent_count
+-- @see client.urgent
+
+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
+
+ if t.data.awful_tag_properties then
+ t.data.awful_tag_properties.screen = nil
+ end
+ end
+end)
+
+function tag.mt:__call(...)
+ return tag.new(...)
+end
+
+-- Extend the luaobject
+-- `awful.tag.setproperty` currently handle calling the setter method itself
+-- while `awful.tag.getproperty`.
+object.properties(capi.tag, {
+ getter_class = tag.object,
+ setter_class = tag.object,
+ getter_fallback = tag.getproperty,
+ setter_fallback = tag.setproperty,
+})
+
+return setmetatable(tag, tag.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/titlebar.lua b/lib/awful/titlebar.lua
new file mode 100644
index 0000000..c055349
--- /dev/null
+++ b/lib/awful/titlebar.lua
@@ -0,0 +1,509 @@
+---------------------------------------------------------------------------
+--- Titlebars for awful.
+--
+-- @author Uli Schlachter
+-- @copyright 2012 Uli Schlachter
+-- @module awful.titlebar
+---------------------------------------------------------------------------
+
+local error = error
+local type = type
+local util = require("awful.util")
+local abutton = require("awful.button")
+local aclient = require("awful.client")
+local atooltip = require("awful.tooltip")
+local beautiful = require("beautiful")
+local drawable = require("wibox.drawable")
+local imagebox = require("wibox.widget.imagebox")
+local textbox = require("wibox.widget.textbox")
+local base = require("wibox.widget.base")
+local capi = {
+ client = client
+}
+local titlebar = {
+ widget = {}
+}
+
+--- The titlebar foreground (text) color.
+-- @beautiful beautiful.titlebar_fg_normal
+-- @param color
+-- @see gears.color
+
+--- The titlebar background color.
+-- @beautiful beautiful.titlebar_bg_normal
+-- @param color
+-- @see gears.color
+
+--- The titlebar background image image.
+-- @beautiful beautiful.titlebar_bgimage_normal
+-- @param surface
+-- @see gears.surface
+
+--- The titlebar foreground (text) color.
+-- @beautiful beautiful.titlebar_fg
+-- @param color
+-- @see gears.color
+
+--- The titlebar background color.
+-- @beautiful beautiful.titlebar_bg
+-- @param color
+-- @see gears.color
+
+--- The titlebar background image image.
+-- @beautiful beautiful.titlebar_bgimage
+-- @param surface
+-- @see gears.surface
+
+--- The focused titlebar foreground (text) color.
+-- @beautiful beautiful.titlebar_fg_focus
+-- @param color
+-- @see gears.color
+
+--- The focused titlebar background color.
+-- @beautiful beautiful.titlebar_bg_focus
+-- @param color
+-- @see gears.color
+
+--- The focused titlebar background image image.
+-- @beautiful beautiful.titlebar_bgimage_focus
+-- @param surface
+-- @see gears.surface
+
+--- floating_button_normal.
+-- @beautiful beautiful.titlebar_floating_button_normal
+-- @param surface
+-- @see gears.surface
+
+--- maximized_button_normal.
+-- @beautiful beautiful.titlebar_maximized_button_normal
+-- @param surface
+-- @see gears.surface
+
+--- minimize_button_normal
+-- @beautiful beautiful.titlebar_minimize_button_normal
+-- @param surface
+-- @see gears.surface
+
+--- close_button_normal.
+-- @beautiful beautiful.titlebar_close_button_normal
+-- @param surface
+-- @see gears.surface
+
+--- ontop_button_normal.
+-- @beautiful beautiful.titlebar_ontop_button_normal
+-- @param surface
+-- @see gears.surface
+
+--- sticky_button_normal.
+-- @beautiful beautiful.titlebar_sticky_button_normal
+-- @param surface
+-- @see gears.surface
+
+--- floating_button_focus.
+-- @beautiful beautiful.titlebar_floating_button_focus
+-- @param surface
+-- @see gears.surface
+
+--- maximized_button_focus.
+-- @beautiful beautiful.titlebar_maximized_button_focus
+-- @param surface
+-- @see gears.surface
+
+--- minimize_button_focus.
+-- @beautiful beautiful.titlebar_minimize_button_focus
+-- @param surface
+-- @see gears.surface
+
+--- close_button_focus.
+-- @beautiful beautiful.titlebar_close_button_focus
+-- @param surface
+-- @see gears.surface
+
+--- ontop_button_focus.
+-- @beautiful beautiful.titlebar_ontop_button_focus
+-- @param surface
+-- @see gears.surface
+
+--- sticky_button_focus.
+-- @beautiful beautiful.titlebar_sticky_button_focus
+-- @param surface
+-- @see gears.surface
+
+--- floating_button_normal_active.
+-- @beautiful beautiful.titlebar_floating_button_normal_active
+-- @param surface
+-- @see gears.surface
+
+--- maximized_button_normal_active.
+-- @beautiful beautiful.titlebar_maximized_button_normal_active
+-- @param surface
+-- @see gears.surface
+
+--- ontop_button_normal_active.
+-- @beautiful beautiful.titlebar_ontop_button_normal_active
+-- @param surface
+-- @see gears.surface
+
+--- sticky_button_normal_active.
+-- @beautiful beautiful.titlebar_sticky_button_normal_active
+-- @param surface
+-- @see gears.surface
+
+--- floating_button_focus_active.
+-- @beautiful beautiful.titlebar_floating_button_focus_active
+-- @param surface
+-- @see gears.surface
+
+--- maximized_button_focus_active.
+-- @beautiful beautiful.titlebar_maximized_button_focus_active
+-- @param surface
+-- @see gears.surface
+
+--- ontop_button_focus_active.
+-- @beautiful beautiful.titlebar_ontop_button_focus_active
+-- @param surface
+-- @see gears.surface
+
+--- sticky_button_focus_active.
+-- @beautiful beautiful.titlebar_sticky_button_focus_active
+-- @param surface
+-- @see gears.surface
+
+--- floating_button_normal_inactive.
+-- @beautiful beautiful.titlebar_floating_button_normal_inactive
+-- @param surface
+-- @see gears.surface
+
+--- maximized_button_normal_inactive.
+-- @beautiful beautiful.titlebar_maximized_button_normal_inactive
+-- @param surface
+-- @see gears.surface
+
+--- ontop_button_normal_inactive.
+-- @beautiful beautiful.titlebar_ontop_button_normal_inactive
+-- @param surface
+-- @see gears.surface
+
+--- sticky_button_normal_inactive.
+-- @beautiful beautiful.titlebar_sticky_button_normal_inactive
+-- @param surface
+-- @see gears.surface
+
+--- floating_button_focus_inactive.
+-- @beautiful beautiful.titlebar_floating_button_focus_inactive
+-- @param surface
+-- @see gears.surface
+
+--- maximized_button_focus_inactive.
+-- @beautiful beautiful.titlebar_maximized_button_focus_inactive
+-- @param surface
+-- @see gears.surface
+
+--- ontop_button_focus_inactive.
+-- @beautiful beautiful.titlebar_ontop_button_focus_inactive
+-- @param surface
+-- @see gears.surface
+
+--- sticky_button_focus_inactive.
+-- @beautiful beautiful.titlebar_sticky_button_focus_inactive
+-- @param surface
+-- @see gears.surface
+
+--- Set a declarative widget hierarchy description.
+-- See [The declarative layout system](../documentation/03-declarative-layout.md.html)
+-- @param args An array containing the widgets disposition
+-- @name setup
+-- @class function
+
+--- Show tooltips when hover on titlebar buttons (defaults to 'true')
+titlebar.enable_tooltip = true
+
+local all_titlebars = setmetatable({}, { __mode = 'k' })
+
+-- Get a color for a titlebar, this tests many values from the array and the theme
+local function get_color(name, c, args)
+ local suffix = "_normal"
+ if capi.client.focus == c then
+ suffix = "_focus"
+ end
+ local function get(array)
+ return array["titlebar_"..name..suffix] or array["titlebar_"..name] or array[name..suffix] or array[name]
+ end
+ return get(args) or get(beautiful)
+end
+
+local function get_titlebar_function(c, position)
+ if position == "left" then
+ return c.titlebar_left
+ elseif position == "right" then
+ return c.titlebar_right
+ elseif position == "top" then
+ return c.titlebar_top
+ elseif position == "bottom" then
+ return c.titlebar_bottom
+ else
+ error("Invalid titlebar position '" .. position .. "'")
+ end
+end
+
+--- Get a client's titlebar
+-- @class function
+-- @tparam client c The client for which a titlebar is wanted.
+-- @tparam[opt={}] table args A table with extra arguments for the titlebar.
+-- @tparam[opt=font.height*1.5] number args.size The height of the titlebar.
+-- @tparam[opt=top] string args.position" values are `top`,
+-- `left`, `right` and `bottom`.
+-- @tparam[opt=top] string args.bg_normal
+-- @tparam[opt=top] string args.bg_focus
+-- @tparam[opt=top] string args.bgimage_normal
+-- @tparam[opt=top] string args.bgimage_focus
+-- @tparam[opt=top] string args.fg_normal
+-- @tparam[opt=top] string args.fg_focus
+-- @tparam[opt=top] string args.font
+-- @name titlebar
+local function new(c, args)
+ args = args or {}
+ local position = args.position or "top"
+ local size = args.size or util.round(beautiful.get_font_height(args.font) * 1.5)
+ local d = get_titlebar_function(c, position)(c, size)
+
+ -- Make sure that there is never more than one titlebar for any given client
+ local bars = all_titlebars[c]
+ if not bars then
+ bars = {}
+ all_titlebars[c] = bars
+ end
+
+ local ret
+ if not bars[position] then
+ local context = {
+ client = c,
+ position = position
+ }
+ ret = drawable(d, context, "awful.titlebar")
+ ret:_inform_visible(true)
+ local function update_colors()
+ local args_ = bars[position].args
+ ret:set_bg(get_color("bg", c, args_))
+ ret:set_fg(get_color("fg", c, args_))
+ ret:set_bgimage(get_color("bgimage", c, args_))
+ end
+
+ bars[position] = {
+ args = args,
+ drawable = ret,
+ update_colors = update_colors
+ }
+
+ -- Update the colors when focus changes
+ c:connect_signal("focus", update_colors)
+ c:connect_signal("unfocus", update_colors)
+
+ -- Inform the drawable when it becomes invisible
+ c:connect_signal("unmanage", function() ret:_inform_visible(false) end)
+ else
+ bars[position].args = args
+ ret = bars[position].drawable
+ end
+
+ -- Make sure the titlebar has the right colors applied
+ bars[position].update_colors()
+
+ -- Handle declarative/recursive widget container
+ ret.setup = base.widget.setup
+
+ return ret
+end
+
+--- Show a client's titlebar.
+-- @param c The client whose titlebar is modified
+-- @param[opt] position The position of the titlebar. Must be one of "left",
+-- "right", "top", "bottom". Default is "top".
+function titlebar.show(c, position)
+ position = position or "top"
+ local bars = all_titlebars[c]
+ local data = bars and bars[position]
+ local args = data and data.args
+ new(c, args)
+end
+
+--- Hide a client's titlebar.
+-- @param c The client whose titlebar is modified
+-- @param[opt] position The position of the titlebar. Must be one of "left",
+-- "right", "top", "bottom". Default is "top".
+function titlebar.hide(c, position)
+ position = position or "top"
+ get_titlebar_function(c, position)(c, 0)
+end
+
+--- Toggle a client's titlebar, hiding it if it is visible, otherwise showing it.
+-- @param c The client whose titlebar is modified
+-- @param[opt] position The position of the titlebar. Must be one of "left",
+-- "right", "top", "bottom". Default is "top".
+function titlebar.toggle(c, position)
+ position = position or "top"
+ local _, size = get_titlebar_function(c, position)(c)
+ if size == 0 then
+ titlebar.show(c, position)
+ else
+ titlebar.hide(c, position)
+ end
+end
+
+--- Create a new titlewidget. A title widget displays the name of a client.
+-- Please note that this returns a textbox and all of textbox' API is available.
+-- This way, you can e.g. modify the font that is used.
+-- @param c The client for which a titlewidget should be created.
+-- @return The title widget.
+function titlebar.widget.titlewidget(c)
+ local ret = textbox()
+ local function update()
+ ret:set_text(c.name or "<unknown>")
+ end
+ c:connect_signal("property::name", update)
+ update()
+
+ return ret
+end
+
+--- Create a new icon widget. An icon widget displays the icon of a client.
+-- Please note that this returns an imagebox and all of the imagebox' API is
+-- available. This way, you can e.g. disallow resizes.
+-- @param c The client for which an icon widget should be created.
+-- @return The icon widget.
+function titlebar.widget.iconwidget(c)
+ local ret = imagebox()
+ local function update()
+ ret:set_image(c.icon)
+ end
+ c:connect_signal("property::icon", update)
+ update()
+
+ return ret
+end
+
+--- Create a new button widget. A button widget displays an image and reacts to
+-- mouse clicks. Please note that the caller has to make sure that this widget
+-- gets redrawn when needed by calling the returned widget's update() function.
+-- The selector function should return a value describing a state. If the value
+-- is a boolean, either "active" or "inactive" are used. The actual image is
+-- then found in the theme as "titlebar_[name]_button_[normal/focus]_[state]".
+-- If that value does not exist, the focused state is ignored for the next try.
+-- @param c The client for which a button is created.
+-- @tparam string name Name of the button, used for accessing the theme and
+-- in the tooltip.
+-- @param selector A function that selects the image that should be displayed.
+-- @param action Function that is called when the button is clicked.
+-- @return The widget
+function titlebar.widget.button(c, name, selector, action)
+ local ret = imagebox()
+
+ if titlebar.enable_tooltip then
+ ret._private.tooltip = atooltip({ objects = {ret}, delay_show = 1 })
+ ret._private.tooltip:set_text(name)
+ end
+
+ local function update()
+ local img = selector(c)
+ if type(img) ~= "nil" then
+ -- Convert booleans automatically
+ if type(img) == "boolean" then
+ if img then
+ img = "active"
+ else
+ img = "inactive"
+ end
+ end
+ local prefix = "normal"
+ if capi.client.focus == c then
+ prefix = "focus"
+ end
+ if img ~= "" then
+ prefix = prefix .. "_"
+ end
+ -- First try with a prefix based on the client's focus state,
+ -- then try again without that prefix if nothing was found,
+ -- and finally, try a fallback for compatibility with Awesome 3.5 themes
+ local theme = beautiful["titlebar_" .. name .. "_button_" .. prefix .. img]
+ or beautiful["titlebar_" .. name .. "_button_" .. img]
+ or beautiful["titlebar_" .. name .. "_button_" .. prefix .. "_inactive"]
+ if theme then
+ img = theme
+ end
+ end
+ ret:set_image(img)
+ end
+ if action then
+ ret:buttons(abutton({ }, 1, nil, function() action(c, selector(c)) end))
+ end
+
+ ret.update = update
+ update()
+
+ -- We do magic based on whether a client is focused above, so we need to
+ -- connect to the corresponding signal here.
+ c:connect_signal("focus", update)
+ c:connect_signal("unfocus", update)
+
+ return ret
+end
+
+--- Create a new float button for a client.
+-- @param c The client for which the button is wanted.
+function titlebar.widget.floatingbutton(c)
+ local widget = titlebar.widget.button(c, "floating", aclient.object.get_floating, aclient.floating.toggle)
+ c:connect_signal("property::floating", widget.update)
+ return widget
+end
+
+--- Create a new maximize button for a client.
+-- @param c The client for which the button is wanted.
+function titlebar.widget.maximizedbutton(c)
+ local widget = titlebar.widget.button(c, "maximized", function(cl)
+ return cl.maximized_horizontal or cl.maximized_vertical
+ end, function(cl, state)
+ cl.maximized_horizontal = not state
+ cl.maximized_vertical = not state
+ end)
+ c:connect_signal("property::maximized_vertical", widget.update)
+ c:connect_signal("property::maximized_horizontal", widget.update)
+ return widget
+end
+
+--- Create a new minimize button for a client.
+-- @param c The client for which the button is wanted.
+function titlebar.widget.minimizebutton(c)
+ local widget = titlebar.widget.button(c, "minimize", function() return "" end, function(cl) cl.minimized = not cl.minimized end)
+ c:connect_signal("property::minimized", widget.update)
+ return widget
+end
+
+--- Create a new closing button for a client.
+-- @param c The client for which the button is wanted.
+function titlebar.widget.closebutton(c)
+ return titlebar.widget.button(c, "close", function() return "" end, function(cl) cl:kill() end)
+end
+
+--- Create a new ontop button for a client.
+-- @param c The client for which the button is wanted.
+function titlebar.widget.ontopbutton(c)
+ local widget = titlebar.widget.button(c, "ontop", function(cl) return cl.ontop end, function(cl, state) cl.ontop = not state end)
+ c:connect_signal("property::ontop", widget.update)
+ return widget
+end
+
+--- Create a new sticky button for a client.
+-- @param c The client for which the button is wanted.
+function titlebar.widget.stickybutton(c)
+ local widget = titlebar.widget.button(c, "sticky", function(cl) return cl.sticky end, function(cl, state) cl.sticky = not state end)
+ c:connect_signal("property::sticky", widget.update)
+ return widget
+end
+
+client.connect_signal("unmanage", function(c)
+ all_titlebars[c] = nil
+end)
+
+return setmetatable(titlebar, { __call = function(_, ...) return new(...) end})
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/tooltip.lua b/lib/awful/tooltip.lua
new file mode 100644
index 0000000..cd151c4
--- /dev/null
+++ b/lib/awful/tooltip.lua
@@ -0,0 +1,616 @@
+-------------------------------------------------------------------------
+--- Tooltip module for awesome objects.
+--
+-- A tooltip is a small hint displayed when the mouse cursor
+-- hovers a specific item.
+-- In awesome, a tooltip can be linked with almost any
+-- object having a `:connect_signal()` method and receiving
+-- `mouse::enter` and `mouse::leave` signals.
+--
+-- How to create a tooltip?
+-- ---
+--
+-- myclock = wibox.widget.textclock({}, "%T", 1)
+-- myclock_t = awful.tooltip({
+-- objects = { myclock },
+-- timer_function = function()
+-- return os.date("Today is %A %B %d %Y\nThe time is %T")
+-- end,
+-- })
+--
+-- How to add the same tooltip to multiple objects?
+-- ---
+--
+-- myclock_t:add_to_object(obj1)
+-- myclock_t:add_to_object(obj2)
+--
+-- Now the same tooltip is attached to `myclock`, `obj1`, `obj2`.
+--
+-- How to remove a tooltip from several objects?
+-- ---
+--
+-- myclock_t:remove_from_object(obj1)
+-- myclock_t:remove_from_object(obj2)
+--
+-- Now the same tooltip is only attached to `myclock`.
+--
+-- @author Sébastien Gross &lt;seb•ɱɩɲʋʃ•awesome•ɑƬ•chezwam•ɖɵʈ•org&gt;
+-- @copyright 2009 Sébastien Gross
+-- @classmod awful.tooltip
+-------------------------------------------------------------------------
+
+local mouse = mouse
+local timer = require("gears.timer")
+local util = require("awful.util")
+local object = require("gears.object")
+local color = require("gears.color")
+local wibox = require("wibox")
+local a_placement = require("awful.placement")
+local abutton = require("awful.button")
+local shape = require("gears.shape")
+local beautiful = require("beautiful")
+local textbox = require("wibox.widget.textbox")
+local dpi = require("beautiful").xresources.apply_dpi
+local cairo = require("lgi").cairo
+local setmetatable = setmetatable
+local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
+local ipairs = ipairs
+local capi = {mouse=mouse, awesome=awesome}
+
+local tooltip = { mt = {} }
+
+-- The mouse point is 1x1, so anything aligned based on it as parent
+-- geometry will go out of bound. To get the desired placement, it is
+-- necessary to swap left with right and top with bottom
+local align_convert = {
+ top_left = "bottom_right",
+ left = "right",
+ bottom_left = "top_right",
+ right = "left",
+ top_right = "bottom_left",
+ bottom_right = "top_left",
+ top = "bottom",
+ bottom = "top",
+}
+
+-- If the wibox is under the cursor, it will trigger a mouse::leave
+local offset = {
+ top_left = {x = 0, y = 0 },
+ left = {x = 0, y = 0 },
+ bottom_left = {x = 0, y = 0 },
+ right = {x = 1, y = 0 },
+ top_right = {x = 0, y = 0 },
+ bottom_right = {x = 1, y = 1 },
+ top = {x = 0, y = 0 },
+ bottom = {x = 0, y = 1 },
+}
+
+--- The tooltip border color.
+-- @beautiful beautiful.tooltip_border_color
+
+--- The tooltip background color.
+-- @beautiful beautiful.tooltip_bg
+
+--- The tooltip foregound (text) color.
+-- @beautiful beautiful.tooltip_fg
+
+--- The tooltip font.
+-- @beautiful beautiful.tooltip_font
+
+--- The tooltip border width.
+-- @beautiful beautiful.tooltip_border_width
+
+--- The tooltip opacity.
+-- @beautiful beautiful.tooltip_opacity
+
+--- The default tooltip shape.
+-- By default, all tooltips are rectangles, however, by setting this variables,
+-- they can default to rounded rectangle or stretched octogons.
+-- @beautiful beautiful.tooltip_shape
+-- @tparam[opt=gears.shape.rectangle] function shape A `gears.shape` compatible function
+-- @see shape
+-- @see gears.shape
+
+local function apply_shape(self)
+ local s = self._private.shape
+
+ local wb = self.wibox
+
+ if not s then
+ -- Clear the shape
+ if wb.shape_bounding then
+ wb.shape_bounding = nil
+ wb:set_bgimage(nil)
+ end
+
+ return
+ end
+
+ local w, h = wb.width, wb.height
+
+ -- First, create a A1 mask for the shape bounding itself
+ local img = cairo.ImageSurface(cairo.Format.A1, w, h)
+ local cr = cairo.Context(img)
+
+ cr:set_source_rgba(1,1,1,1)
+
+ s(cr, w, h, unpack(self._private.shape_args or {}))
+ cr:fill()
+ wb.shape_bounding = img._native
+
+ -- The wibox background uses ARGB32 border so tooltip anti-aliasing works
+ -- when an external compositor is used. This will look better than
+ -- the capi.drawin's own border support.
+ img = cairo.ImageSurface(cairo.Format.ARGB32, w, h)
+ cr = cairo.Context(img)
+
+ -- Draw the border (multiply by 2, then mask the inner part to save a path)
+ local bw = (self._private.border_width
+ or beautiful.tooltip_border_width
+ or beautiful.border_width or 0) * 2
+
+ -- Fix anti-aliasing
+ if bw > 2 and awesome.composite_manager_running then
+ bw = bw - 1
+ end
+
+ local bc = self._private.border_color
+ or beautiful.tooltip_border_color
+ or beautiful.border_normal
+ or "#ffcb60"
+
+ cr:translate(bw, bw)
+ s(cr, w-2*bw, h-2*bw, unpack(self._private.shape_args or {}))
+ cr:set_line_width(bw)
+ cr:set_source(color(bc))
+ cr:stroke_preserve()
+ cr:clip()
+
+ local bg = self._private.bg
+ or beautiful.tooltip_bg
+ or beautiful.bg_focus or "#ffcb60"
+
+ cr:set_source(color(bg))
+ cr:paint()
+
+ wb:set_bgimage(img)
+end
+
+local function apply_mouse_mode(self)
+ local w = self:get_wibox()
+ local align = self._private.align
+ local real_placement = align_convert[align]
+
+ a_placement[real_placement](w, {
+ parent = capi.mouse,
+ offset = offset[align]
+ })
+end
+
+local function apply_outside_mode(self)
+ local w = self:get_wibox()
+
+ local _, position = a_placement.next_to(w, {
+ geometry = self._private.widget_geometry,
+ preferred_positions = self.preferred_positions,
+ honor_workarea = true,
+ })
+
+ if position ~= self.current_position then
+ -- Re-apply the shape.
+ apply_shape(self)
+ end
+
+ self.current_position = position
+end
+
+-- Place the tooltip under the mouse.
+--
+-- @tparam tooltip self A tooltip object.
+local function set_geometry(self)
+ -- calculate width / height
+ local n_w, n_h = self.textbox:get_preferred_size(mouse.screen)
+ n_w = n_w + self.marginbox.left + self.marginbox.right
+ n_h = n_h + self.marginbox.top + self.marginbox.bottom
+
+ local w = self:get_wibox()
+ w:geometry({ width = n_w, height = n_h })
+
+ if self._private.shape then
+ apply_shape(self)
+ end
+
+ local mode = self.mode
+
+ if mode == "outside" and self._private.widget_geometry then
+ apply_outside_mode(self)
+ else
+ apply_mouse_mode(self)
+ end
+
+ a_placement.no_offscreen(w)
+end
+
+-- Show a tooltip.
+--
+-- @tparam tooltip self The tooltip to show.
+local function show(self)
+ -- do nothing if the tooltip is already shown
+ if self._private.visible then return end
+ if self.timer then
+ if not self.timer.started then
+ self:timer_function()
+ self.timer:start()
+ end
+ end
+ set_geometry(self)
+ self.wibox.visible = true
+ self._private.visible = true
+ self:emit_signal("property::visible")
+end
+
+-- Hide a tooltip.
+--
+-- @tparam tooltip self The tooltip to hide.
+local function hide(self)
+ -- do nothing if the tooltip is already hidden
+ if not self._private.visible then return end
+ if self.timer then
+ if self.timer.started then
+ self.timer:stop()
+ end
+ end
+ self.wibox.visible = false
+ self._private.visible = false
+ self:emit_signal("property::visible")
+end
+
+--- The wibox.
+-- @property wibox
+-- @param `wibox`
+
+function tooltip:get_wibox()
+ if self._private.wibox then
+ return self._private.wibox
+ end
+
+ local wb = wibox(self.wibox_properties)
+ wb:set_widget(self.marginbox)
+
+ -- Close the tooltip when clicking it. This gets done on release, to not
+ -- emit the release event on an underlying object, e.g. the titlebar icon.
+ wb:buttons(abutton({}, 1, nil, self.hide))
+
+ self._private.wibox = wb
+
+ return wb
+end
+
+--- Is the tooltip visible?
+-- @property visible
+-- @param boolean
+
+function tooltip:get_visible()
+ return self._private.visible
+end
+
+function tooltip:set_visible(value)
+ if self._private.visible == value then return end
+
+ if value then
+ show(self)
+ else
+ hide(self)
+ end
+end
+
+--- The horizontal alignment.
+--
+-- The following values are valid:
+--
+-- * top_left
+-- * left
+-- * bottom_left
+-- * right
+-- * top_right
+-- * bottom_right
+-- * bottom
+-- * top
+--
+-- @property align
+-- @see beautiful.tooltip_align
+
+--- The default tooltip alignment.
+-- @beautiful beautiful.tooltip_align
+-- @param string
+-- @see align
+
+function tooltip:get_align()
+ return self._private.align
+end
+
+function tooltip:set_align(value)
+ if not align_convert[value] then
+ return
+ end
+
+ self._private.align = value
+
+ set_geometry(self)
+ self:emit_signal("property::align")
+end
+
+--- The shape of the tooltip window.
+-- If the shape require some parameters, use `set_shape`.
+-- @property shape
+-- @see gears.shape
+-- @see set_shape
+-- @see beautiful.tooltip_shape
+
+--- Set the tooltip shape.
+-- All other arguments will be passed to the shape function.
+-- @tparam gears.shape s The shape
+-- @see shape
+-- @see gears.shape
+function tooltip:set_shape(s, ...)
+ self._private.shape = s
+ self._private.shape_args = {...}
+ apply_shape(self)
+end
+
+--- Set the tooltip positioning mode.
+-- This affects how the tooltip is placed. By default, the tooltip is `align`ed
+-- close to the mouse cursor. It is also possible to place the tooltip relative
+-- to the widget geometry.
+--
+-- Valid modes are:
+--
+-- * "mouse": Next to the mouse cursor
+-- * "outside": Outside of the widget
+--
+-- @property mode
+-- @param string
+
+function tooltip:set_mode(mode)
+ self._private.mode = mode
+
+ set_geometry(self)
+ self:emit_signal("property::mode")
+end
+
+function tooltip:get_mode()
+ return self._private.mode or "mouse"
+end
+
+--- The preferred positions when in `outside` mode.
+--
+-- If the tooltip fits on multiple sides of the drawable, then this defines the
+-- priority
+--
+-- The default is:
+--
+-- {"top", "right", "left", "bottom"}
+--
+-- @property preferred_positions
+-- @tparam table preferred_positions The position, ordered by priorities
+
+function tooltip:get_preferred_positions()
+ return self._private.preferred_positions or
+ {"top", "right", "left", "bottom"}
+end
+
+function tooltip:set_preferred_positions(value)
+ self._private.preferred_positions = value
+
+ set_geometry(self)
+end
+
+--- Change displayed text.
+--
+-- @property text
+-- @tparam tooltip self The tooltip object.
+-- @tparam string text New tooltip text, passed to
+-- `wibox.widget.textbox.set_text`.
+
+function tooltip:set_text(text)
+ self.textbox:set_text(text)
+ if self._private.visible then
+ set_geometry(self)
+ end
+end
+
+--- Change displayed markup.
+--
+-- @property markup
+-- @tparam tooltip self The tooltip object.
+-- @tparam string text New tooltip markup, passed to
+-- `wibox.widget.textbox.set_markup`.
+
+function tooltip:set_markup(text)
+ self.textbox:set_markup(text)
+ if self._private.visible then
+ set_geometry(self)
+ end
+end
+
+--- Change the tooltip's update interval.
+--
+-- @property timeout
+-- @tparam tooltip self A tooltip object.
+-- @tparam number timeout The timeout value.
+
+function tooltip:set_timeout(timeout)
+ if self.timer then
+ self.timer.timeout = timeout
+ end
+end
+
+--- Add tooltip to an object.
+--
+-- @tparam tooltip self The tooltip.
+-- @tparam gears.object obj An object with `mouse::enter` and
+-- `mouse::leave` signals.
+-- @function add_to_object
+function tooltip:add_to_object(obj)
+ if not obj then return end
+
+ obj:connect_signal("mouse::enter", self.show)
+ obj:connect_signal("mouse::leave", self.hide)
+end
+
+--- Remove tooltip from an object.
+--
+-- @tparam tooltip self The tooltip.
+-- @tparam gears.object obj An object with `mouse::enter` and
+-- `mouse::leave` signals.
+-- @function remove_from_object
+function tooltip:remove_from_object(obj)
+ obj:disconnect_signal("mouse::enter", self.show)
+ obj:disconnect_signal("mouse::leave", self.hide)
+end
+
+-- Tooltip can be applied to both widgets, wibox and client, their geometry
+-- works differently.
+local function get_parent_geometry(arg1, arg2)
+ if type(arg2) == "table" and arg2.width then
+ return arg2
+ elseif type(arg1) == "table" and arg1.width then
+ return arg1
+ end
+end
+
+--- Create a new tooltip and link it to a widget.
+-- Tooltips emit `property::visible` when their visibility changes.
+-- @tparam table args Arguments for tooltip creation.
+-- @tparam function args.timer_function A function to dynamically set the
+-- tooltip text. Its return value will be passed to
+-- `wibox.widget.textbox.set_markup`.
+-- @tparam[opt=1] number args.timeout The timeout value for
+-- `timer_function`.
+-- @tparam[opt] table args.objects A list of objects linked to the tooltip.
+-- @tparam[opt] number args.delay_show Delay showing the tooltip by this many
+-- seconds.
+-- @tparam[opt=apply_dpi(5)] integer args.margin_leftright The left/right margin for the text.
+-- @tparam[opt=apply_dpi(3)] integer args.margin_topbottom The top/bottom margin for the text.
+-- @tparam[opt=nil] gears.shape args.shape The shape
+-- @treturn awful.tooltip The created tooltip.
+-- @see add_to_object
+-- @see timeout
+-- @see text
+-- @see markup
+-- @function awful.tooltip
+function tooltip.new(args)
+ local self = object {
+ enable_properties = true,
+ }
+
+ rawset(self,"_private", {})
+
+ self._private.visible = false
+ self._private.align = args.align or beautiful.tooltip_align or "right"
+ self._private.shape = args.shape or beautiful.tooltip_shape
+ or shape.rectangle
+
+ -- private data
+ if args.delay_show then
+ local delay_timeout
+
+ delay_timeout = timer { timeout = args.delay_show }
+ delay_timeout:connect_signal("timeout", function ()
+ show(self)
+ delay_timeout:stop()
+ end)
+
+ function self.show(other, geo)
+ -- Auto detect clients and wiboxes
+ if other.drawable or other.pid then
+ geo = other:geometry()
+ end
+
+ -- Cache the geometry in case it is needed later
+ self._private.widget_geometry = get_parent_geometry(other, geo)
+
+ if not delay_timeout.started then
+ delay_timeout:start()
+ end
+ end
+ function self.hide()
+ if delay_timeout.started then
+ delay_timeout:stop()
+ end
+ hide(self)
+ end
+ else
+ function self.show(other, geo)
+ -- Auto detect clients and wiboxes
+ if other.drawable or other.pid then
+ geo = other:geometry()
+ end
+
+ -- Cache the geometry in case it is needed later
+ self._private.widget_geometry = get_parent_geometry(other, geo)
+
+ show(self)
+ end
+ function self.hide()
+ hide(self)
+ end
+ end
+
+ -- export functions
+ util.table.crush(self, tooltip, true)
+
+ -- setup the timer action only if needed
+ if args.timer_function then
+ self.timer = timer { timeout = args.timeout and args.timeout or 1 }
+ self.timer_function = function()
+ self:set_markup(args.timer_function())
+ end
+ self.timer:connect_signal("timeout", self.timer_function)
+ end
+
+ local fg = beautiful.tooltip_fg or beautiful.fg_focus or "#000000"
+ local font = beautiful.tooltip_font or beautiful.font
+
+ -- Set default properties
+ self.wibox_properties = {
+ visible = false,
+ ontop = true,
+ border_width = 0,
+ fg = fg,
+ bg = color.transparent,
+ opacity = beautiful.tooltip_opacity or 1,
+ }
+
+ self.textbox = textbox()
+ self.textbox:set_font(font)
+
+ -- Add margin.
+ local m_lr = args.margin_leftright or dpi(5)
+ local m_tb = args.margin_topbottom or dpi(3)
+ self.marginbox = wibox.container.margin(self.textbox, m_lr, m_lr, m_tb, m_tb)
+
+ -- Add tooltip to objects
+ if args.objects then
+ for _, obj in ipairs(args.objects) do
+ self:add_to_object(obj)
+ end
+ end
+
+ -- Apply the properties
+ for k, v in pairs(args) do
+ if tooltip["set_"..k] then
+ self[k] = v
+ end
+ end
+
+ return self
+end
+
+function tooltip.mt:__call(...)
+ return tooltip.new(...)
+end
+
+return setmetatable(tooltip, tooltip.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/util.lua b/lib/awful/util.lua
new file mode 100644
index 0000000..8dcb955
--- /dev/null
+++ b/lib/awful/util.lua
@@ -0,0 +1,588 @@
+---------------------------------------------------------------------------
+--- Utility module for awful
+--
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2008 Julien Danjou
+-- @module awful.util
+---------------------------------------------------------------------------
+
+-- Grab environment we need
+local os = os
+local assert = assert
+local load = loadstring or load -- luacheck: globals loadstring (compatibility with Lua 5.1)
+local loadfile = loadfile
+local debug = debug
+local pairs = pairs
+local ipairs = ipairs
+local type = type
+local rtable = table
+local string = string
+local lgi = require("lgi")
+local grect = require("gears.geometry").rectangle
+local Gio = require("lgi").Gio
+local Pango = lgi.Pango
+local capi =
+{
+ awesome = awesome,
+ mouse = mouse
+}
+local gears_debug = require("gears.debug")
+local floor = math.floor
+
+local util = {}
+util.table = {}
+
+--- The default shell used when spawing processes.
+util.shell = os.getenv("SHELL") or "/bin/sh"
+
+local displayed_deprecations = {}
+--- Display a deprecation notice, but only once per traceback.
+-- @param[opt] see The message to a new method / function to use.
+-- @tparam table args Extra arguments
+-- @tparam boolean args.raw Print the message as-is without the automatic context
+function util.deprecate(see, args)
+ args = args or {}
+ local tb = debug.traceback()
+ if displayed_deprecations[tb] then
+ return
+ end
+ displayed_deprecations[tb] = true
+
+ -- Get function name/desc from caller.
+ local info = debug.getinfo(2, "n")
+ local funcname = info.name or "?"
+ local msg = "awful: function " .. funcname .. " is deprecated"
+ if see then
+ if args.raw then
+ msg = see
+ elseif string.sub(see, 1, 3) == 'Use' then
+ msg = msg .. ". " .. see
+ else
+ msg = msg .. ", see " .. see
+ end
+ end
+ gears_debug.print_warning(msg .. ".\n" .. tb)
+end
+
+--- Create a class proxy with deprecation messages.
+-- This is useful when a class has moved somewhere else.
+-- @tparam table fallback The new class
+-- @tparam string old_name The old class name
+-- @tparam string new_name The new class name
+-- @treturn table A proxy class.
+function util.deprecate_class(fallback, old_name, new_name)
+ local message = old_name.." has been renamed to "..new_name
+
+ local function call(_,...)
+ util.deprecate(message)
+
+ return fallback(...)
+ end
+
+ local function index(_, k)
+ util.deprecate(message)
+
+ return fallback[k]
+ end
+
+ local function newindex(_, k, v)
+ util.deprecate(message, {raw = true})
+
+ fallback[k] = v
+ end
+
+ return setmetatable({}, {__call = call, __index = index, __newindex = newindex})
+end
+
+--- Get a valid color for Pango markup
+-- @param color The color.
+-- @tparam string fallback The color to return if the first is invalid. (default: black)
+-- @treturn string color if it is valid, else fallback.
+function util.ensure_pango_color(color, fallback)
+ color = tostring(color)
+ return Pango.Color.parse(Pango.Color(), color) and color or fallback or "black"
+end
+
+--- Make i cycle.
+-- @param t A length. Must be greater than zero.
+-- @param i An absolute index to fit into #t.
+-- @return An integer in (1, t) or nil if t is less than or equal to zero.
+function util.cycle(t, i)
+ if t < 1 then return end
+ i = i % t
+ if i == 0 then
+ i = t
+ end
+ return i
+end
+
+--- Create a directory
+-- @param dir The directory.
+-- @return mkdir return code
+function util.mkdir(dir)
+ return os.execute("mkdir -p " .. dir)
+end
+
+--- Eval Lua code.
+-- @return The return value of Lua code.
+function util.eval(s)
+ return assert(load(s))()
+end
+
+local xml_entity_names = { ["'"] = "&apos;", ["\""] = "&quot;", ["<"] = "&lt;", [">"] = "&gt;", ["&"] = "&amp;" };
+--- Escape a string from XML char.
+-- Useful to set raw text in textbox.
+-- @param text Text to escape.
+-- @return Escape text.
+function util.escape(text)
+ return text and text:gsub("['&<>\"]", xml_entity_names) or nil
+end
+
+local xml_entity_chars = { lt = "<", gt = ">", nbsp = " ", quot = "\"", apos = "'", ndash = "-", mdash = "-", amp = "&" };
+--- Unescape a string from entities.
+-- @param text Text to unescape.
+-- @return Unescaped text.
+function util.unescape(text)
+ return text and text:gsub("&(%a+);", xml_entity_chars) or nil
+end
+
+--- Check if a file is a Lua valid file.
+-- This is done by loading the content and compiling it with loadfile().
+-- @param path The file path.
+-- @return A function if everything is alright, a string with the error
+-- otherwise.
+function util.checkfile(path)
+ local f, e = loadfile(path)
+ -- Return function if function, otherwise return error.
+ if f then return f end
+ return e
+end
+
+--- Try to restart awesome.
+-- It checks if the configuration file is valid, and then restart if it's ok.
+-- If it's not ok, the error will be returned.
+-- @return Never return if awesome restart, or return a string error.
+function util.restart()
+ local c = util.checkfile(capi.awesome.conffile)
+
+ if type(c) ~= "function" then
+ return c
+ end
+
+ capi.awesome.restart()
+end
+
+--- Get the config home according to the XDG basedir specification.
+-- @return the config home (XDG_CONFIG_HOME) with a slash at the end.
+function util.get_xdg_config_home()
+ return (os.getenv("XDG_CONFIG_HOME") or os.getenv("HOME") .. "/.config") .. "/"
+end
+
+--- Get the cache home according to the XDG basedir specification.
+-- @return the cache home (XDG_CACHE_HOME) with a slash at the end.
+function util.get_xdg_cache_home()
+ return (os.getenv("XDG_CACHE_HOME") or os.getenv("HOME") .. "/.cache") .. "/"
+end
+
+--- Get the path to the user's config dir.
+-- This is the directory containing the configuration file ("rc.lua").
+-- @return A string with the requested path with a slash at the end.
+function util.get_configuration_dir()
+ return capi.awesome.conffile:match(".*/") or "./"
+end
+
+--- Get the path to a directory that should be used for caching data.
+-- @return A string with the requested path with a slash at the end.
+function util.get_cache_dir()
+ return util.get_xdg_cache_home() .. "awesome/"
+end
+
+--- Get the path to the directory where themes are installed.
+-- @return A string with the requested path with a slash at the end.
+function util.get_themes_dir()
+ return "/usr/share/awesome/themes" .. "/"
+end
+
+--- Get the path to the directory where our icons are installed.
+-- @return A string with the requested path with a slash at the end.
+function util.get_awesome_icon_dir()
+ return "/usr/share/awesome/icons" .. "/"
+end
+
+--- Get the user's config or cache dir.
+-- It first checks XDG_CONFIG_HOME / XDG_CACHE_HOME, but then goes with the
+-- default paths.
+-- @param d The directory to get (either "config" or "cache").
+-- @return A string containing the requested path.
+function util.getdir(d)
+ if d == "config" then
+ -- No idea why this is what is returned, I recommend everyone to use
+ -- get_configuration_dir() instead
+ return util.get_xdg_config_home() .. "awesome/"
+ elseif d == "cache" then
+ return util.get_cache_dir()
+ end
+end
+
+--- Search for an icon and return the full path.
+-- It searches for the icon path under the given directories with respect to the
+-- given extensions for the icon filename.
+-- @param iconname The name of the icon to search for.
+-- @param exts Table of image extensions allowed, otherwise { 'png', gif' }
+-- @param dirs Table of dirs to search, otherwise { '/usr/share/pixmaps/' }
+-- @tparam[opt] string size The size. If this is specified, subdirectories `x`
+-- of the dirs are searched first.
+function util.geticonpath(iconname, exts, dirs, size)
+ exts = exts or { 'png', 'gif' }
+ dirs = dirs or { '/usr/share/pixmaps/', '/usr/share/icons/hicolor/' }
+ local icontypes = { 'apps', 'actions', 'categories', 'emblems',
+ 'mimetypes', 'status', 'devices', 'extras', 'places', 'stock' }
+ for _, d in pairs(dirs) do
+ local icon
+ for _, e in pairs(exts) do
+ icon = d .. iconname .. '.' .. e
+ if util.file_readable(icon) then
+ return icon
+ end
+ if size then
+ for _, t in pairs(icontypes) do
+ icon = string.format("%s%ux%u/%s/%s.%s", d, size, size, t, iconname, e)
+ if util.file_readable(icon) then
+ return icon
+ end
+ end
+ end
+ end
+ end
+end
+
+--- Check if a file exists, is not readable and not a directory.
+-- @param filename The file path.
+-- @return True if file exists and is readable.
+function util.file_readable(filename)
+ local gfile = Gio.File.new_for_path(filename)
+ local gfileinfo = gfile:query_info("standard::type,access::can-read",
+ Gio.FileQueryInfoFlags.NONE)
+ return gfileinfo and gfileinfo:get_file_type() ~= "DIRECTORY" and
+ gfileinfo:get_attribute_boolean("access::can-read")
+end
+
+--- Check if a path exists, is readable and is a directory.
+-- @tparam string path The directory path.
+-- @treturn boolean True if dir exists and is readable.
+function util.dir_readable(path)
+ local gfile = Gio.File.new_for_path(path)
+ local gfileinfo = gfile:query_info("standard::type,access::can-read",
+ Gio.FileQueryInfoFlags.NONE)
+ return gfileinfo and gfileinfo:get_file_type() == "DIRECTORY" and
+ gfileinfo:get_attribute_boolean("access::can-read")
+end
+
+--- Check if a path is a directory.
+-- @tparam string path
+-- @treturn bool True if path exists and is a directory.
+function util.is_dir(path)
+ return Gio.File.new_for_path(path):query_file_type({}) == "DIRECTORY"
+end
+
+local function subset_mask_apply(mask, set)
+ local ret = {}
+ for i = 1, #set do
+ if mask[i] then
+ rtable.insert(ret, set[i])
+ end
+ end
+ return ret
+end
+
+local function subset_next(mask)
+ local i = 1
+ while i <= #mask and mask[i] do
+ mask[i] = false
+ i = i + 1
+ end
+
+ if i <= #mask then
+ mask[i] = 1
+ return true
+ end
+ return false
+end
+
+--- Return all subsets of a specific set.
+-- This function, giving a set, will return all subset it.
+-- For example, if we consider a set with value { 10, 15, 34 },
+-- it will return a table containing 2^n set:
+-- { }, { 10 }, { 15 }, { 34 }, { 10, 15 }, { 10, 34 }, etc.
+-- @param set A set.
+-- @return A table with all subset.
+function util.subsets(set)
+ local mask = {}
+ local ret = {}
+ for i = 1, #set do mask[i] = false end
+
+ -- Insert the empty one
+ rtable.insert(ret, {})
+
+ while subset_next(mask) do
+ rtable.insert(ret, subset_mask_apply(mask, set))
+ end
+ return ret
+end
+
+--- Get the nearest rectangle in the given direction. Every rectangle is specified as a table
+-- with 'x', 'y', 'width', 'height' keys, the same as client or screen geometries.
+-- @deprecated awful.util.get_rectangle_in_direction
+-- @param dir The direction, can be either "up", "down", "left" or "right".
+-- @param recttbl A table of rectangle specifications.
+-- @param cur The current rectangle.
+-- @return The index for the rectangle in recttbl closer to cur in the given direction. nil if none found.
+-- @see gears.geometry
+function util.get_rectangle_in_direction(dir, recttbl, cur)
+ util.deprecate("gears.geometry.rectangle.get_in_direction")
+
+ return grect.get_in_direction(dir, recttbl, cur)
+end
+
+--- Join all tables given as parameters.
+-- This will iterate all tables and insert all their keys into a new table.
+-- @param args A list of tables to join
+-- @return A new table containing all keys from the arguments.
+function util.table.join(...)
+ local ret = {}
+ for _, t in pairs({...}) do
+ if t then
+ for k, v in pairs(t) do
+ if type(k) == "number" then
+ rtable.insert(ret, v)
+ else
+ ret[k] = v
+ end
+ end
+ end
+ end
+ return ret
+end
+
+--- Override elements in the first table by the one in the second.
+--
+-- Note that this method doesn't copy entries found in `__index`.
+-- @tparam table t the table to be overriden
+-- @tparam table set the table used to override members of `t`
+-- @tparam[opt=false] boolean raw Use rawset (avoid the metatable)
+-- @treturn table t (for convenience)
+function util.table.crush(t, set, raw)
+ if raw then
+ for k, v in pairs(set) do
+ rawset(t, k, v)
+ end
+ else
+ for k, v in pairs(set) do
+ t[k] = v
+ end
+ end
+
+ return t
+end
+
+--- Pack all elements with an integer key into a new table
+-- While both lua and luajit implement __len over sparse
+-- table, the standard define it as an implementation
+-- detail.
+--
+-- This function remove any non numeric keys from the value set
+--
+-- @tparam table t A potentially sparse table
+-- @treturn table A packed table with all numeric keys
+function util.table.from_sparse(t)
+ local keys= {}
+ for k in pairs(t) do
+ if type(k) == "number" then
+ keys[#keys+1] = k
+ end
+ end
+
+ table.sort(keys)
+
+ local ret = {}
+ for _,v in ipairs(keys) do
+ ret[#ret+1] = t[v]
+ end
+
+ return ret
+end
+
+--- Check if a table has an item and return its key.
+-- @param t The table.
+-- @param item The item to look for in values of the table.
+-- @return The key were the item is found, or nil if not found.
+function util.table.hasitem(t, item)
+ for k, v in pairs(t) do
+ if v == item then
+ return k
+ end
+ end
+end
+
+--- Split a string into multiple lines
+-- @param text String to wrap.
+-- @param width Maximum length of each line. Default: 72.
+-- @param indent Number of spaces added before each wrapped line. Default: 0.
+-- @return The string with lines wrapped to width.
+function util.linewrap(text, width, indent)
+ text = text or ""
+ width = width or 72
+ indent = indent or 0
+
+ local pos = 1
+ return text:gsub("(%s+)()(%S+)()",
+ function(_, st, word, fi)
+ if fi - pos > width then
+ pos = st
+ return "\n" .. string.rep(" ", indent) .. word
+ end
+ end)
+end
+
+--- Count number of lines in a string
+-- @tparam string text Input string.
+-- @treturn int Number of lines.
+function util.linecount(text)
+ return select(2, text:gsub('\n', '\n')) + 1
+end
+
+--- Get a sorted table with all integer keys from a table
+-- @param t the table for which the keys to get
+-- @return A table with keys
+function util.table.keys(t)
+ local keys = { }
+ for k, _ in pairs(t) do
+ rtable.insert(keys, k)
+ end
+ rtable.sort(keys, function (a, b)
+ return type(a) == type(b) and a < b or false
+ end)
+ return keys
+end
+
+--- Filter a tables keys for certain content types
+-- @param t The table to retrieve the keys for
+-- @param ... the types to look for
+-- @return A filtered table with keys
+function util.table.keys_filter(t, ...)
+ local keys = util.table.keys(t)
+ local keys_filtered = { }
+ for _, k in pairs(keys) do
+ for _, et in pairs({...}) do
+ if type(t[k]) == et then
+ rtable.insert(keys_filtered, k)
+ break
+ end
+ end
+ end
+ return keys_filtered
+end
+
+--- Reverse a table
+-- @param t the table to reverse
+-- @return the reversed table
+function util.table.reverse(t)
+ local tr = { }
+ -- reverse all elements with integer keys
+ for _, v in ipairs(t) do
+ rtable.insert(tr, 1, v)
+ end
+ -- add the remaining elements
+ for k, v in pairs(t) do
+ if type(k) ~= "number" then
+ tr[k] = v
+ end
+ end
+ return tr
+end
+
+--- Clone a table
+-- @param t the table to clone
+-- @param deep Create a deep clone? (default: true)
+-- @return a clone of t
+function util.table.clone(t, deep)
+ deep = deep == nil and true or deep
+ local c = { }
+ for k, v in pairs(t) do
+ if deep and type(v) == "table" then
+ c[k] = util.table.clone(v)
+ else
+ c[k] = v
+ end
+ end
+ return c
+end
+
+---
+-- Returns an iterator to cycle through, starting from the first element or the
+-- given index, all elements of a table that match a given criteria.
+--
+-- @param t the table to iterate
+-- @param filter a function that returns true to indicate a positive match
+-- @param start what index to start iterating from. Default is 1 (=> start of
+-- the table)
+function util.table.iterate(t, filter, start)
+ local count = 0
+ local index = start or 1
+ local length = #t
+
+ return function ()
+ while count < length do
+ local item = t[index]
+ index = util.cycle(#t, index + 1)
+ count = count + 1
+ if filter(item) then return item end
+ end
+ end
+end
+
+
+--- Merge items from the one table to another one
+-- @tparam table t the container table
+-- @tparam table set the mixin table
+-- @treturn table Return `t` for convenience
+function util.table.merge(t, set)
+ for _, v in ipairs(set) do
+ table.insert(t, v)
+ end
+ return t
+end
+
+
+-- Escape all special pattern-matching characters so that lua interprets them
+-- literally instead of as a character class.
+-- Source: http://stackoverflow.com/a/20778724/15690
+function util.quote_pattern(s)
+ -- All special characters escaped in a string: %%, %^, %$, ...
+ local patternchars = '['..("%^$().[]*+-?"):gsub("(.)", "%%%1")..']'
+ return string.gsub(s, patternchars, "%%%1")
+end
+
+-- Generate a pattern matching expression that ignores case.
+-- @param s Original pattern matching expression.
+function util.query_to_pattern(q)
+ local s = util.quote_pattern(q)
+ -- Poor man's case-insensitive character matching.
+ s = string.gsub(s, "%a",
+ function (c)
+ return string.format("[%s%s]", string.lower(c),
+ string.upper(c))
+ end)
+ return s
+end
+
+--- Round a number to an integer.
+-- @tparam number x
+-- @treturn integer
+function util.round(x)
+ return floor(x + 0.5)
+end
+
+return util
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/wibar.lua b/lib/awful/wibar.lua
new file mode 100644
index 0000000..bbe7289
--- /dev/null
+++ b/lib/awful/wibar.lua
@@ -0,0 +1,603 @@
+---------------------------------------------------------------------------
+--- Wibox module for awful.
+-- This module allows you to easily create wibox and attach them to the edge of
+-- a screen.
+--
+-- @author Emmanuel Lepage Vallee &lt;elv1313@gmail.com&gt;
+-- @copyright 2016 Emmanuel Lepage Vallee
+-- @classmod awful.wibar
+---------------------------------------------------------------------------
+
+-- Grab environment we need
+local capi =
+{
+ screen = screen,
+ client = client
+}
+local setmetatable = setmetatable
+local tostring = tostring
+local ipairs = ipairs
+local error = error
+local wibox = require("wibox")
+local beautiful = require("beautiful")
+local util = require("awful.util")
+local placement = require("awful.placement")
+
+local function get_screen(s)
+ return s and capi.screen[s]
+end
+
+local awfulwibar = { mt = {} }
+
+--- Array of table with wiboxes inside.
+-- It's an array so it is ordered.
+local wiboxes = setmetatable({}, {__mode = "v"})
+
+-- Compute the margin on one side
+local function get_margin(w, position, auto_stop)
+ local h_or_w = (position == "top" or position == "bottom") and "height" or "width"
+ local ret = 0
+
+ for _, v in ipairs(wiboxes) do
+ -- Ignore the wibars placed after this one
+ if auto_stop and v == w then break end
+
+ if v.position == position and v.screen == w.screen and v.visible then
+ ret = ret + v[h_or_w]
+ end
+ end
+
+ return ret
+end
+
+-- `honor_workarea` cannot be used as it does modify the workarea itself.
+-- a manual padding has to be generated.
+local function get_margins(w)
+ local position = w.position
+ assert(position)
+
+ local margins = {left=0, right=0, top=0, bottom=0}
+
+ margins[position] = get_margin(w, position, true)
+
+ -- Avoid overlapping wibars
+ if position == "left" or position == "right" then
+ margins.top = get_margin(w, "top" )
+ margins.bottom = get_margin(w, "bottom")
+ end
+
+ return margins
+end
+
+-- Create the placement function
+local function gen_placement(position, stretch)
+ local maximize = (position == "right" or position == "left") and
+ "maximize_vertically" or "maximize_horizontally"
+
+ return placement[position] + (stretch and placement[maximize] or nil)
+end
+
+-- Attach the placement function.
+local function attach(wb, align)
+ gen_placement(align, wb._stretch)(wb, {
+ attach = true,
+ update_workarea = true,
+ margins = get_margins(wb)
+ })
+end
+
+-- Re-attach all wibars on a given wibar screen
+local function reattach(wb)
+ local s = wb.screen
+ for _, w in ipairs(wiboxes) do
+ if w ~= wb and w.screen == s then
+ if w.detach_callback then
+ w.detach_callback()
+ w.detach_callback = nil
+ end
+ attach(w, w.position)
+ end
+ end
+end
+
+--- The wibox position.
+-- @property position
+-- @param string Either "left", right", "top" or "bottom"
+
+local function get_position(wb)
+ return wb._position or "top"
+end
+
+local function set_position(wb, position)
+ -- Detach first to avoid any uneeded callbacks
+ if wb.detach_callback then
+ wb.detach_callback()
+
+ -- Avoid disconnecting twice, this produces a lot of warnings
+ wb.detach_callback = nil
+ end
+
+ -- Move the wibar to the end of the list to avoid messing up the others in
+ -- case there is stacked wibars on one side.
+ if wb._position then
+ for k, w in ipairs(wiboxes) do
+ if w == wb then
+ table.remove(wiboxes, k)
+ end
+ end
+ table.insert(wiboxes, wb)
+ end
+
+ -- In case the position changed, it may be necessary to reset the size
+ if (wb._position == "left" or wb._position == "right")
+ and (position == "top" or position == "bottom") then
+ wb.height = math.ceil(beautiful.get_font_height(wb.font) * 1.5)
+ elseif (wb._position == "top" or wb._position == "bottom")
+ and (position == "left" or position == "right") then
+ wb.width = math.ceil(beautiful.get_font_height(wb.font) * 1.5)
+ end
+
+ -- Changing the position will also cause the other margins to be invalidated.
+ -- For example, adding a wibar to the top will change the margins of any left
+ -- or right wibars. To solve, this, they need to be re-attached.
+ reattach(wb)
+
+ -- Set the new position
+ wb._position = position
+
+ -- Attach to the new position
+ attach(wb, position)
+end
+
+--- Stretch the wibar.
+--
+-- @property stretch
+-- @param[opt=true] boolean
+
+local function get_stretch(w)
+ return w._stretch
+end
+
+local function set_stretch(w, value)
+ w._stretch = value
+
+ attach(w, w.position)
+end
+
+--- Remove a wibar.
+-- @function remove
+local function remove(self)
+ self.visible = false
+
+ if self.detach_callback then
+ self.detach_callback()
+ self.detach_callback = nil
+ end
+
+ for k, w in ipairs(wiboxes) do
+ if w == self then
+ table.remove(wiboxes, k)
+ end
+ end
+
+ self._screen = nil
+end
+
+--- Get a wibox position if it has been set, or return top.
+-- @param wb The wibox
+-- @deprecated awful.wibar.get_position
+-- @return The wibox position.
+function awfulwibar.get_position(wb)
+ util.deprecate("Use wb:get_position() instead of awful.wibar.get_position")
+ return get_position(wb)
+end
+
+--- Put a wibox on a screen at this position.
+-- @param wb The wibox to attach.
+-- @param position The position: top, bottom left or right.
+-- @param screen This argument is deprecated, use wb.screen directly.
+-- @deprecated awful.wibar.set_position
+function awfulwibar.set_position(wb, position, screen) --luacheck: no unused args
+ util.deprecate("Use wb:set_position(position) instead of awful.wibar.set_position")
+
+ set_position(wb, position)
+end
+
+--- Attach a wibox to a screen.
+--
+-- This function has been moved to the `awful.placement` module. Calling this
+-- no longer does anything.
+--
+-- @param wb The wibox to attach.
+-- @param position The position of the wibox: top, bottom, left or right.
+-- @param screen The screen to attach to
+-- @see awful.placement
+-- @deprecated awful.wibar.attach
+function awfulwibar.attach(wb, position, screen) --luacheck: no unused args
+ util.deprecate("awful.wibar.attach is deprecated, use the 'attach' property"..
+ " of awful.placement. This method doesn't do anything anymore"
+ )
+end
+
+--- Align a wibox.
+--
+-- Supported alignment are:
+--
+-- * top_left
+-- * top_right
+-- * bottom_left
+-- * bottom_right
+-- * left
+-- * right
+-- * top
+-- * bottom
+-- * centered
+-- * center_vertical
+-- * center_horizontal
+--
+-- @param wb The wibox.
+-- @param align The alignment
+-- @param screen This argument is deprecated. It is not used. Use wb.screen
+-- directly.
+-- @deprecated awful.wibar.align
+-- @see awful.placement.align
+function awfulwibar.align(wb, align, screen) --luacheck: no unused args
+ if align == "center" then
+ util.deprecate("awful.wibar.align(wb, 'center' is deprecated, use 'centered'")
+ align = "centered"
+ end
+
+ if screen then
+ util.deprecate("awful.wibar.align 'screen' argument is deprecated")
+ end
+
+ if placement[align] then
+ return placement[align](wb)
+ end
+end
+
+--- Stretch a wibox so it takes all screen width or height.
+--
+-- **This function has been removed.**
+--
+-- @deprecated awful.wibox.stretch
+-- @see awful.placement
+-- @see awful.wibar.stretch
+
+--- Create a new wibox and attach it to a screen edge.
+-- You can add also position key with value top, bottom, left or right.
+-- You can also use width or height in % and set align to center, right or left.
+-- You can also set the screen key with a screen number to attach the wibox.
+-- If not specified, the primary screen is assumed.
+-- @see wibox
+-- @tparam[opt=nil] table arg
+-- @tparam string arg.position The position.
+-- @tparam string arg.stretch If the wibar need to be stretched to fill the screen.
+-- @tparam integer arg.border_width Border width.
+-- @tparam string arg.border_color Border color.
+-- @tparam boolean arg.ontop On top of other windows.
+-- @tparam string arg.cursor The mouse cursor.
+-- @tparam boolean arg.visible Visibility.
+-- @tparam number arg.opacity The opacity of the wibox, between 0 and 1.
+-- @tparam string arg.type The window type (desktop, normal, dock, …).
+-- @tparam integer arg.x The x coordinates.
+-- @tparam integer arg.y The y coordinates.
+-- @tparam integer arg.width The width of the wibox.
+-- @tparam integer arg.height The height of the wibox.
+-- @tparam screen arg.screen The wibox screen.
+-- @tparam wibox.widget arg.widget The widget that the wibox displays.
+-- @param arg.shape_bounding The wibox’s bounding shape as a (native) cairo surface.
+-- @param arg.shape_clip The wibox’s clip shape as a (native) cairo surface.
+-- @tparam color arg.bg The background of the wibox.
+-- @tparam surface arg.bgimage The background image of the drawable.
+-- @tparam color arg.fg The foreground (text) of the wibox.
+-- @return The new wibar
+-- @function awful.wibar
+function awfulwibar.new(arg)
+ arg = arg or {}
+ local position = arg.position or "top"
+ local has_to_stretch = true
+ local screen = get_screen(arg.screen or 1)
+
+ arg.type = arg.type or "dock"
+
+ if position ~= "top" and position ~="bottom"
+ and position ~= "left" and position ~= "right" then
+ error("Invalid position in awful.wibar(), you may only use"
+ .. " 'top', 'bottom', 'left' and 'right'")
+ end
+
+ -- Set default size
+ if position == "left" or position == "right" then
+ arg.width = arg.width or math.ceil(beautiful.get_font_height(arg.font) * 1.5)
+ if arg.height then
+ has_to_stretch = false
+ if arg.screen then
+ local hp = tostring(arg.height):match("(%d+)%%")
+ if hp then
+ arg.height = math.ceil(screen.geometry.height * hp / 100)
+ end
+ end
+ end
+ else
+ arg.height = arg.height or math.ceil(beautiful.get_font_height(arg.font) * 1.5)
+ if arg.width then
+ has_to_stretch = false
+ if arg.screen then
+ local wp = tostring(arg.width):match("(%d+)%%")
+ if wp then
+ arg.width = math.ceil(screen.geometry.width * wp / 100)
+ end
+ end
+ end
+ end
+
+ arg.screen = nil
+
+ local w = wibox(arg)
+
+ w.screen = screen
+ w._screen = screen --HACK When a screen is removed, then getbycoords wont work
+ w._stretch = arg.stretch == nil and has_to_stretch or arg.stretch
+
+ w.get_position = get_position
+ w.set_position = set_position
+
+ w.get_stretch = get_stretch
+ w.set_stretch = set_stretch
+ w.remove = remove
+
+ if arg.visible == nil then w.visible = true end
+
+ w:set_position(position)
+
+ table.insert(wiboxes, w)
+
+ w:connect_signal("property::visible", function() reattach(w) end)
+
+ return w
+end
+
+capi.screen.connect_signal("removed", function(s)
+ for _, wibar in ipairs(wiboxes) do
+ if wibar._screen == s then
+ wibar:remove()
+ end
+ end
+end)
+
+function awfulwibar.mt:__call(...)
+ return awfulwibar.new(...)
+end
+
+--Imported documentation
+
+--- Border width.
+--
+-- **Signal:**
+--
+-- * *property::border_width*
+--
+-- @property border_width
+-- @param integer
+
+--- Border color.
+--
+-- Please note that this property only support string based 24 bit or 32 bit
+-- colors:
+--
+-- Red Blue
+-- _| _|
+-- #FF00FF
+-- T‾
+-- Green
+--
+--
+-- Red Blue
+-- _| _|
+-- #FF00FF00
+-- T‾ ‾T
+-- Green Alpha
+--
+-- **Signal:**
+--
+-- * *property::border_color*
+--
+-- @property border_color
+-- @param string
+
+--- On top of other windows.
+--
+-- **Signal:**
+--
+-- * *property::ontop*
+--
+-- @property ontop
+-- @param boolean
+
+--- The mouse cursor.
+--
+-- **Signal:**
+--
+-- * *property::cursor*
+--
+-- @property cursor
+-- @param string
+-- @see mouse
+
+--- Visibility.
+--
+-- **Signal:**
+--
+-- * *property::visible*
+--
+-- @property visible
+-- @param boolean
+
+--- The opacity of the wibox, between 0 and 1.
+--
+-- **Signal:**
+--
+-- * *property::opacity*
+--
+-- @property opacity
+-- @tparam number opacity (between 0 and 1)
+
+--- The window type (desktop, normal, dock, ...).
+--
+-- **Signal:**
+--
+-- * *property::type*
+--
+-- @property type
+-- @param string
+-- @see client.type
+
+--- The x coordinates.
+--
+-- **Signal:**
+--
+-- * *property::x*
+--
+-- @property x
+-- @param integer
+
+--- The y coordinates.
+--
+-- **Signal:**
+--
+-- * *property::y*
+--
+-- @property y
+-- @param integer
+
+--- The width of the wibox.
+--
+-- **Signal:**
+--
+-- * *property::width*
+--
+-- @property width
+-- @param width
+
+--- The height of the wibox.
+--
+-- **Signal:**
+--
+-- * *property::height*
+--
+-- @property height
+-- @param height
+
+--- The wibox screen.
+--
+-- @property screen
+-- @param screen
+
+--- The wibox's `drawable`.
+--
+-- **Signal:**
+--
+-- * *property::drawable*
+--
+-- @property drawable
+-- @tparam drawable drawable
+
+--- The widget that the `wibox` displays.
+-- @property widget
+-- @param widget
+
+--- The X window id.
+--
+-- **Signal:**
+--
+-- * *property::window*
+--
+-- @property window
+-- @param string
+-- @see client.window
+
+--- The wibox's bounding shape as a (native) cairo surface.
+--
+-- **Signal:**
+--
+-- * *property::shape_bounding*
+--
+-- @property shape_bounding
+-- @param surface._native
+
+--- The wibox's clip shape as a (native) cairo surface.
+--
+-- **Signal:**
+--
+-- * *property::shape_clip*
+--
+-- @property shape_clip
+-- @param surface._native
+
+--- Get or set mouse buttons bindings to a wibox.
+--
+-- @param buttons_table A table of buttons objects, or nothing.
+-- @function buttons
+
+--- Get or set wibox geometry. That's the same as accessing or setting the x,
+-- y, width or height properties of a wibox.
+--
+-- @param A table with coordinates to modify.
+-- @return A table with wibox coordinates and geometry.
+-- @function geometry
+
+--- Get or set wibox struts.
+--
+-- @param strut A table with new strut, or nothing
+-- @return The wibox strut in a table.
+-- @function struts
+-- @see client.struts
+
+--- The default background color.
+-- @beautiful beautiful.bg_normal
+-- @see bg
+
+--- The default foreground (text) color.
+-- @beautiful beautiful.fg_normal
+-- @see fg
+
+--- Set a declarative widget hierarchy description.
+-- See [The declarative layout system](../documentation/03-declarative-layout.md.html)
+-- @param args An array containing the widgets disposition
+-- @name setup
+-- @class function
+
+--- The background of the wibox.
+-- @param c The background to use. This must either be a cairo pattern object,
+-- nil or a string that gears.color() understands.
+-- @property bg
+-- @see gears.color
+
+--- The background image of the drawable.
+-- If `image` is a function, it will be called with `(context, cr, width, height)`
+-- as arguments. Any other arguments passed to this method will be appended.
+-- @param image A background image or a function
+-- @property bgimage
+-- @see gears.surface
+
+--- The foreground (text) of the wibox.
+-- @param c The foreground to use. This must either be a cairo pattern object,
+-- nil or a string that gears.color() understands.
+-- @property fg
+-- @see gears.color
+
+--- Find a widget by a point.
+-- The wibox must have drawn itself at least once for this to work.
+-- @tparam number x X coordinate of the point
+-- @tparam number y Y coordinate of the point
+-- @treturn table A sorted table of widgets positions. The first element is the biggest
+-- container while the last is the topmost widget. The table contains *x*, *y*,
+-- *width*, *height* and *widget*.
+-- @name find_widgets
+-- @class function
+
+
+return setmetatable(awfulwibar, awfulwibar.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/wibox.lua b/lib/awful/wibox.lua
new file mode 100644
index 0000000..e8c7c7f
--- /dev/null
+++ b/lib/awful/wibox.lua
@@ -0,0 +1,12 @@
+---------------------------------------------------------------------------
+--- This module is deprecated and has been renamed `awful.wibar`
+--
+-- @author Emmanuel Lepage Vallee &lt;elv1313@gmail.com&gt;
+-- @copyright 2016 Emmanuel Lepage Vallee
+-- @module awful.wibox
+---------------------------------------------------------------------------
+local util = require("awful.util")
+
+return util.deprecate_class(require("awful.wibar"), "awful.wibox", "awful.wibar")
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/widget/button.lua b/lib/awful/widget/button.lua
new file mode 100644
index 0000000..388f9dd
--- /dev/null
+++ b/lib/awful/widget/button.lua
@@ -0,0 +1,263 @@
+---------------------------------------------------------------------------
+-- A simple button widget.
+-- @usage local button = awful.widget.button()
+-- button:buttons(awful.util.table.join(
+-- button:buttons(),
+-- awful.button({}, 1, nil, function ()
+-- print("Mouse was clicked")
+-- end)
+-- ))
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2008-2009 Julien Danjou
+-- @classmod awful.widget.button
+---------------------------------------------------------------------------
+
+local setmetatable = setmetatable
+local abutton = require("awful.button")
+local imagebox = require("wibox.widget.imagebox")
+local widget = require("wibox.widget.base")
+local surface = require("gears.surface")
+local cairo = require("lgi").cairo
+
+local button = { mt = {} }
+
+--- Create a button widget. When clicked, the image is deplaced to make it like
+-- a real button.
+--
+-- @param args Widget arguments. "image" is the image to display.
+-- @return A textbox widget configured as a button.
+function button.new(args)
+ if not args or not args.image then
+ return widget.empty_widget()
+ end
+
+ local w = imagebox()
+ local orig_set_image = w.set_image
+ local img_release
+ local img_press
+
+ function w:set_image(image)
+ img_release = surface.load(image)
+ img_press = img_release:create_similar(cairo.Content.COLOR_ALPHA, img_release.width, img_release.height)
+ local cr = cairo.Context(img_press)
+ cr:set_source_surface(img_release, 2, 2)
+ cr:paint()
+ orig_set_image(self, img_release)
+ end
+ w:set_image(args.image)
+ w:buttons(abutton({}, 1, function () orig_set_image(w, img_press) end, function () orig_set_image(w, img_release) end))
+
+ w:connect_signal("mouse::leave", function(self) orig_set_image(self, img_release) end)
+
+ return w
+end
+
+function button.mt:__call(...)
+ return button.new(...)
+end
+
+--Imported documentation
+
+
+--- Get a widex index.
+-- @param widget The widget to look for
+-- @param[opt] recursive Also check sub-widgets
+-- @param[opt] ... Aditional widgets to add at the end of the \"path\"
+-- @return The index
+-- @return The parent layout
+-- @return The path between \"self\" and \"widget\"
+-- @function index
+
+--- Get all direct and indirect children widgets.
+-- This will scan all containers recursively to find widgets
+-- Warning: This method it prone to stack overflow id the widget, or any of its
+-- children, contain (directly or indirectly) itself.
+-- @treturn table The children
+-- @function get_all_children
+
+--- Set a declarative widget hierarchy description.
+-- See [The declarative layout system](../documentation/03-declarative-layout.md.html)
+-- @param args An array containing the widgets disposition
+-- @function setup
+
+--- Force a widget height.
+-- @property forced_height
+-- @tparam number|nil height The height (`nil` for automatic)
+
+--- Force a widget width.
+-- @property forced_width
+-- @tparam number|nil width The width (`nil` for automatic)
+
+--- The widget opacity (transparency).
+-- @property opacity
+-- @tparam[opt=1] number opacity The opacity (between 0 and 1)
+
+--- The widget visibility.
+-- @property visible
+-- @param boolean
+
+--- Set/get a widget's buttons.
+-- @param _buttons The table of buttons that should bind to the widget.
+-- @function buttons
+
+--- Emit a signal and ensure all parent widgets in the hierarchies also
+-- forward the signal. This is useful to track signals when there is a dynamic
+-- set of containers and layouts wrapping the widget.
+-- @tparam string signal_name
+-- @param ... Other arguments
+-- @function emit_signal_recursive
+
+--- When the layout (size) change.
+-- This signal is emitted when the previous results of `:layout()` and `:fit()`
+-- are no longer valid. Unless this signal is emitted, `:layout()` and `:fit()`
+-- must return the same result when called with the same arguments.
+-- @signal widget::layout_changed
+-- @see widget::redraw_needed
+
+--- When the widget content changed.
+-- This signal is emitted when the content of the widget changes. The widget will
+-- be redrawn, it is not re-layouted. Put differently, it is assumed that
+-- `:layout()` and `:fit()` would still return the same results as before.
+-- @signal widget::redraw_needed
+-- @see widget::layout_changed
+
+--- When a mouse button is pressed over the widget.
+-- @signal button::press
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When a mouse button is released over the widget.
+-- @signal button::release
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse enter a widget.
+-- @signal mouse::enter
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse leave a widget.
+-- @signal mouse::leave
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+
+--Imported documentation
+
+
+--- Disconnect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback that should be disconnected
+-- @function disconnect_signal
+
+--- Emit a signal.
+--
+-- @tparam string name The name of the signal
+-- @param ... Extra arguments for the callback functions. Each connected
+-- function receives the object as first argument and then any extra arguments
+-- that are given to emit_signal()
+-- @function emit_signal
+
+--- Connect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function connect_signal
+
+--- Connect to a signal weakly. This allows the callback function to be garbage
+-- collected and automatically disconnects the signal when that happens.
+--
+-- **Warning:**
+-- Only use this function if you really, really, really know what you
+-- are doing.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function weak_connect_signal
+
+
+return setmetatable(button, button.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/widget/common.lua b/lib/awful/widget/common.lua
new file mode 100644
index 0000000..e9ae699
--- /dev/null
+++ b/lib/awful/widget/common.lua
@@ -0,0 +1,119 @@
+---------------------------------------------------------------------------
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2008-2009 Julien Danjou
+-- @classmod awful.widget.common
+---------------------------------------------------------------------------
+
+-- Grab environment we need
+local type = type
+local ipairs = ipairs
+local capi = { button = button }
+local wibox = require("wibox")
+local dpi = require("beautiful").xresources.apply_dpi
+
+--- Common utilities for awful widgets
+local common = {}
+
+--- Common method to create buttons.
+-- @tab buttons
+-- @param object
+-- @treturn table
+function common.create_buttons(buttons, object)
+ if buttons then
+ local btns = {}
+ for _, b in ipairs(buttons) do
+ -- Create a proxy button object: it will receive the real
+ -- press and release events, and will propagate them to the
+ -- button object the user provided, but with the object as
+ -- argument.
+ local btn = capi.button { modifiers = b.modifiers, button = b.button }
+ btn:connect_signal("press", function () b:emit_signal("press", object) end)
+ btn:connect_signal("release", function () b:emit_signal("release", object) end)
+ btns[#btns + 1] = btn
+ end
+
+ return btns
+ end
+end
+
+--- Common update method.
+-- @param w The widget.
+-- @tab buttons
+-- @func label Function to generate label parameters from an object.
+-- The function gets passed an object from `objects`, and
+-- has to return `text`, `bg`, `bg_image`, `icon`.
+-- @tab data Current data/cache, indexed by objects.
+-- @tab objects Objects to be displayed / updated.
+function common.list_update(w, buttons, label, data, objects)
+ -- update the widgets, creating them if needed
+ w:reset()
+ for i, o in ipairs(objects) do
+ local cache = data[o]
+ local ib, tb, bgb, tbm, ibm, l
+ if cache then
+ ib = cache.ib
+ tb = cache.tb
+ bgb = cache.bgb
+ tbm = cache.tbm
+ ibm = cache.ibm
+ else
+ ib = wibox.widget.imagebox()
+ tb = wibox.widget.textbox()
+ bgb = wibox.container.background()
+ tbm = wibox.container.margin(tb, dpi(4), dpi(4))
+ ibm = wibox.container.margin(ib, dpi(4))
+ l = wibox.layout.fixed.horizontal()
+
+ -- All of this is added in a fixed widget
+ l:fill_space(true)
+ l:add(ibm)
+ l:add(tbm)
+
+ -- And all of this gets a background
+ bgb:set_widget(l)
+
+ bgb:buttons(common.create_buttons(buttons, o))
+
+ data[o] = {
+ ib = ib,
+ tb = tb,
+ bgb = bgb,
+ tbm = tbm,
+ ibm = ibm,
+ }
+ end
+
+ local text, bg, bg_image, icon, args = label(o, tb)
+ args = args or {}
+
+ -- The text might be invalid, so use pcall.
+ if text == nil or text == "" then
+ tbm:set_margins(0)
+ else
+ if not tb:set_markup_silently(text) then
+ tb:set_markup("<i>&lt;Invalid text&gt;</i>")
+ end
+ end
+ bgb:set_bg(bg)
+ if type(bg_image) == "function" then
+ -- TODO: Why does this pass nil as an argument?
+ bg_image = bg_image(tb,o,nil,objects,i)
+ end
+ bgb:set_bgimage(bg_image)
+ if icon then
+ ib:set_image(icon)
+ else
+ ibm:set_margins(0)
+ end
+
+ bgb.shape = args.shape
+ bgb.shape_border_width = args.shape_border_width
+ bgb.shape_border_color = args.shape_border_color
+
+ w:add(bgb)
+ end
+end
+
+return common
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/widget/graph.lua b/lib/awful/widget/graph.lua
new file mode 100644
index 0000000..e231463
--- /dev/null
+++ b/lib/awful/widget/graph.lua
@@ -0,0 +1,16 @@
+---------------------------------------------------------------------------
+--- This module has been moved to `wibox.widget.graph`
+--
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2009 Julien Danjou
+-- @classmod awful.widget.graph
+---------------------------------------------------------------------------
+local util = require("awful.util")
+
+return util.deprecate_class(
+ require("wibox.widget.graph"),
+ "awful.widget.graph",
+ "wibox.widget.graph"
+)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/widget/init.lua b/lib/awful/widget/init.lua
new file mode 100644
index 0000000..02400a1
--- /dev/null
+++ b/lib/awful/widget/init.lua
@@ -0,0 +1,24 @@
+---------------------------------------------------------------------------
+--- Widget module for awful
+--
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2008-2009 Julien Danjou
+-- @classmod awful.widget
+---------------------------------------------------------------------------
+
+return
+{
+ taglist = require("awful.widget.taglist");
+ tasklist = require("awful.widget.tasklist");
+ button = require("awful.widget.button");
+ launcher = require("awful.widget.launcher");
+ prompt = require("awful.widget.prompt");
+ progressbar = require("awful.widget.progressbar");
+ graph = require("awful.widget.graph");
+ layoutbox = require("awful.widget.layoutbox");
+ textclock = require("awful.widget.textclock");
+ keyboardlayout = require("awful.widget.keyboardlayout");
+ watch = require("awful.widget.watch");
+}
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/widget/keyboardlayout.lua b/lib/awful/widget/keyboardlayout.lua
new file mode 100644
index 0000000..4f79a81
--- /dev/null
+++ b/lib/awful/widget/keyboardlayout.lua
@@ -0,0 +1,308 @@
+---------------------------------------------------------------------------
+-- @author Aleksey Fedotov &lt;lexa@cfotr.com&gt;
+-- @copyright 2015 Aleksey Fedotov
+-- @classmod wibox.widget.keyboardlayout
+---------------------------------------------------------------------------
+
+local capi = {awesome = awesome}
+local setmetatable = setmetatable
+local textbox = require("wibox.widget.textbox")
+local button = require("awful.button")
+local util = require("awful.util")
+local widget_base = require("wibox.widget.base")
+local gdebug = require("gears.debug")
+
+--- Keyboard Layout widget.
+-- awful.widget.keyboardlayout
+local keyboardlayout = { mt = {} }
+
+-- As to the country-code-like symbols below, refer to the names of XKB's
+-- data files in /.../xkb/symbols/*.
+keyboardlayout.xkeyboard_country_code = {
+ ["ad"] = true, -- Andorra
+ ["af"] = true, -- Afganistan
+ ["al"] = true, -- Albania
+ ["am"] = true, -- Armenia
+ ["ara"] = true, -- Arabic
+ ["at"] = true, -- Austria
+ ["az"] = true, -- Azerbaijan
+ ["ba"] = true, -- Bosnia and Herzegovina
+ ["bd"] = true, -- Bangladesh
+ ["be"] = true, -- Belgium
+ ["bg"] = true, -- Bulgaria
+ ["br"] = true, -- Brazil
+ ["bt"] = true, -- Bhutan
+ ["bw"] = true, -- Botswana
+ ["by"] = true, -- Belarus
+ ["ca"] = true, -- Canada
+ ["cd"] = true, -- Congo
+ ["ch"] = true, -- Switzerland
+ ["cm"] = true, -- Cameroon
+ ["cn"] = true, -- China
+ ["cz"] = true, -- Czechia
+ ["de"] = true, -- Germany
+ ["dk"] = true, -- Denmark
+ ["ee"] = true, -- Estonia
+ ["epo"] = true, -- Esperanto
+ ["es"] = true, -- Spain
+ ["et"] = true, -- Ethiopia
+ ["fi"] = true, -- Finland
+ ["fo"] = true, -- Faroe Islands
+ ["fr"] = true, -- France
+ ["gb"] = true, -- United Kingdom
+ ["ge"] = true, -- Georgia
+ ["gh"] = true, -- Ghana
+ ["gn"] = true, -- Guinea
+ ["gr"] = true, -- Greece
+ ["hr"] = true, -- Croatia
+ ["hu"] = true, -- Hungary
+ ["ie"] = true, -- Ireland
+ ["il"] = true, -- Israel
+ ["in"] = true, -- India
+ ["iq"] = true, -- Iraq
+ ["ir"] = true, -- Iran
+ ["is"] = true, -- Iceland
+ ["it"] = true, -- Italy
+ ["jp"] = true, -- Japan
+ ["ke"] = true, -- Kenya
+ ["kg"] = true, -- Kyrgyzstan
+ ["kh"] = true, -- Cambodia
+ ["kr"] = true, -- Korea
+ ["kz"] = true, -- Kazakhstan
+ ["la"] = true, -- Laos
+ ["latam"] = true, -- Latin America
+ ["latin"] = true, -- Latin
+ ["lk"] = true, -- Sri Lanka
+ ["lt"] = true, -- Lithuania
+ ["lv"] = true, -- Latvia
+ ["ma"] = true, -- Morocco
+ ["mao"] = true, -- Maori
+ ["me"] = true, -- Montenegro
+ ["mk"] = true, -- Macedonia
+ ["ml"] = true, -- Mali
+ ["mm"] = true, -- Myanmar
+ ["mn"] = true, -- Mongolia
+ ["mt"] = true, -- Malta
+ ["mv"] = true, -- Maldives
+ ["ng"] = true, -- Nigeria
+ ["nl"] = true, -- Netherlands
+ ["no"] = true, -- Norway
+ ["np"] = true, -- Nepal
+ ["ph"] = true, -- Philippines
+ ["pk"] = true, -- Pakistan
+ ["pl"] = true, -- Poland
+ ["pt"] = true, -- Portugal
+ ["ro"] = true, -- Romania
+ ["rs"] = true, -- Serbia
+ ["ru"] = true, -- Russia
+ ["se"] = true, -- Sweden
+ ["si"] = true, -- Slovenia
+ ["sk"] = true, -- Slovakia
+ ["sn"] = true, -- Senegal
+ ["sy"] = true, -- Syria
+ ["th"] = true, -- Thailand
+ ["tj"] = true, -- Tajikistan
+ ["tm"] = true, -- Turkmenistan
+ ["tr"] = true, -- Turkey
+ ["tw"] = true, -- Taiwan
+ ["tz"] = true, -- Tanzania
+ ["ua"] = true, -- Ukraine
+ ["us"] = true, -- USA
+ ["uz"] = true, -- Uzbekistan
+ ["vn"] = true, -- Vietnam
+ ["za"] = true, -- South Africa
+}
+
+-- Callback for updating current layout.
+local function update_status (self)
+ self._current = awesome.xkb_get_layout_group();
+ local text = ""
+ if (#self._layout > 0) then
+ text = (" " .. self._layout[self._current] .. " ")
+ end
+ self.widget:set_text(text)
+end
+
+--- Auxiliary function for the local function update_layout().
+-- Create an array whose element is a table consisting of the four fields:
+-- vendor, file, section and group_idx, which all correspond to the
+-- xkb_symbols pattern "vendor/file(section):group_idx".
+-- @tparam string group_names The string awesome.xkb_get_group_names() returns.
+-- @treturn table An array of tables whose keys are vendor, file, section, and group_idx.
+function keyboardlayout.get_groups_from_group_names(group_names)
+ if group_names == nil then
+ return nil
+ end
+
+ -- Pattern elements to be captured.
+ local word_pat = "([%w_]+)"
+ local sec_pat = "(%b())"
+ local idx_pat = ":(%d)"
+ -- Pairs of a pattern and its callback. In callbacks, set 'group_idx' to 1
+ -- and return it if there's no specification on 'group_idx' in the given
+ -- pattern.
+ local pattern_and_callback_pairs = {
+ -- vendor/file(section):group_idx
+ ["^" .. word_pat .. "/" .. word_pat .. sec_pat .. idx_pat .. "$"]
+ = function(token, pattern)
+ local vendor, file, section, group_idx = string.match(token, pattern)
+ return vendor, file, section, group_idx
+ end,
+ -- vendor/file(section)
+ ["^" .. word_pat .. "/" .. word_pat .. sec_pat .. "$"]
+ = function(token, pattern)
+ local vendor, file, section = string.match(token, pattern)
+ return vendor, file, section, 1
+ end,
+ -- vendor/file:group_idx
+ ["^" .. word_pat .. "/" .. word_pat .. idx_pat .. "$"]
+ = function(token, pattern)
+ local vendor, file, group_idx = string.match(token, pattern)
+ return vendor, file, nil, group_idx
+ end,
+ -- vendor/file
+ ["^" .. word_pat .. "/" .. word_pat .. "$"]
+ = function(token, pattern)
+ local vendor, file = string.match(token, pattern)
+ return vendor, file, nil, 1
+ end,
+ -- file(section):group_idx
+ ["^" .. word_pat .. sec_pat .. idx_pat .. "$"]
+ = function(token, pattern)
+ local file, section, group_idx = string.match(token, pattern)
+ return nil, file, section, group_idx
+ end,
+ -- file(section)
+ ["^" .. word_pat .. sec_pat .. "$"]
+ = function(token, pattern)
+ local file, section = string.match(token, pattern)
+ return nil, file, section, 1
+ end,
+ -- file:group_idx
+ ["^" .. word_pat .. idx_pat .. "$"]
+ = function(token, pattern)
+ local file, group_idx = string.match(token, pattern)
+ return nil, file, nil, group_idx
+ end,
+ -- file
+ ["^" .. word_pat .. "$"]
+ = function(token, pattern)
+ local file = string.match(token, pattern)
+ return nil, file, nil, 1
+ end
+ }
+
+ -- Split 'group_names' into 'tokens'. The separator is "+".
+ local tokens = {}
+ string.gsub(group_names, "[^+]+", function(match)
+ table.insert(tokens, match)
+ end)
+
+ -- For each token in 'tokens', check if it matches one of the patterns in
+ -- the array 'pattern_and_callback_pairs', where the patterns are used as
+ -- key. If a match is found, extract captured strings using the
+ -- corresponding callback function. Check if those extracted is country
+ -- specific part of a layout. If so, add it to 'layout_groups'; otherwise,
+ -- ignore it.
+ local layout_groups = {}
+ for i = 1, #tokens do
+ for pattern, callback in pairs(pattern_and_callback_pairs) do
+ local vendor, file, section, group_idx = callback(tokens[i], pattern)
+ if file then
+ if not keyboardlayout.xkeyboard_country_code[file] then
+ break
+ end
+
+ if section then
+ section = string.gsub(section, "%(([%w-_]+)%)", "%1")
+ end
+
+ table.insert(layout_groups, { vendor = vendor,
+ file = file,
+ section = section,
+ group_idx = tonumber(group_idx) })
+ break
+ end
+ end
+ end
+
+ return layout_groups
+end
+
+-- Callback for updating list of layouts
+local function update_layout(self)
+ self._layout = {};
+ local layouts = keyboardlayout.get_groups_from_group_names(awesome.xkb_get_group_names())
+ if layouts == nil or layouts[1] == nil then
+ gdebug.print_error("Failed to get list of keyboard groups")
+ return
+ end
+ if #layouts == 1 then
+ layouts[1].group_idx = 0
+ end
+ for _, v in ipairs(layouts) do
+ local layout_name = self.layout_name(v)
+ -- Please note that numbers of groups reported by xkb_get_group_names
+ -- is greater by one than the real group number.
+ self._layout[v.group_idx - 1] = layout_name
+ end
+ update_status(self)
+end
+
+--- Create a keyboard layout widget. It shows current keyboard layout name in a textbox.
+-- @return A keyboard layout widget.
+function keyboardlayout.new()
+ local widget = textbox()
+ local self = widget_base.make_widget(widget)
+
+ self.widget = widget
+
+ self.layout_name = function(v)
+ local name = v.file
+ if v.section ~= nil then
+ name = name .. "(" .. v.section .. ")"
+ end
+ return name
+ end
+
+ self.next_layout = function()
+ self.set_layout((self._current + 1) % (#self._layout + 1))
+ end
+
+ self.set_layout = function(group_number)
+ if (0 > group_number) or (group_number > #self._layout) then
+ error("Invalid group number: " .. group_number ..
+ "expected number from 0 to " .. #self._layout)
+ return;
+ end
+ awesome.xkb_set_layout_group(group_number);
+ end
+
+ update_layout(self);
+
+ -- callback for processing layout changes
+ capi.awesome.connect_signal("xkb::map_changed",
+ function () update_layout(self) end)
+ capi.awesome.connect_signal("xkb::group_changed",
+ function () update_status(self) end);
+
+ -- Mouse bindings
+ self:buttons(
+ util.table.join(button({ }, 1, self.next_layout))
+ )
+
+ return self
+end
+
+local _instance = nil;
+
+function keyboardlayout.mt:__call(...)
+ if _instance == nil then
+ _instance = keyboardlayout.new(...)
+ end
+ return _instance
+end
+
+return setmetatable(keyboardlayout, keyboardlayout.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/widget/launcher.lua b/lib/awful/widget/launcher.lua
new file mode 100644
index 0000000..a944908
--- /dev/null
+++ b/lib/awful/widget/launcher.lua
@@ -0,0 +1,41 @@
+---------------------------------------------------------------------------
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2008-2009 Julien Danjou
+-- @classmod awful.widget.launcher
+---------------------------------------------------------------------------
+
+local setmetatable = setmetatable
+local util = require("awful.util")
+local spawn = require("awful.spawn")
+local wbutton = require("awful.widget.button")
+local button = require("awful.button")
+
+local launcher = { mt = {} }
+
+--- Create a button widget which will launch a command.
+-- @param args Standard widget table arguments, plus image for the image path
+-- and command for the command to run on click, or either menu to create menu.
+-- @return A launcher widget.
+function launcher.new(args)
+ if not args.command and not args.menu then return end
+ local w = wbutton(args)
+ if not w then return end
+
+ local b
+ if args.command then
+ b = util.table.join(w:buttons(), button({}, 1, nil, function () spawn(args.command) end))
+ elseif args.menu then
+ b = util.table.join(w:buttons(), button({}, 1, nil, function () args.menu:toggle() end))
+ end
+
+ w:buttons(b)
+ return w
+end
+
+function launcher.mt:__call(...)
+ return launcher.new(...)
+end
+
+return setmetatable(launcher, launcher.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/widget/layoutbox.lua b/lib/awful/widget/layoutbox.lua
new file mode 100644
index 0000000..c5f8ed3
--- /dev/null
+++ b/lib/awful/widget/layoutbox.lua
@@ -0,0 +1,73 @@
+---------------------------------------------------------------------------
+--- Layoutbox widget.
+--
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2009 Julien Danjou
+-- @classmod awful.widget.layoutbox
+---------------------------------------------------------------------------
+
+local setmetatable = setmetatable
+local capi = { screen = screen, tag = tag }
+local layout = require("awful.layout")
+local tooltip = require("awful.tooltip")
+local beautiful = require("beautiful")
+local imagebox = require("wibox.widget.imagebox")
+
+local function get_screen(s)
+ return s and capi.screen[s]
+end
+
+local layoutbox = { mt = {} }
+
+local boxes = nil
+
+local function update(w, screen)
+ screen = get_screen(screen)
+ local name = layout.getname(layout.get(screen))
+ w._layoutbox_tooltip:set_text(name or "[no name]")
+ w:set_image(name and beautiful["layout_" .. name])
+end
+
+local function update_from_tag(t)
+ local screen = get_screen(t.screen)
+ local w = boxes[screen]
+ if w then
+ update(w, screen)
+ end
+end
+
+--- Create a layoutbox widget. It draws a picture with the current layout
+-- symbol of the current tag.
+-- @param screen The screen number that the layout will be represented for.
+-- @return An imagebox widget configured as a layoutbox.
+function layoutbox.new(screen)
+ screen = get_screen(screen or 1)
+
+ -- Do we already have the update callbacks registered?
+ if boxes == nil then
+ boxes = setmetatable({}, { __mode = "kv" })
+ capi.tag.connect_signal("property::selected", update_from_tag)
+ capi.tag.connect_signal("property::layout", update_from_tag)
+ layoutbox.boxes = boxes
+ end
+
+ -- Do we already have a layoutbox for this screen?
+ local w = boxes[screen]
+ if not w then
+ w = imagebox()
+ w._layoutbox_tooltip = tooltip {objects = {w}, delay_show = 1}
+
+ update(w, screen)
+ boxes[screen] = w
+ end
+
+ return w
+end
+
+function layoutbox.mt:__call(...)
+ return layoutbox.new(...)
+end
+
+return setmetatable(layoutbox, layoutbox.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/widget/progressbar.lua b/lib/awful/widget/progressbar.lua
new file mode 100644
index 0000000..11147f4
--- /dev/null
+++ b/lib/awful/widget/progressbar.lua
@@ -0,0 +1,16 @@
+---------------------------------------------------------------------------
+--- This module has been moved to `wibox.widget.progressbar`
+--
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2009 Julien Danjou
+-- @classmod awful.widget.progressbar
+---------------------------------------------------------------------------
+local util = require("awful.util")
+
+return util.deprecate_class(
+ require("wibox.widget.progressbar"),
+ "awful.widget.progressbar",
+ "wibox.widget.progressbar"
+)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/widget/prompt.lua b/lib/awful/widget/prompt.lua
new file mode 100644
index 0000000..ff9d904
--- /dev/null
+++ b/lib/awful/widget/prompt.lua
@@ -0,0 +1,64 @@
+---------------------------------------------------------------------------
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2009 Julien Danjou
+-- @classmod awful.widget.prompt
+---------------------------------------------------------------------------
+
+local setmetatable = setmetatable
+
+local completion = require("awful.completion")
+local util = require("awful.util")
+local spawn = require("awful.spawn")
+local prompt = require("awful.prompt")
+local widget_base = require("wibox.widget.base")
+local textbox = require("wibox.widget.textbox")
+local type = type
+
+local widgetprompt = { mt = {} }
+
+--- Run method for promptbox.
+--
+-- @param promptbox The promptbox to run.
+local function run(promptbox)
+ return prompt.run {
+ prompt = promptbox.prompt,
+ textbox = promptbox.widget,
+ completion_callback = completion.shell,
+ history_path = util.get_cache_dir() .. "/history",
+ exe_callback = function (...)
+ promptbox:spawn_and_handle_error(...)
+ end,
+ }
+end
+
+local function spawn_and_handle_error(self, ...)
+ local result = spawn(...)
+ if type(result) == "string" then
+ self.widget:set_text(result)
+ end
+end
+
+--- Create a prompt widget which will launch a command.
+--
+-- @param args Arguments table. "prompt" is the prompt to use.
+-- @return A launcher widget.
+function widgetprompt.new(args)
+ args = args or {}
+ local widget = textbox()
+ local promptbox = widget_base.make_widget(widget)
+
+ promptbox.widget = widget
+ promptbox.widget:set_ellipsize("start")
+ promptbox.run = run
+ promptbox.spawn_and_handle_error = spawn_and_handle_error
+ promptbox.prompt = args.prompt or "Run: "
+ return promptbox
+end
+
+function widgetprompt.mt:__call(...)
+ return widgetprompt.new(...)
+end
+
+return setmetatable(widgetprompt, widgetprompt.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/widget/taglist.lua b/lib/awful/widget/taglist.lua
new file mode 100644
index 0000000..d8cf475
--- /dev/null
+++ b/lib/awful/widget/taglist.lua
@@ -0,0 +1,452 @@
+---------------------------------------------------------------------------
+--- Taglist widget module for awful
+--
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2008-2009 Julien Danjou
+-- @classmod awful.widget.taglist
+---------------------------------------------------------------------------
+
+-- Grab environment we need
+local capi = { screen = screen,
+ awesome = awesome,
+ client = client }
+local setmetatable = setmetatable
+local pairs = pairs
+local ipairs = ipairs
+local table = table
+local common = require("awful.widget.common")
+local util = require("awful.util")
+local tag = require("awful.tag")
+local beautiful = require("beautiful")
+local fixed = require("wibox.layout.fixed")
+local surface = require("gears.surface")
+local timer = require("gears.timer")
+
+local function get_screen(s)
+ return s and capi.screen[s]
+end
+
+local taglist = { mt = {} }
+taglist.filter = {}
+
+--- The tag list main foreground (text) color.
+-- @beautiful beautiful.taglist_fg_focus
+-- @param[opt=fg_focus] color
+-- @see gears.color
+
+--- The tag list main background color.
+-- @beautiful beautiful.taglist_bg_focus
+-- @param[opt=bg_focus] color
+-- @see gears.color
+
+--- The tag list urgent elements foreground (text) color.
+-- @beautiful beautiful.taglist_fg_urgent
+-- @param[opt=fg_urgent] color
+-- @see gears.color
+
+--- The tag list urgent elements background color.
+-- @beautiful beautiful.taglist_bg_urgent
+-- @param[opt=bg_urgent] color
+-- @see gears.color
+
+--- The tag list occupied elements background color.
+-- @beautiful beautiful.taglist_bg_occupied
+-- @param color
+-- @see gears.color
+
+--- The tag list occupied elements foreground (text) color.
+-- @beautiful beautiful.taglist_fg_occupied
+-- @param color
+-- @see gears.color
+
+--- The tag list empty elements background color.
+-- @beautiful beautiful.taglist_bg_empty
+-- @param color
+-- @see gears.color
+
+--- The tag list empty elements foreground (text) color.
+-- @beautiful beautiful.taglist_fg_empty
+-- @param color
+-- @see gears.color
+
+--- The selected elements background image.
+-- @beautiful beautiful.taglist_squares_sel
+-- @param surface
+-- @see gears.surface
+
+--- The unselected elements background image.
+-- @beautiful beautiful.taglist_squares_unsel
+-- @param surface
+-- @see gears.surface
+
+--- The selected empty elements background image.
+-- @beautiful beautiful.taglist_squares_sel_empty
+-- @param surface
+-- @see gears.surface
+
+--- The unselected empty elements background image.
+-- @beautiful beautiful.taglist_squares_unsel_empty
+-- @param surface
+-- @see gears.surface
+
+--- If the background images can be resized.
+-- @beautiful beautiful.taglist_squares_resize
+-- @param boolean
+
+--- Do not display the tag icons, even if they are set.
+-- @beautiful beautiful.taglist_disable_icon
+-- @param boolean
+
+--- The taglist font.
+-- @beautiful beautiful.taglist_font
+-- @param string
+
+--- The main shape used for the elements.
+-- This will be the fallback for state specific shapes.
+-- To get a shape for the whole taglist, use `wibox.container.background`.
+-- @beautiful beautiful.taglist_shape
+-- @param[opt=rectangle] gears.shape
+-- @see gears.shape
+-- @see beautiful.taglist_shape_empty
+-- @see beautiful.taglist_shape_focus
+-- @see beautiful.taglist_shape_urgent
+
+--- The shape elements border width.
+-- @beautiful beautiful.taglist_shape_border_width
+-- @param[opt=0] number
+-- @see wibox.container.background
+
+--- The elements shape border color.
+-- @beautiful beautiful.taglist_shape_border_color
+-- @param color
+-- @see gears.color
+
+--- The shape used for the empty elements.
+-- @beautiful beautiful.taglist_shape_empty
+-- @param[opt=rectangle] gears.shape
+-- @see gears.shape
+
+--- The shape used for the empty elements border width.
+-- @beautiful beautiful.taglist_shape_border_width_empty
+-- @param[opt=0] number
+-- @see wibox.container.background
+
+--- The empty elements shape border color.
+-- @beautiful beautiful.taglist_shape_border_color_empty
+-- @param color
+-- @see gears.color
+
+--- The shape used for the selected elements.
+-- @beautiful beautiful.taglist_shape_focus
+-- @param[opt=rectangle] gears.shape
+-- @see gears.shape
+
+--- The shape used for the selected elements border width.
+-- @beautiful beautiful.taglist_shape_border_width_focus
+-- @param[opt=0] number
+-- @see wibox.container.background
+
+--- The selected elements shape border color.
+-- @beautiful beautiful.taglist_shape_border_color_focus
+-- @param color
+-- @see gears.color
+
+--- The shape used for the urgent elements.
+-- @beautiful beautiful.taglist_shape_urgent
+-- @param[opt=rectangle] gears.shape
+-- @see gears.shape
+
+--- The shape used for the urgent elements border width.
+-- @beautiful beautiful.taglist_shape_border_width_urgent
+-- @param[opt=0] number
+-- @see wibox.container.background
+
+--- The urgents elements shape border color.
+-- @beautiful beautiful.taglist_shape_border_color_urgent
+-- @param color
+-- @see gears.color
+
+local instances = nil
+
+function taglist.taglist_label(t, args)
+ if not args then args = {} end
+ local theme = beautiful.get()
+ local fg_focus = args.fg_focus or theme.taglist_fg_focus or theme.fg_focus
+ local bg_focus = args.bg_focus or theme.taglist_bg_focus or theme.bg_focus
+ local fg_urgent = args.fg_urgent or theme.taglist_fg_urgent or theme.fg_urgent
+ local bg_urgent = args.bg_urgent or theme.taglist_bg_urgent or theme.bg_urgent
+ local bg_occupied = args.bg_occupied or theme.taglist_bg_occupied
+ local fg_occupied = args.fg_occupied or theme.taglist_fg_occupied
+ local bg_empty = args.bg_empty or theme.taglist_bg_empty
+ local fg_empty = args.fg_empty or theme.taglist_fg_empty
+ local taglist_squares_sel = args.squares_sel or theme.taglist_squares_sel
+ local taglist_squares_unsel = args.squares_unsel or theme.taglist_squares_unsel
+ local taglist_squares_sel_empty = args.squares_sel_empty or theme.taglist_squares_sel_empty
+ local taglist_squares_unsel_empty = args.squares_unsel_empty or theme.taglist_squares_unsel_empty
+ local taglist_squares_resize = theme.taglist_squares_resize or args.squares_resize or "true"
+ local taglist_disable_icon = args.taglist_disable_icon or theme.taglist_disable_icon or false
+ local font = args.font or theme.taglist_font or theme.font or ""
+ local text = nil
+ local sel = capi.client.focus
+ local bg_color = nil
+ local fg_color = nil
+ local bg_image
+ local icon
+ local shape = args.shape or theme.taglist_shape
+ local shape_border_width = args.shape_border_width or theme.taglist_shape_border_width
+ local shape_border_color = args.shape_border_color or theme.taglist_shape_border_color
+ -- TODO: Re-implement bg_resize
+ local bg_resize = false -- luacheck: ignore
+ local is_selected = false
+ local cls = t:clients()
+
+ if sel and taglist_squares_sel then
+ -- Check that the selected client is tagged with 't'.
+ local seltags = sel:tags()
+ for _, v in ipairs(seltags) do
+ if v == t then
+ bg_image = taglist_squares_sel
+ bg_resize = taglist_squares_resize == "true"
+ is_selected = true
+ break
+ end
+ end
+ end
+ if #cls == 0 and t.selected and taglist_squares_sel_empty then
+ bg_image = taglist_squares_sel_empty
+ bg_resize = taglist_squares_resize == "true"
+ elseif not is_selected then
+ if #cls > 0 then
+ if taglist_squares_unsel then
+ bg_image = taglist_squares_unsel
+ bg_resize = taglist_squares_resize == "true"
+ end
+ if bg_occupied then bg_color = bg_occupied end
+ if fg_occupied then fg_color = fg_occupied end
+ else
+ if taglist_squares_unsel_empty then
+ bg_image = taglist_squares_unsel_empty
+ bg_resize = taglist_squares_resize == "true"
+ end
+ if bg_empty then bg_color = bg_empty end
+ if fg_empty then fg_color = fg_empty end
+
+ if args.shape_empty or theme.taglist_shape_empty then
+ shape = args.shape_empty or theme.taglist_shape_empty
+ end
+
+ if args.shape_border_width_empty or theme.taglist_shape_border_width_empty then
+ shape_border_width = args.shape_border_width_empty or theme.taglist_shape_border_width_empty
+ end
+
+ if args.shape_border_color_empty or theme.taglist_shape_border_color_empty then
+ shape_border_color = args.shape_border_color_empty or theme.taglist_shape_border_color_empty
+ end
+ end
+ end
+ if t.selected then
+ bg_color = bg_focus
+ fg_color = fg_focus
+
+ if args.shape_focus or theme.taglist_shape_focus then
+ shape = args.shape_focus or theme.taglist_shape_focus
+ end
+
+ if args.shape_border_width_focus or theme.taglist_shape_border_width_focus then
+ shape = args.shape_border_width_focus or theme.taglist_shape_border_width_focus
+ end
+
+ if args.shape_border_color_focus or theme.taglist_shape_border_color_focus then
+ shape = args.shape_border_color_focus or theme.taglist_shape_border_color_focus
+ end
+
+ elseif tag.getproperty(t, "urgent") then
+ if bg_urgent then bg_color = bg_urgent end
+ if fg_urgent then fg_color = fg_urgent end
+
+ if args.shape_urgent or theme.taglist_shape_urgent then
+ shape = args.shape_urgent or theme.taglist_shape_urgent
+ end
+
+ if args.shape_border_width_urgent or theme.taglist_shape_border_width_urgent then
+ shape_border_width = args.shape_border_width_urgent or theme.taglist_shape_border_width_urgent
+ end
+
+ if args.shape_border_color_urgent or theme.taglist_shape_border_color_urgent then
+ shape_border_color = args.shape_border_color_urgent or theme.taglist_shape_border_color_urgent
+ end
+ end
+
+ if not tag.getproperty(t, "icon_only") then
+ text = "<span font_desc='"..font.."'>"
+ if fg_color then
+ text = text .. "<span color='" .. util.ensure_pango_color(fg_color) ..
+ "'>" .. (util.escape(t.name) or "") .. "</span>"
+ else
+ text = text .. (util.escape(t.name) or "")
+ end
+ text = text .. "</span>"
+ end
+ if not taglist_disable_icon then
+ if t.icon then
+ icon = surface.load(t.icon)
+ end
+ end
+
+ local other_args = {
+ shape = shape,
+ shape_border_width = shape_border_width,
+ shape_border_color = shape_border_color,
+ }
+
+ return text, bg_color, bg_image, not taglist_disable_icon and icon or nil, other_args
+end
+
+local function taglist_update(s, w, buttons, filter, data, style, update_function)
+ local tags = {}
+ for _, t in ipairs(s.tags) do
+ if not tag.getproperty(t, "hide") and filter(t) then
+ table.insert(tags, t)
+ end
+ end
+
+ local function label(c) return taglist.taglist_label(c, style) end
+
+ update_function(w, buttons, label, data, tags)
+end
+
+--- Create a new taglist widget. The last two arguments (update_function
+-- and base_widget) serve to customize the layout of the taglist (eg. to
+-- make it vertical). For that, you will need to copy the
+-- awful.widget.common.list_update function, make your changes to it
+-- and pass it as update_function here. Also change the base_widget if the
+-- default is not what you want.
+-- @param screen The screen to draw taglist for.
+-- @param filter Filter function to define what clients will be listed.
+-- @param buttons A table with buttons binding to set.
+-- @tparam[opt={}] table style The style overrides default theme.
+-- @tparam[opt=nil] string|pattern style.fg_focus
+-- @tparam[opt=nil] string|pattern style.bg_focus
+-- @tparam[opt=nil] string|pattern style.fg_urgent
+-- @tparam[opt=nil] string|pattern style.bg_urgent
+-- @tparam[opt=nil] string|pattern style.bg_occupied
+-- @tparam[opt=nil] string|pattern style.fg_occupied
+-- @tparam[opt=nil] string|pattern style.bg_empty
+-- @tparam[opt=nil] string|pattern style.fg_empty
+-- @tparam[opt=nil] string style.taglist_squares_sel
+-- @tparam[opt=nil] string style.taglist_squares_unsel
+-- @tparam[opt=nil] string style.taglist_squares_sel_empty
+-- @tparam[opt=nil] string style.taglist_squares_unsel_empty
+-- @tparam[opt=nil] string style.taglist_squares_resize
+-- @tparam[opt=nil] string style.taglist_disable_icon
+-- @tparam[opt=nil] string style.font
+-- @tparam[opt=nil] number style.spacing The spacing between tags.
+-- @param[opt] update_function Function to create a tag widget on each
+-- update. See `awful.widget.common`.
+-- @param[opt] base_widget Optional container widget for tag widgets. Default
+-- is wibox.layout.fixed.horizontal().
+-- @param base_widget.bg_focus The background color for focused client.
+-- @param base_widget.fg_focus The foreground color for focused client.
+-- @param base_widget.bg_urgent The background color for urgent clients.
+-- @param base_widget.fg_urgent The foreground color for urgent clients.
+-- @param[opt] base_widget.squares_sel A user provided image for selected squares.
+-- @param[opt] base_widget.squares_unsel A user provided image for unselected squares.
+-- @param[opt] base_widget.squares_sel_empty A user provided image for selected squares for empty tags.
+-- @param[opt] base_widget.squares_unsel_empty A user provided image for unselected squares for empty tags.
+-- @param[opt] base_widget.squares_resize True or false to resize squares.
+-- @param base_widget.font The font.
+-- @function awful.taglist
+function taglist.new(screen, filter, buttons, style, update_function, base_widget)
+ screen = get_screen(screen)
+ local uf = update_function or common.list_update
+ local w = base_widget or fixed.horizontal()
+
+ if w.set_spacing and (style and style.spacing or beautiful.taglist_spacing) then
+ w:set_spacing(style and style.spacing or beautiful.taglist_spacing)
+ end
+
+ local data = setmetatable({}, { __mode = 'k' })
+
+ local queued_update = {}
+ function w._do_taglist_update()
+ -- Add a delayed callback for the first update.
+ if not queued_update[screen] then
+ timer.delayed_call(function()
+ if screen.valid then
+ taglist_update(screen, w, buttons, filter, data, style, uf)
+ end
+ queued_update[screen] = false
+ end)
+ queued_update[screen] = true
+ end
+ end
+ if instances == nil then
+ instances = setmetatable({}, { __mode = "k" })
+ local function u(s)
+ local i = instances[get_screen(s)]
+ if i then
+ for _, tlist in pairs(i) do
+ tlist._do_taglist_update()
+ end
+ end
+ end
+ local uc = function (c) return u(c.screen) end
+ local ut = function (t) return u(t.screen) end
+ capi.client.connect_signal("focus", uc)
+ capi.client.connect_signal("unfocus", uc)
+ tag.attached_connect_signal(nil, "property::selected", ut)
+ tag.attached_connect_signal(nil, "property::icon", ut)
+ tag.attached_connect_signal(nil, "property::hide", ut)
+ tag.attached_connect_signal(nil, "property::name", ut)
+ tag.attached_connect_signal(nil, "property::activated", ut)
+ tag.attached_connect_signal(nil, "property::screen", ut)
+ tag.attached_connect_signal(nil, "property::index", ut)
+ tag.attached_connect_signal(nil, "property::urgent", ut)
+ capi.client.connect_signal("property::screen", function(c, old_screen)
+ u(c.screen)
+ u(old_screen)
+ end)
+ 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]
+ if not list then
+ list = setmetatable({}, { __mode = "v" })
+ instances[screen] = list
+ end
+ table.insert(list, w)
+ return w
+end
+
+--- Filtering function to include all nonempty tags on the screen.
+-- @param t The tag.
+-- @return true if t is not empty, else false
+function taglist.filter.noempty(t)
+ return #t:clients() > 0 or t.selected
+end
+
+--- Filtering function to include selected tags on the screen.
+-- @param t The tag.
+-- @return true if t is not empty, else false
+function taglist.filter.selected(t)
+ return t.selected
+end
+
+--- Filtering function to include all tags on the screen.
+-- @return true
+function taglist.filter.all()
+ return true
+end
+
+function taglist.mt:__call(...)
+ return taglist.new(...)
+end
+
+return setmetatable(taglist, taglist.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/widget/tasklist.lua b/lib/awful/widget/tasklist.lua
new file mode 100644
index 0000000..d5580c1
--- /dev/null
+++ b/lib/awful/widget/tasklist.lua
@@ -0,0 +1,573 @@
+---------------------------------------------------------------------------
+--- Tasklist widget module for awful.
+--
+-- <a name="status_icons"></a>
+-- **Status icons:**
+--
+-- By default, the tasklist prepends some symbols in front of the client name.
+-- This is used to notify that the client has some specific properties that are
+-- currently enabled. This can be disabled using
+-- `beautiful.tasklist_plain_task_name`=true in the theme.
+--
+-- <table class='widget_list' border=1>
+-- <tr style='font-weight: bold;'>
+-- <th align='center'>Icon</th>
+-- <th align='center'>Client property</th>
+-- </tr>
+-- <tr><td>▪</td><td><a href="./client.html#client.sticky">sticky</a></td></tr>
+-- <tr><td>⌃</td><td><a href="./client.html#client.ontop">ontop</a></td></tr>
+-- <tr><td>▴</td><td><a href="./client.html#client.above">above</a></td></tr>
+-- <tr><td>▾</td><td><a href="./client.html#client.below">below</a></td></tr>
+-- <tr><td>✈</td><td><a href="./client.html#client.floating">floating</a></td></tr>
+-- <tr><td>+</td><td><a href="./client.html#client.maximized">maximized</a></td></tr>
+-- <tr><td>⬌</td><td><a href="./client.html#client.maximized_horizontal">maximized_horizontal</a></td></tr>
+-- <tr><td>⬍</td><td><a href="./client.html#client.maximized_vertical">maximized_vertical</a></td></tr>
+-- </table>
+--
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2008-2009 Julien Danjou
+-- @classmod awful.widget.tasklist
+---------------------------------------------------------------------------
+
+-- Grab environment we need
+local capi = { screen = screen,
+ client = client }
+local ipairs = ipairs
+local setmetatable = setmetatable
+local table = table
+local common = require("awful.widget.common")
+local beautiful = require("beautiful")
+local util = require("awful.util")
+local tag = require("awful.tag")
+local flex = require("wibox.layout.flex")
+local timer = require("gears.timer")
+
+local function get_screen(s)
+ return s and screen[s]
+end
+
+local tasklist = { mt = {} }
+
+local instances
+
+--- The default foreground (text) color.
+-- @beautiful beautiful.tasklist_fg_normal
+-- @tparam[opt=nil] string|pattern fg_normal
+-- @see gears.color
+
+--- The default background color.
+-- @beautiful beautiful.tasklist_bg_normal
+-- @tparam[opt=nil] string|pattern bg_normal
+-- @see gears.color
+
+--- The focused client foreground (text) color.
+-- @beautiful beautiful.tasklist_fg_focus
+-- @tparam[opt=nil] string|pattern fg_focus
+-- @see gears.color
+
+--- The focused client background color.
+-- @beautiful beautiful.tasklist_bg_focus
+-- @tparam[opt=nil] string|pattern bg_focus
+-- @see gears.color
+
+--- The urgent clients foreground (text) color.
+-- @beautiful beautiful.tasklist_fg_urgent
+-- @tparam[opt=nil] string|pattern fg_urgent
+-- @see gears.color
+
+--- The urgent clients background color.
+-- @beautiful beautiful.tasklist_bg_urgent
+-- @tparam[opt=nil] string|pattern bg_urgent
+-- @see gears.color
+
+--- The minimized clients foreground (text) color.
+-- @beautiful beautiful.tasklist_fg_minimize
+-- @tparam[opt=nil] string|pattern fg_minimize
+-- @see gears.color
+
+--- The minimized clients background color.
+-- @beautiful beautiful.tasklist_bg_minimize
+-- @tparam[opt=nil] string|pattern bg_minimize
+-- @see gears.color
+
+--- The elements default background image.
+-- @beautiful beautiful.tasklist_bg_image_normal
+-- @tparam[opt=nil] string bg_image_normal
+
+--- The focused client background image.
+-- @beautiful beautiful.tasklist_bg_image_focus
+-- @tparam[opt=nil] string bg_image_focus
+
+--- The urgent clients background image.
+-- @beautiful beautiful.tasklist_bg_image_urgent
+-- @tparam[opt=nil] string bg_image_urgent
+
+--- The minimized clients background image.
+-- @beautiful beautiful.tasklist_bg_image_minimize
+-- @tparam[opt=nil] string bg_image_minimize
+
+--- Disable the tasklist client icons.
+-- @beautiful beautiful.tasklist_tasklist_disable_icon
+-- @tparam[opt=false] boolean tasklist_disable_icon
+
+--- Disable the extra tasklist client property notification icons.
+--
+-- See the <a href="status_icons">Status icons</a> section for more details.
+--
+-- @beautiful beautiful.tasklist_plain_task_name
+-- @tparam[opt=false] boolean tasklist_plain_task_name
+
+--- The tasklist font.
+-- @beautiful beautiful.tasklist_font
+-- @tparam[opt=nil] string font
+
+--- The focused client alignment.
+-- @beautiful beautiful.tasklist_align
+-- @tparam[opt=left] string align *left*, *right* or *center*
+
+--- The focused client title alignment.
+-- @beautiful beautiful.tasklist_font_focus
+-- @tparam[opt=nil] string font_focus
+
+--- The minimized clients font.
+-- @beautiful beautiful.tasklist_font_minimized
+-- @tparam[opt=nil] string font_minimized
+
+--- The urgent clients font.
+-- @beautiful beautiful.tasklist_font_urgent
+-- @tparam[opt=nil] string font_urgent
+
+--- The space between the tasklist elements.
+-- @beautiful beautiful.tasklist_spacing
+-- @tparam[opt=0] number spacing The spacing between tags.
+
+--- The default tasklist elements shape.
+-- @beautiful beautiful.tasklist_shape
+-- @tparam[opt=nil] gears.shape shape
+
+--- The default tasklist elements border width.
+-- @beautiful beautiful.tasklist_shape_border_width
+-- @tparam[opt=0] number shape_border_width
+
+--- The default tasklist elements border color.
+-- @beautiful beautiful.tasklist_shape_border_color
+-- @tparam[opt=nil] string|color shape_border_color
+-- @see gears.color
+
+--- The focused client shape.
+-- @beautiful beautiful.tasklist_shape_focus
+-- @tparam[opt=nil] gears.shape shape_focus
+
+--- The focused client border width.
+-- @beautiful beautiful.tasklist_shape_border_width_focus
+-- @tparam[opt=0] number shape_border_width_focus
+
+--- The focused client border color.
+-- @beautiful beautiful.tasklist_shape_border_color_focus
+-- @tparam[opt=nil] string|color shape_border_color_focus
+-- @see gears.color
+
+--- The minimized clients shape.
+-- @beautiful beautiful.tasklist_shape_minimized
+-- @tparam[opt=nil] gears.shape shape_minimized
+
+--- The minimized clients border width.
+-- @beautiful beautiful.tasklist_shape_border_width_minimized
+-- @tparam[opt=0] number shape_border_width_minimized
+
+--- The minimized clients border color.
+-- @beautiful beautiful.tasklist_shape_border_color_minimized
+-- @tparam[opt=nil] string|color shape_border_color_minimized
+-- @see gears.color
+
+--- The urgent clients shape.
+-- @beautiful beautiful.tasklist_shape_urgent
+-- @tparam[opt=nil] gears.shape shape_urgent
+
+--- The urgent clients border width.
+-- @beautiful beautiful.tasklist_shape_border_width_urgent
+-- @tparam[opt=0] number shape_border_width_urgent
+
+--- The urgent clients border color.
+-- @beautiful beautiful.tasklist_shape_border_color_urgent
+-- @tparam[opt=nil] string|color shape_border_color_urgent
+-- @see gears.color
+
+-- Public structures
+tasklist.filter = {}
+
+local function tasklist_label(c, args, tb)
+ if not args then args = {} end
+ local theme = beautiful.get()
+ local align = args.align or theme.tasklist_align or "left"
+ local fg_normal = util.ensure_pango_color(args.fg_normal or theme.tasklist_fg_normal or theme.fg_normal, "white")
+ local bg_normal = args.bg_normal or theme.tasklist_bg_normal or theme.bg_normal or "#000000"
+ local fg_focus = util.ensure_pango_color(args.fg_focus or theme.tasklist_fg_focus or theme.fg_focus, fg_normal)
+ local bg_focus = args.bg_focus or theme.tasklist_bg_focus or theme.bg_focus or bg_normal
+ local fg_urgent = util.ensure_pango_color(args.fg_urgent or theme.tasklist_fg_urgent or theme.fg_urgent, fg_normal)
+ local bg_urgent = args.bg_urgent or theme.tasklist_bg_urgent or theme.bg_urgent or bg_normal
+ local fg_minimize = util.ensure_pango_color(args.fg_minimize or theme.tasklist_fg_minimize or theme.fg_minimize, fg_normal)
+ local bg_minimize = args.bg_minimize or theme.tasklist_bg_minimize or theme.bg_minimize or bg_normal
+ local bg_image_normal = args.bg_image_normal or theme.bg_image_normal
+ local bg_image_focus = args.bg_image_focus or theme.bg_image_focus
+ local bg_image_urgent = args.bg_image_urgent or theme.bg_image_urgent
+ local bg_image_minimize = args.bg_image_minimize or theme.bg_image_minimize
+ local tasklist_disable_icon = args.tasklist_disable_icon or theme.tasklist_disable_icon or false
+ local font = args.font or theme.tasklist_font or theme.font or ""
+ local font_focus = args.font_focus or theme.tasklist_font_focus or theme.font_focus or font or ""
+ local font_minimized = args.font_minimized or theme.tasklist_font_minimized or theme.font_minimized or font or ""
+ local font_urgent = args.font_urgent or theme.tasklist_font_urgent or theme.font_urgent or font or ""
+ local text = ""
+ local name = ""
+ local bg
+ local bg_image
+ local shape = args.shape or theme.tasklist_shape
+ local shape_border_width = args.shape_border_width or theme.tasklist_shape_border_width
+ local shape_border_color = args.shape_border_color or theme.tasklist_shape_border_color
+
+ -- symbol to use to indicate certain client properties
+ local sticky = args.sticky or theme.tasklist_sticky or "▪"
+ local ontop = args.ontop or theme.tasklist_ontop or '⌃'
+ local above = args.above or theme.tasklist_above or '▴'
+ local below = args.below or theme.tasklist_below or '▾'
+ local floating = args.floating or theme.tasklist_floating or '✈'
+ local maximized = args.maximized or theme.tasklist_maximized or '<b>+</b>'
+ local maximized_horizontal = args.maximized_horizontal or theme.tasklist_maximized_horizontal or '⬌'
+ local maximized_vertical = args.maximized_vertical or theme.tasklist_maximized_vertical or '⬍'
+
+ tb:set_align(align)
+
+ if not theme.tasklist_plain_task_name then
+ if c.sticky then name = name .. sticky end
+
+ if c.ontop then name = name .. ontop
+ elseif c.above then name = name .. above
+ elseif c.below then name = name .. below end
+
+ if c.maximized then
+ name = name .. maximized
+ else
+ if c.maximized_horizontal then name = name .. maximized_horizontal end
+ if c.maximized_vertical then name = name .. maximized_vertical end
+ if c.floating then name = name .. floating end
+ end
+ end
+
+ if c.minimized then
+ name = name .. (util.escape(c.icon_name) or util.escape(c.name) or util.escape("<untitled>"))
+ else
+ name = name .. (util.escape(c.name) or util.escape("<untitled>"))
+ end
+
+ local focused = capi.client.focus == c
+ -- Handle transient_for: the first parent that does not skip the taskbar
+ -- is considered to be focused, if the real client has skip_taskbar.
+ if not focused and capi.client.focus and capi.client.focus.skip_taskbar
+ and capi.client.focus:get_transient_for_matching(function(cl)
+ return not cl.skip_taskbar
+ end) == c then
+ focused = true
+ end
+
+ if focused then
+ bg = bg_focus
+ text = text .. "<span color='"..fg_focus.."'>"..name.."</span>"
+ bg_image = bg_image_focus
+ font = font_focus
+
+ if args.shape_focus or theme.tasklist_shape_focus then
+ shape = args.shape_focus or theme.tasklist_shape_focus
+ end
+
+ if args.shape_border_width_focus or theme.tasklist_shape_border_width_focus then
+ shape_border_width = args.shape_border_width_focus or theme.tasklist_shape_border_width_focus
+ end
+
+ if args.shape_border_color_focus or theme.tasklist_shape_border_color_focus then
+ shape_border_color = args.shape_border_color_focus or theme.tasklist_shape_border_color_focus
+ end
+ elseif c.urgent then
+ bg = bg_urgent
+ text = text .. "<span color='"..fg_urgent.."'>"..name.."</span>"
+ bg_image = bg_image_urgent
+ font = font_urgent
+
+ if args.shape_urgent or theme.tasklist_shape_urgent then
+ shape = args.shape_urgent or theme.tasklist_shape_urgent
+ end
+
+ if args.shape_border_width_urgent or theme.tasklist_shape_border_width_urgent then
+ shape_border_width = args.shape_border_width_urgent or theme.tasklist_shape_border_width_urgent
+ end
+
+ if args.shape_border_color_urgent or theme.tasklist_shape_border_color_urgent then
+ shape_border_color = args.shape_border_color_urgent or theme.tasklist_shape_border_color_urgent
+ end
+ elseif c.minimized then
+ bg = bg_minimize
+ text = text .. "<span color='"..fg_minimize.."'>"..name.."</span>"
+ bg_image = bg_image_minimize
+ font = font_minimized
+
+ if args.shape_minimized or theme.tasklist_shape_minimized then
+ shape = args.shape_minimized or theme.tasklist_shape_minimized
+ end
+
+ if args.shape_border_width_minimized or theme.tasklist_shape_border_width_minimized then
+ shape_border_width = args.shape_border_width_minimized or theme.tasklist_shape_border_width_minimized
+ end
+
+ if args.shape_border_color_minimized or theme.tasklist_shape_border_color_minimized then
+ shape_border_color = args.shape_border_color_minimized or theme.tasklist_shape_border_color_minimized
+ end
+ else
+ bg = bg_normal
+ text = text .. "<span color='"..fg_normal.."'>"..name.."</span>"
+ bg_image = bg_image_normal
+ end
+ tb:set_font(font)
+
+ local other_args = {
+ shape = shape,
+ shape_border_width = shape_border_width,
+ shape_border_color = shape_border_color,
+ }
+
+ return text, bg, bg_image, not tasklist_disable_icon and c.icon or nil, other_args
+end
+
+local function tasklist_update(s, w, buttons, filter, data, style, update_function)
+ local clients = {}
+ for _, c in ipairs(capi.client.get()) do
+ if not (c.skip_taskbar or c.hidden
+ or c.type == "splash" or c.type == "dock" or c.type == "desktop")
+ and filter(c, s) then
+ table.insert(clients, c)
+ end
+ end
+
+ local function label(c, tb) return tasklist_label(c, style, tb) end
+
+ update_function(w, buttons, label, data, clients)
+end
+
+--- Create a new tasklist widget. The last two arguments (update_function
+-- and base_widget) serve to customize the layout of the tasklist (eg. to
+-- make it vertical). For that, you will need to copy the
+-- awful.widget.common.list_update function, make your changes to it
+-- and pass it as update_function here. Also change the base_widget if the
+-- default is not what you want.
+-- @param screen The screen to draw tasklist for.
+-- @param filter Filter function to define what clients will be listed.
+-- @param buttons A table with buttons binding to set.
+-- @tparam[opt={}] table style The style overrides default theme.
+-- @tparam[opt=nil] string|pattern style.fg_normal
+-- @tparam[opt=nil] string|pattern style.bg_normal
+-- @tparam[opt=nil] string|pattern style.fg_focus
+-- @tparam[opt=nil] string|pattern style.bg_focus
+-- @tparam[opt=nil] string|pattern style.fg_urgent
+-- @tparam[opt=nil] string|pattern style.bg_urgent
+-- @tparam[opt=nil] string|pattern style.fg_minimize
+-- @tparam[opt=nil] string|pattern style.bg_minimize
+-- @tparam[opt=nil] string style.bg_image_normal
+-- @tparam[opt=nil] string style.bg_image_focus
+-- @tparam[opt=nil] string style.bg_image_urgent
+-- @tparam[opt=nil] string style.bg_image_minimize
+-- @tparam[opt=nil] boolean style.tasklist_disable_icon
+-- @tparam[opt=nil] string style.font
+-- @tparam[opt=left] string style.align *left*, *right* or *center*
+-- @tparam[opt=nil] string style.font_focus
+-- @tparam[opt=nil] string style.font_minimized
+-- @tparam[opt=nil] string style.font_urgent
+-- @tparam[opt=nil] number style.spacing The spacing between tags.
+-- @tparam[opt=nil] gears.shape style.shape
+-- @tparam[opt=nil] number style.shape_border_width
+-- @tparam[opt=nil] string|color style.shape_border_color
+-- @tparam[opt=nil] gears.shape style.shape_focus
+-- @tparam[opt=nil] number style.shape_border_width_focus
+-- @tparam[opt=nil] string|color style.shape_border_color_focus
+-- @tparam[opt=nil] gears.shape style.shape_minimized
+-- @tparam[opt=nil] number style.shape_border_width_minimized
+-- @tparam[opt=nil] string|color style.shape_border_color_minimized
+-- @tparam[opt=nil] gears.shape style.shape_urgent
+-- @tparam[opt=nil] number style.shape_border_width_urgent
+-- @tparam[opt=nil] string|color style.shape_border_color_urgent
+-- @param[opt] update_function Function to create a tag widget on each
+-- update. See `awful.widget.common.list_update`.
+-- @tparam[opt] table base_widget Container widget for tag widgets. Default
+-- is `wibox.layout.flex.horizontal`.
+-- @function awful.tasklist
+function tasklist.new(screen, filter, buttons, style, update_function, base_widget)
+ screen = get_screen(screen)
+ local uf = update_function or common.list_update
+ local w = base_widget or flex.horizontal()
+
+ local data = setmetatable({}, { __mode = 'k' })
+
+ if w.set_spacing and (style and style.spacing or beautiful.taglist_spacing) then
+ w:set_spacing(style and style.spacing or beautiful.taglist_spacing)
+ end
+
+ local queued_update = false
+ function w._do_tasklist_update()
+ -- Add a delayed callback for the first update.
+ if not queued_update then
+ timer.delayed_call(function()
+ queued_update = false
+ if screen.valid then
+ tasklist_update(screen, w, buttons, filter, data, style, uf)
+ end
+ end)
+ queued_update = true
+ end
+ end
+ function w._unmanage(c)
+ data[c] = nil
+ end
+ if instances == nil then
+ instances = setmetatable({}, { __mode = "k" })
+ local function us(s)
+ local i = instances[get_screen(s)]
+ if i then
+ for _, tlist in pairs(i) do
+ tlist._do_tasklist_update()
+ end
+ end
+ end
+ local function u()
+ for s in pairs(instances) do
+ if s.valid then
+ us(s)
+ end
+ end
+ end
+
+ tag.attached_connect_signal(nil, "property::selected", u)
+ tag.attached_connect_signal(nil, "property::activated", u)
+ capi.client.connect_signal("property::urgent", u)
+ capi.client.connect_signal("property::sticky", u)
+ capi.client.connect_signal("property::ontop", u)
+ capi.client.connect_signal("property::above", u)
+ capi.client.connect_signal("property::below", u)
+ capi.client.connect_signal("property::floating", u)
+ capi.client.connect_signal("property::maximized_horizontal", u)
+ capi.client.connect_signal("property::maximized_vertical", u)
+ capi.client.connect_signal("property::minimized", u)
+ capi.client.connect_signal("property::name", u)
+ capi.client.connect_signal("property::icon_name", u)
+ capi.client.connect_signal("property::icon", u)
+ capi.client.connect_signal("property::skip_taskbar", u)
+ capi.client.connect_signal("property::screen", function(c, old_screen)
+ us(c.screen)
+ us(old_screen)
+ end)
+ capi.client.connect_signal("property::hidden", u)
+ capi.client.connect_signal("tagged", u)
+ capi.client.connect_signal("untagged", u)
+ capi.client.connect_signal("unmanage", function(c)
+ u(c)
+ for _, i in pairs(instances) do
+ for _, tlist in pairs(i) do
+ tlist._unmanage(c)
+ end
+ end
+ end)
+ 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]
+ if not list then
+ list = setmetatable({}, { __mode = "v" })
+ instances[screen] = list
+ end
+ table.insert(list, w)
+ return w
+end
+
+--- Filtering function to include all clients.
+-- @return true
+function tasklist.filter.allscreen()
+ return true
+end
+
+--- Filtering function to include the clients from all tags on the screen.
+-- @param c The client.
+-- @param screen The screen we are drawing on.
+-- @return true if c is on screen, false otherwise
+function tasklist.filter.alltags(c, screen)
+ -- Only print client on the same screen as this widget
+ return get_screen(c.screen) == get_screen(screen)
+end
+
+--- Filtering function to include only the clients from currently selected tags.
+-- @param c The client.
+-- @param screen The screen we are drawing on.
+-- @return true if c is in a selected tag on screen, false otherwise
+function tasklist.filter.currenttags(c, screen)
+ screen = get_screen(screen)
+ -- Only print client on the same screen as this widget
+ if get_screen(c.screen) ~= screen then return false end
+ -- Include sticky client too
+ if c.sticky then return true end
+ local tags = screen.tags
+ for _, t in ipairs(tags) do
+ if t.selected then
+ local ctags = c:tags()
+ for _, v in ipairs(ctags) do
+ if v == t then
+ return true
+ end
+ end
+ end
+ end
+ return false
+end
+
+--- Filtering function to include only the minimized clients from currently selected tags.
+-- @param c The client.
+-- @param screen The screen we are drawing on.
+-- @return true if c is in a selected tag on screen and is minimized, false otherwise
+function tasklist.filter.minimizedcurrenttags(c, screen)
+ screen = get_screen(screen)
+ -- Only print client on the same screen as this widget
+ if get_screen(c.screen) ~= screen then return false end
+ -- Check client is minimized
+ if not c.minimized then return false end
+ -- Include sticky client
+ if c.sticky then return true end
+ local tags = screen.tags
+ for _, t in ipairs(tags) do
+ -- Select only minimized clients
+ if t.selected then
+ local ctags = c:tags()
+ for _, v in ipairs(ctags) do
+ if v == t then
+ return true
+ end
+ end
+ end
+ end
+ return false
+end
+
+--- Filtering function to include only the currently focused client.
+-- @param c The client.
+-- @param screen The screen we are drawing on.
+-- @return true if c is focused on screen, false otherwise
+function tasklist.filter.focused(c, screen)
+ -- Only print client on the same screen as this widget
+ return get_screen(c.screen) == get_screen(screen) and capi.client.focus == c
+end
+
+function tasklist.mt:__call(...)
+ return tasklist.new(...)
+end
+
+return setmetatable(tasklist, tasklist.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/widget/textclock.lua b/lib/awful/widget/textclock.lua
new file mode 100644
index 0000000..002aa0e
--- /dev/null
+++ b/lib/awful/widget/textclock.lua
@@ -0,0 +1,16 @@
+---------------------------------------------------------------------------
+-- This widget has moved to `wibox.widget.textclock`
+--
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2008-2009 Julien Danjou
+-- @classmod awful.widget.textclock
+---------------------------------------------------------------------------
+local util = require("awful.util")
+
+return util.deprecate_class(
+ require("wibox.widget.textclock"),
+ "awful.widget.textclock",
+ "wibox.widget.textclock"
+)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/widget/watch.lua b/lib/awful/widget/watch.lua
new file mode 100644
index 0000000..bc4c9af
--- /dev/null
+++ b/lib/awful/widget/watch.lua
@@ -0,0 +1,91 @@
+---------------------------------------------------------------------------
+--- Watch widget.
+-- Here is an example of simple temperature widget which will update each 15
+-- seconds implemented in two different ways.
+-- The first, simpler one, will just display the return command output
+-- (so output is stripped by shell commands).
+-- In the other example `sensors` returns to the widget its full output
+-- and it's trimmed in the widget callback function:
+--
+-- 211 mytextclock,
+-- 212 wibox.widget.textbox(' | '),
+-- 213 -- one way to do that:
+-- 214 awful.widget.watch('bash -c "sensors | grep temp1"', 15),
+-- 215 -- another way:
+-- 216 awful.widget.watch('sensors', 15, function(widget, stdout)
+-- 217 for line in stdout:gmatch("[^\r\n]+") do
+-- 218 if line:match("temp1") then
+-- 219 widget:set_text(line)
+-- 220 return
+-- 221 end
+-- 222 end
+-- 223 end),
+-- 224 s.mylayoutbox,
+--
+-- ![Example screenshot](../images/awful_widget_watch.png)
+--
+-- @author Benjamin Petrenko
+-- @author Yauheni Kirylau
+-- @copyright 2015, 2016 Benjamin Petrenko, Yauheni Kirylau
+-- @classmod awful.widget.watch
+---------------------------------------------------------------------------
+
+local setmetatable = setmetatable
+local textbox = require("wibox.widget.textbox")
+local timer = require("gears.timer")
+local spawn = require("awful.spawn")
+
+local watch = { mt = {} }
+
+--- Create a textbox that shows the output of a command
+-- and updates it at a given time interval.
+--
+-- @tparam string|table command The command.
+--
+-- @tparam[opt=5] integer timeout The time interval at which the textbox
+-- will be updated.
+--
+-- @tparam[opt] function callback The function that will be called after
+-- the command output will be received. it is shown in the textbox.
+-- Defaults to:
+-- function(widget, stdout, stderr, exitreason, exitcode)
+-- widget:set_text(stdout)
+-- end
+-- @param callback.widget Base widget instance.
+-- @tparam string callback.stdout Output on stdout.
+-- @tparam string callback.stderr Output on stderr.
+-- @tparam string callback.exitreason Exit Reason.
+-- The reason can be "exit" or "signal".
+-- @tparam integer callback.exitcode Exit code.
+-- For "exit" reason it's the exit code.
+-- For "signal" reason — the signal causing process termination.
+--
+-- @param[opt=wibox.widget.textbox()] base_widget Base widget.
+--
+-- @return The widget used by this watch
+function watch.new(command, timeout, callback, base_widget)
+ timeout = timeout or 5
+ base_widget = base_widget or textbox()
+ callback = callback or function(widget, stdout, stderr, exitreason, exitcode) -- luacheck: no unused args
+ widget:set_text(stdout)
+ end
+ local t = timer { timeout = timeout }
+ t:connect_signal("timeout", function()
+ t:stop()
+ spawn.easy_async(command, function(stdout, stderr, exitreason, exitcode)
+ callback(base_widget, stdout, stderr, exitreason, exitcode)
+ t:again()
+ end)
+ end)
+ t:start()
+ t:emit_signal("timeout")
+ return base_widget
+end
+
+function watch.mt.__call(_, ...)
+ return watch.new(...)
+end
+
+return setmetatable(watch, watch.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/beautiful.lua b/lib/beautiful.lua
new file mode 100644
index 0000000..f6f0106
--- /dev/null
+++ b/lib/beautiful.lua
@@ -0,0 +1,7 @@
+-- Work-around for broken systems which are updated by overwriting the awesome
+-- installation. This would not remove beautiful.lua from older awesome versions
+-- and thus breakage follows.
+-- The work-around is to use a pointless beautiful.lua file.
+return require("beautiful.init")
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/beautiful/init.lua b/lib/beautiful/init.lua
new file mode 100644
index 0000000..0e72cbc
--- /dev/null
+++ b/lib/beautiful/init.lua
@@ -0,0 +1,217 @@
+----------------------------------------------------------------------------
+--- Theme library.
+--
+-- @author Damien Leone &lt;damien.leone@gmail.com&gt;
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2008-2009 Damien Leone, Julien Danjou
+-- @module beautiful
+----------------------------------------------------------------------------
+
+-- Grab environment
+local os = os
+local pairs = pairs
+local type = type
+local dofile = dofile
+local setmetatable = setmetatable
+local lgi = require("lgi")
+local Pango = lgi.Pango
+local PangoCairo = lgi.PangoCairo
+local gears_debug = require("gears.debug")
+local protected_call = require("gears.protected_call")
+
+local xresources = require("beautiful.xresources")
+
+local beautiful = { xresources = xresources, mt = {} }
+
+-- Local data
+local theme = {}
+local descs = setmetatable({}, { __mode = 'k' })
+local fonts = setmetatable({}, { __mode = 'v' })
+local active_font
+
+--- The default font.
+-- @beautiful beautiful.font
+
+-- The default background color.
+-- @beautiful beautiful.bg_normal
+
+-- The default focused element background color.
+-- @beautiful beautiful.bg_focus
+
+-- The default urgent element background color.
+-- @beautiful beautiful.bg_urgent
+
+-- The default minimized element background color.
+-- @beautiful beautiful.bg_minimize
+
+-- The system tray background color.
+-- Please note that only solid colors are currently supported.
+-- @beautiful beautiful.bg_systray
+
+-- The default focused element foreground (text) color.
+-- @beautiful beautiful.fg_normal
+
+-- The default focused element foreground (text) color.
+-- @beautiful beautiful.fg_focus
+
+-- The default urgent element foreground (text) color.
+-- @beautiful beautiful.fg_urgent
+
+-- The default minimized element foreground (text) color.
+-- @beautiful beautiful.fg_minimize
+
+--- The gap between clients.
+-- @beautiful beautiful.useless_gap
+-- @param[opt=0] number
+
+--- The client border width.
+-- @beautiful beautiful.border_width
+
+--- The default clients border width.
+-- Note that only solid colors are supported.
+-- @beautiful beautiful.border_normal
+
+--- The focused client border width.
+-- Note that only solid colors are supported.
+-- @beautiful beautiful.border_focus
+
+--- The marked clients border width.
+-- Note that only solid colors are supported.
+-- @beautiful beautiful.border_marked
+
+--- The wallpaper path.
+-- @beautiful beautiful.wallpaper
+
+-- The icon theme name.
+-- It has to be a directory in `/usr/share/icons` or an XDG icon folder.
+-- @beautiful beautiful.icon_theme
+
+--- The Awesome icon path.
+-- @beautiful beautiful.awesome_icon
+
+--- Load a font from a string or a font description.
+--
+-- @see https://developer.gnome.org/pango/stable/pango-Fonts.html#pango-font-description-from-string
+-- @tparam string|lgi.Pango.FontDescription name Font, which can be a
+-- string or a lgi.Pango.FontDescription.
+-- @treturn table A table with `name`, `description` and `height`.
+local function load_font(name)
+ name = name or active_font
+ if name and type(name) ~= "string" then
+ if descs[name] then
+ name = descs[name]
+ else
+ name = name:to_string()
+ end
+ end
+ if fonts[name] then
+ return fonts[name]
+ end
+
+ -- Load new font
+ local desc = Pango.FontDescription.from_string(name)
+ local ctx = PangoCairo.font_map_get_default():create_context()
+ ctx:set_resolution(beautiful.xresources.get_dpi())
+
+ -- Apply default values from the context (e.g. a default font size)
+ desc:merge(ctx:get_font_description(), false)
+
+ -- Calculate font height.
+ local metrics = ctx:get_metrics(desc, nil)
+ local height = math.ceil((metrics:get_ascent() + metrics:get_descent()) / Pango.SCALE)
+
+ local font = { name = name, description = desc, height = height }
+ fonts[name] = font
+ descs[desc] = name
+ return font
+end
+
+--- Set an active font
+--
+-- @param name The font
+local function set_font(name)
+ active_font = load_font(name).name
+end
+
+--- Get a font description.
+--
+-- See https://developer.gnome.org/pango/stable/pango-Fonts.html#PangoFontDescription.
+-- @tparam string|lgi.Pango.FontDescription name The name of the font.
+-- @treturn lgi.Pango.FontDescription
+function beautiful.get_font(name)
+ return load_font(name).description
+end
+
+--- Get a new font with merged attributes, based on another one.
+--
+-- See https://developer.gnome.org/pango/stable/pango-Fonts.html#pango-font-description-from-string.
+-- @tparam string|Pango.FontDescription name The base font.
+-- @tparam string merge Attributes that should be merged, e.g. "bold".
+-- @treturn lgi.Pango.FontDescription
+function beautiful.get_merged_font(name, merge)
+ local font = beautiful.get_font(name)
+ merge = Pango.FontDescription.from_string(merge)
+ local merged = font:copy_static()
+ merged:merge(merge, true)
+ return beautiful.get_font(merged:to_string())
+end
+
+--- Get the height of a font.
+--
+-- @param name Name of the font
+function beautiful.get_font_height(name)
+ return load_font(name).height
+end
+
+--- Init function, should be runned at the beginning of configuration file.
+-- @tparam string|table config The theme to load. It can be either the path to
+-- the theme file (returning a table) or directly the table
+-- containing all the theme values.
+function beautiful.init(config)
+ if config then
+ local homedir = os.getenv("HOME")
+
+ -- If `config` is the path to a theme file, run this file,
+ -- otherwise if it is a theme table, save it.
+ if type(config) == 'string' then
+ -- Expand the '~' $HOME shortcut
+ config = config:gsub("^~/", homedir .. "/")
+ theme = protected_call(dofile, config)
+ elseif type(config) == 'table' then
+ theme = config
+ end
+
+ if theme then
+ -- expand '~'
+ if homedir then
+ for k, v in pairs(theme) do
+ if type(v) == "string" then theme[k] = v:gsub("^~/", homedir .. "/") end
+ end
+ end
+
+ if theme.font then set_font(theme.font) end
+ else
+ return gears_debug.print_error("beautiful: error loading theme file " .. config)
+ end
+ else
+ return gears_debug.print_error("beautiful: error loading theme: no theme specified")
+ end
+end
+
+--- Get the current theme.
+--
+-- @treturn table The current theme table.
+function beautiful.get()
+ return theme
+end
+
+function beautiful.mt:__index(k)
+ return theme[k]
+end
+
+-- Set the default font
+set_font("sans 8")
+
+return setmetatable(beautiful, beautiful.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/beautiful/xresources.lua b/lib/beautiful/xresources.lua
new file mode 100644
index 0000000..f0f5d78
--- /dev/null
+++ b/lib/beautiful/xresources.lua
@@ -0,0 +1,117 @@
+----------------------------------------------------------------------------
+--- Library for getting xrdb data.
+--
+-- @author Yauhen Kirylau &lt;yawghen@gmail.com&gt;
+-- @copyright 2015 Yauhen Kirylau
+-- @module beautiful.xresources
+----------------------------------------------------------------------------
+
+-- Grab environment
+local awesome = awesome
+local screen = screen
+local round = require("awful.util").round
+local gears_debug = require("gears.debug")
+
+local xresources = {}
+
+local fallback = {
+ --black
+ color0 = '#000000',
+ color8 = '#465457',
+ --red
+ color1 = '#cb1578',
+ color9 = '#dc5e86',
+ --green
+ color2 = '#8ecb15',
+ color10 = '#9edc60',
+ --yellow
+ color3 = '#cb9a15',
+ color11 = '#dcb65e',
+ --blue
+ color4 = '#6f15cb',
+ color12 = '#7e5edc',
+ --purple
+ color5 = '#cb15c9',
+ color13 = '#b75edc',
+ --cyan
+ color6 = '#15b4cb',
+ color14 = '#5edcb4',
+ --white
+ color7 = '#888a85',
+ color15 = '#ffffff',
+ --
+ background = '#0e0021',
+ foreground = '#bcbcbc',
+}
+
+--- Get current base colorscheme from xrdb.
+-- @treturn table Color table with keys 'background', 'foreground' and 'color0'..'color15'
+function xresources.get_current_theme()
+ local keys = { 'background', 'foreground' }
+ for i=0,15 do table.insert(keys, "color"..i) end
+ local colors = {}
+ for _, key in ipairs(keys) do
+ colors[key] = awesome.xrdb_get_value("", key)
+ if not colors[key] then
+ gears_debug.print_warning("beautiful: can't get colorscheme from xrdb (using fallback).")
+ return fallback
+ end
+ if colors[key]:find("rgb:") then
+ colors[key] = "#"..colors[key]:gsub("[a]?rgb:", ""):gsub("/", "")
+ end
+ end
+ return colors
+end
+
+
+local dpi_per_screen = {}
+
+local function get_screen(s)
+ return s and screen[s]
+end
+
+--- Get global or per-screen DPI value falling back to xrdb.
+-- @tparam[opt] integer|screen s The screen.
+-- @treturn number DPI value.
+function xresources.get_dpi(s)
+ s = get_screen(s)
+ if dpi_per_screen[s] then
+ return dpi_per_screen[s]
+ end
+ if not xresources.dpi then
+ -- Might not be present when run under unit tests
+ if awesome and awesome.xrdb_get_value then
+ xresources.dpi = tonumber(awesome.xrdb_get_value("", "Xft.dpi"))
+ end
+ if not xresources.dpi then
+ xresources.dpi = 96
+ end
+ end
+ return xresources.dpi
+end
+
+
+--- Set DPI for a given screen (defaults to global).
+-- @tparam number dpi DPI value.
+-- @tparam[opt] integer s Screen.
+function xresources.set_dpi(dpi, s)
+ s = get_screen(s)
+ if not s then
+ xresources.dpi = dpi
+ else
+ dpi_per_screen[s] = dpi
+ end
+end
+
+
+--- Compute resulting size applying current DPI value (optionally per screen).
+-- @tparam number size Size
+-- @tparam[opt] integer|screen s The screen.
+-- @treturn integer Resulting size (rounded to integer).
+function xresources.apply_dpi(size, s)
+ return round(size / 96 * xresources.get_dpi(s))
+end
+
+return xresources
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/gears/cache.lua b/lib/gears/cache.lua
new file mode 100644
index 0000000..dc5add5
--- /dev/null
+++ b/lib/gears/cache.lua
@@ -0,0 +1,51 @@
+---------------------------------------------------------------------------
+-- @author Uli Schlachter
+-- @copyright 2015 Uli Schlachter
+-- @classmod gears.cache
+---------------------------------------------------------------------------
+
+local select = select
+local setmetatable = setmetatable
+local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
+
+local cache = {}
+
+--- Get an entry from the cache, creating it if it's missing.
+-- @param ... Arguments for the creation callback. These are checked against the
+-- cache contents for equality.
+-- @return The entry from the cache
+function cache:get(...)
+ local result = self._cache
+ for i = 1, select("#", ...) do
+ local arg = select(i, ...)
+ local next = result[arg]
+ if not next then
+ next = {}
+ result[arg] = next
+ end
+ result = next
+ end
+ local ret = result._entry
+ if not ret then
+ ret = { self._creation_cb(...) }
+ result._entry = ret
+ end
+ return unpack(ret)
+end
+
+--- Create a new cache object. A cache keeps some data that can be
+-- garbage-collected at any time, but might be useful to keep.
+-- @param creation_cb Callback that is used for creating missing cache entries.
+-- @return A new cache object.
+function cache.new(creation_cb)
+ return setmetatable({
+ _cache = setmetatable({}, { __mode = "v" }),
+ _creation_cb = creation_cb
+ }, {
+ __index = cache
+ })
+end
+
+return setmetatable(cache, { __call = function(_, ...) return cache.new(...) end })
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/gears/color.lua b/lib/gears/color.lua
new file mode 100644
index 0000000..f0197c1
--- /dev/null
+++ b/lib/gears/color.lua
@@ -0,0 +1,346 @@
+---------------------------------------------------------------------------
+-- @author Uli Schlachter
+-- @copyright 2010 Uli Schlachter
+-- @module gears.color
+---------------------------------------------------------------------------
+
+local setmetatable = setmetatable
+local string = string
+local table = table
+local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
+local tonumber = tonumber
+local ipairs = ipairs
+local pairs = pairs
+local type = type
+local lgi = require("lgi")
+local cairo = lgi.cairo
+local Pango = lgi.Pango
+local surface = require("gears.surface")
+
+local color = { mt = {} }
+local pattern_cache
+
+--- Create a pattern from a given string.
+-- This function can create solid, linear, radial and png patterns. In general,
+-- patterns are specified as strings formatted as "type:arguments". "arguments"
+-- is specific to the pattern being used. For example, one can use
+-- "radial:50,50,10:55,55,30:0,#ff0000:0.5,#00ff00:1,#0000ff".
+-- Alternatively, patterns can be specified via tables. In this case, the
+-- table's 'type' member specifies the type. For example:
+-- {
+-- type = "radial",
+-- from = { 50, 50, 10 },
+-- to = { 55, 55, 30 },
+-- stops = { { 0, "#ff0000" }, { 0.5, "#00ff00" }, { 1, "#0000ff" } }
+-- }
+-- Any argument that cannot be understood is passed to @{create_solid_pattern}.
+--
+-- Please note that you MUST NOT modify the returned pattern, for example by
+-- calling :set_matrix() on it, because this function uses a cache and your
+-- changes could thus have unintended side effects. Use @{create_pattern_uncached}
+-- if you need to modify the returned pattern.
+-- @see create_pattern_uncached, create_solid_pattern, create_png_pattern,
+-- create_linear_pattern, create_radial_pattern
+-- @tparam string col The string describing the pattern.
+-- @return a cairo pattern object
+-- @function gears.color
+
+--- Parse a HTML-color.
+-- This function can parse colors like `#rrggbb` and `#rrggbbaa` and also `red`.
+-- Max 4 chars per channel.
+--
+-- @param col The color to parse
+-- @treturn table 4 values representing color in RGBA format (each of them in
+-- [0, 1] range) or nil if input is incorrect.
+-- @usage -- This will return 0, 1, 0, 1
+-- gears.color.parse_color("#00ff00ff")
+function color.parse_color(col)
+ local rgb = {}
+ if string.match(col, "^#%x+$") then
+ local hex_str = col:sub(2, #col)
+ local channels
+ if #hex_str % 3 == 0 then
+ channels = 3
+ elseif #hex_str % 4 == 0 then
+ channels = 4
+ else
+ return nil
+ end
+ local chars_per_channel = #hex_str / channels
+ if chars_per_channel > 4 then
+ return nil
+ end
+ local dividor = (0x10 ^ chars_per_channel) - 1
+ for idx=1,#hex_str,chars_per_channel do
+ local channel_val = tonumber(hex_str:sub(idx,idx+chars_per_channel-1), 16)
+ table.insert(rgb, channel_val / dividor)
+ end
+ if channels == 3 then
+ table.insert(rgb, 1)
+ end
+ else
+ local c = Pango.Color()
+ if not c:parse(col) then
+ return nil
+ end
+ rgb = {
+ c.red / 0xffff,
+ c.green / 0xffff,
+ c.blue / 0xffff,
+ 1.0
+ }
+ end
+ assert(#rgb == 4, col)
+ return unpack(rgb)
+end
+
+--- Find all numbers in a string
+--
+-- @tparam string s The string to parse
+-- @return Each number found as a separate value
+local function parse_numbers(s)
+ local res = {}
+ for k in string.gmatch(s, "-?[0-9]+[.]?[0-9]*") do
+ table.insert(res, tonumber(k))
+ end
+ return unpack(res)
+end
+
+--- Create a solid pattern
+--
+-- @param col The color for the pattern
+-- @return A cairo pattern object
+function color.create_solid_pattern(col)
+ if col == nil then
+ col = "#000000"
+ elseif type(col) == "table" then
+ col = col.color
+ end
+ return cairo.Pattern.create_rgba(color.parse_color(col))
+end
+
+--- Create an image pattern from a png file
+--
+-- @param file The filename of the file
+-- @return a cairo pattern object
+function color.create_png_pattern(file)
+ if type(file) == "table" then
+ file = file.file
+ end
+ local image = surface.load(file)
+ local pattern = cairo.Pattern.create_for_surface(image)
+ pattern:set_extend(cairo.Extend.REPEAT)
+ return pattern
+end
+
+--- Add stops to the given pattern.
+-- @param p The cairo pattern to add stops to
+-- @param iterator An iterator that returns strings. Each of those strings
+-- should be in the form place,color where place is in [0, 1].
+local function add_iterator_stops(p, iterator)
+ for k in iterator do
+ local sub = string.gmatch(k, "[^,]+")
+ local point, clr = sub(), sub()
+ p:add_color_stop_rgba(point, color.parse_color(clr))
+ end
+end
+
+--- Add a list of stops to a given pattern
+local function add_stops_table(pat, arg)
+ for _, stop in ipairs(arg) do
+ pat:add_color_stop_rgba(stop[1], color.parse_color(stop[2]))
+ end
+end
+
+--- Create a pattern from a string
+local function string_pattern(creator, arg)
+ local iterator = string.gmatch(arg, "[^:]+")
+ -- Create a table where each entry is a number from the original string
+ local args = { parse_numbers(iterator()) }
+ local to = { parse_numbers(iterator()) }
+ -- Now merge those two tables
+ for _, v in pairs(to) do
+ table.insert(args, v)
+ end
+ -- And call our creator function with the values
+ local p = creator(unpack(args))
+
+ add_iterator_stops(p, iterator)
+ return p
+end
+
+--- Create a linear pattern object.
+-- The pattern is created from a string. This string should have the following
+-- form: `"x0, y0:x1, y1:<stops>"`
+-- Alternatively, the pattern can be specified as a table:
+-- { type = "linear", from = { x0, y0 }, to = { x1, y1 },
+-- stops = { <stops> } }
+-- `x0,y0` and `x1,y1` are the start and stop point of the pattern.
+-- For the explanation of `<stops>`, see `color.create_pattern`.
+-- @tparam string|table arg The argument describing the pattern.
+-- @return a cairo pattern object
+function color.create_linear_pattern(arg)
+ local pat
+
+ if type(arg) == "string" then
+ return string_pattern(cairo.Pattern.create_linear, arg)
+ elseif type(arg) ~= "table" then
+ error("Wrong argument type: " .. type(arg))
+ end
+
+ pat = cairo.Pattern.create_linear(arg.from[1], arg.from[2], arg.to[1], arg.to[2])
+ add_stops_table(pat, arg.stops)
+ return pat
+end
+
+--- Create a radial pattern object.
+-- The pattern is created from a string. This string should have the following
+-- form: `"x0, y0, r0:x1, y1, r1:<stops>"`
+-- Alternatively, the pattern can be specified as a table:
+-- { type = "radial", from = { x0, y0, r0 }, to = { x1, y1, r1 },
+-- stops = { <stops> } }
+-- `x0,y0` and `x1,y1` are the start and stop point of the pattern.
+-- `r0` and `r1` are the radii of the start / stop circle.
+-- For the explanation of `<stops>`, see `color.create_pattern`.
+-- @tparam string|table arg The argument describing the pattern
+-- @return a cairo pattern object
+function color.create_radial_pattern(arg)
+ local pat
+
+ if type(arg) == "string" then
+ return string_pattern(cairo.Pattern.create_radial, arg)
+ elseif type(arg) ~= "table" then
+ error("Wrong argument type: " .. type(arg))
+ end
+
+ pat = cairo.Pattern.create_radial(arg.from[1], arg.from[2], arg.from[3],
+ arg.to[1], arg.to[2], arg.to[3])
+ add_stops_table(pat, arg.stops)
+ return pat
+end
+
+--- Mapping of all supported color types. New entries can be added.
+color.types = {
+ solid = color.create_solid_pattern,
+ png = color.create_png_pattern,
+ linear = color.create_linear_pattern,
+ radial = color.create_radial_pattern
+}
+
+--- Create a pattern from a given string.
+-- For full documentation of this function, please refer to
+-- `color.create_pattern`. The difference between `color.create_pattern`
+-- and this function is that this function does not insert the generated
+-- objects into the pattern cache. Thus, you are allowed to modify the
+-- returned object.
+-- @see create_pattern
+-- @param col The string describing the pattern.
+-- @return a cairo pattern object
+function color.create_pattern_uncached(col)
+ -- If it already is a cairo pattern, just leave it as that
+ if cairo.Pattern:is_type_of(col) then
+ return col
+ end
+ col = col or "#000000"
+ if type(col) == "string" then
+ local t = string.match(col, "[^:]+")
+ if color.types[t] then
+ local pos = string.len(t)
+ local arg = string.sub(col, pos + 2)
+ return color.types[t](arg)
+ end
+ elseif type(col) == "table" then
+ local t = col.type
+ if color.types[t] then
+ return color.types[t](col)
+ end
+ end
+ return color.create_solid_pattern(col)
+end
+
+--- Create a pattern from a given string, same as `gears.color`.
+-- @see gears.color
+function color.create_pattern(col)
+ if cairo.Pattern:is_type_of(col) then
+ return col
+ end
+ return pattern_cache:get(col or "#000000")
+end
+
+--- Check if a pattern is opaque.
+-- A pattern is transparent if the background on which it gets drawn (with
+-- operator OVER) doesn't influence the visual result.
+-- @param col An argument that `create_pattern` accepts.
+-- @return The pattern if it is surely opaque, else nil
+function color.create_opaque_pattern(col)
+ local pattern = color.create_pattern(col)
+ local kind = pattern:get_type()
+
+ if kind == "SOLID" then
+ local _, _, _, _, alpha = pattern:get_rgba()
+ if alpha ~= 1 then
+ return
+ end
+ return pattern
+ elseif kind == "SURFACE" then
+ local status, surf = pattern:get_surface()
+ if status ~= "SUCCESS" or surf.content ~= "COLOR" then
+ -- The surface has an alpha channel which *might* be non-opaque
+ return
+ end
+
+ -- Only the "NONE" extend mode is forbidden, everything else doesn't
+ -- introduce transparent parts
+ if pattern:get_extend() == "NONE" then
+ return
+ end
+
+ return pattern
+ elseif kind == "LINEAR" then
+ local _, stops = pattern:get_color_stop_count()
+
+ -- No color stops or extend NONE -> pattern *might* contain transparency
+ if stops == 0 or pattern:get_extend() == "NONE" then
+ return
+ end
+
+ -- Now check if any of the color stops contain transparency
+ for i = 0, stops - 1 do
+ local _, _, _, _, _, alpha = pattern:get_color_stop_rgba(i)
+ if alpha ~= 1 then
+ return
+ end
+ end
+ return pattern
+ end
+
+ -- Unknown type, e.g. mesh or raster source or unsupported type (radial
+ -- gradients can do weird self-intersections)
+end
+
+--- Fill non-transparent area of an image with a given color.
+-- @param image Image or path to it.
+-- @param new_color New color.
+-- @return Recolored image.
+function color.recolor_image(image, new_color)
+ if type(image) == 'string' then
+ image = surface.duplicate_surface(image)
+ end
+ local cr = cairo.Context.create(image)
+ cr:set_source(color.create_pattern(new_color))
+ cr:mask(cairo.Pattern.create_for_surface(image), 0, 0)
+ return image
+end
+
+function color.mt.__call(_, ...)
+ return color.create_pattern(...)
+end
+
+pattern_cache = require("gears.cache").new(color.create_pattern_uncached)
+
+--- No color
+color.transparent = color.create_pattern("#00000000")
+
+return setmetatable(color, color.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/gears/debug.lua b/lib/gears/debug.lua
new file mode 100644
index 0000000..55f72f5
--- /dev/null
+++ b/lib/gears/debug.lua
@@ -0,0 +1,78 @@
+---------------------------------------------------------------------------
+-- @author Uli Schlachter
+-- @copyright 2010 Uli Schlachter
+-- @module gears.debug
+---------------------------------------------------------------------------
+
+local tostring = tostring
+local print = print
+local type = type
+local pairs = pairs
+
+local debug = {}
+
+--- Given a table (or any other data) return a string that contains its
+-- tag, value and type. If data is a table then recursively call `dump_raw`
+-- on each of its values.
+-- @param data Value to inspect.
+-- @param shift Spaces to indent lines with.
+-- @param tag The name of the value.
+-- @tparam[opt=10] int depth Depth of recursion.
+-- @return a string which contains tag, value, value type and table key/value
+-- pairs if data is a table.
+local function dump_raw(data, shift, tag, depth)
+ depth = depth == nil and 10 or depth or 0
+ local result = ""
+
+ if tag then
+ result = result .. tostring(tag) .. " : "
+ end
+
+ if type(data) == "table" and depth > 0 then
+ shift = (shift or "") .. " "
+ result = result .. tostring(data)
+ for k, v in pairs(data) do
+ result = result .. "\n" .. shift .. dump_raw(v, shift, k, depth - 1)
+ end
+ else
+ result = result .. tostring(data) .. " (" .. type(data) .. ")"
+ if depth == 0 and type(data) == "table" then
+ result = result .. " […]"
+ end
+ end
+
+ return result
+end
+
+--- Inspect the value in data.
+-- @param data Value to inspect.
+-- @param tag The name of the value.
+-- @tparam[opt] int depth Depth of recursion.
+-- @return string A string that contains the expanded value of data.
+function debug.dump_return(data, tag, depth)
+ return dump_raw(data, nil, tag, depth)
+end
+
+--- Print the table (or any other value) to the console.
+-- @param data Table to print.
+-- @param tag The name of the table.
+-- @tparam[opt] int depth Depth of recursion.
+function debug.dump(data, tag, depth)
+ print(debug.dump_return(data, tag, depth))
+end
+
+--- Print an warning message
+-- @tparam string message The warning message to print
+function debug.print_warning(message)
+ io.stderr:write(os.date("%Y-%m-%d %T W: ") .. tostring(message) .. "\n")
+end
+
+--- Print an error message
+-- @tparam string message The error message to print
+function debug.print_error(message)
+ io.stderr:write(os.date("%Y-%m-%d %T E: ") .. tostring(message) .. "\n")
+end
+
+return debug
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/gears/geometry.lua b/lib/gears/geometry.lua
new file mode 100644
index 0000000..a429abd
--- /dev/null
+++ b/lib/gears/geometry.lua
@@ -0,0 +1,240 @@
+---------------------------------------------------------------------------
+--
+-- Helper functions used to compute geometries.
+--
+-- When this module refer to a geometry table, this assume a table with at least
+-- an *x*, *y*, *width* and *height* keys and numeric values.
+--
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2008 Julien Danjou
+-- @module gears.geometry
+---------------------------------------------------------------------------
+local math = math
+
+local gears = {geometry = {rectangle = {} } }
+
+--- Get the square distance between a rectangle and a point.
+-- @tparam table geom A rectangle
+-- @tparam number geom.x The horizontal coordinate
+-- @tparam number geom.y The vertical coordinate
+-- @tparam number geom.width The rectangle width
+-- @tparam number geom.height The rectangle height
+-- @tparam number x X coordinate of point
+-- @tparam number y Y coordinate of point
+-- @treturn number The squared distance of the rectangle to the provided point
+function gears.geometry.rectangle.get_square_distance(geom, x, y)
+ local dist_x, dist_y = 0, 0
+ if x < geom.x then
+ dist_x = geom.x - x
+ elseif x >= geom.x + geom.width then
+ dist_x = x - geom.x - geom.width + 1
+ end
+ if y < geom.y then
+ dist_y = geom.y - y
+ elseif y >= geom.y + geom.height then
+ dist_y = y - geom.y - geom.height + 1
+ end
+ return dist_x * dist_x + dist_y * dist_y
+end
+
+--- Return the closest rectangle from `list` for a given point.
+-- @tparam table list A list of geometry tables.
+-- @tparam number x The x coordinate
+-- @tparam number y The y coordinate
+-- @return The key from the closest geometry.
+function gears.geometry.rectangle.get_closest_by_coord(list, x, y)
+ local dist = math.huge
+ local ret = nil
+
+ for k, v in pairs(list) do
+ local d = gears.geometry.rectangle.get_square_distance(v, x, y)
+ if d < dist then
+ ret, dist = k, d
+ end
+ end
+
+ return ret
+end
+
+--- Return the rectangle containing the [x, y] point.
+--
+-- Note that if multiple element from the geometry list contains the point, the
+-- returned result is nondeterministic.
+--
+-- @tparam table list A list of geometry tables.
+-- @tparam number x The x coordinate
+-- @tparam number y The y coordinate
+-- @return The key from the closest geometry. In case no result is found, *nil*
+-- is returned.
+function gears.geometry.rectangle.get_by_coord(list, x, y)
+ for k, geometry in pairs(list) do
+ if x >= geometry.x and x < geometry.x + geometry.width
+ and y >= geometry.y and y < geometry.y + geometry.height then
+ return k
+ end
+ end
+end
+
+--- Return true whether rectangle B is in the right direction
+-- compared to rectangle A.
+-- @param dir The direction.
+-- @param gA The geometric specification for rectangle A.
+-- @param gB The geometric specification for rectangle B.
+-- @return True if B is in the direction of A.
+local function is_in_direction(dir, gA, gB)
+ if dir == "up" then
+ return gA.y > gB.y
+ elseif dir == "down" then
+ return gA.y < gB.y
+ elseif dir == "left" then
+ return gA.x > gB.x
+ elseif dir == "right" then
+ return gA.x < gB.x
+ end
+ return false
+end
+
+--- Calculate distance between two points.
+-- i.e: if we want to move to the right, we will take the right border
+-- of the currently focused screen and the left side of the checked screen.
+-- @param dir The direction.
+-- @param _gA The first rectangle.
+-- @param _gB The second rectangle.
+-- @return The distance between the screens.
+local function calculate_distance(dir, _gA, _gB)
+ local gAx = _gA.x
+ local gAy = _gA.y
+ local gBx = _gB.x
+ local gBy = _gB.y
+
+ if dir == "up" then
+ gBy = _gB.y + _gB.height
+ elseif dir == "down" then
+ gAy = _gA.y + _gA.height
+ elseif dir == "left" then
+ gBx = _gB.x + _gB.width
+ elseif dir == "right" then
+ gAx = _gA.x + _gA.width
+ end
+
+ return math.sqrt(math.pow(gBx - gAx, 2) + math.pow(gBy - gAy, 2))
+end
+
+--- Get the nearest rectangle in the given direction. Every rectangle is specified as a table
+-- with *x*, *y*, *width*, *height* keys, the same as client or screen geometries.
+-- @tparam string dir The direction, can be either *up*, *down*, *left* or *right*.
+-- @tparam table recttbl A table of rectangle specifications.
+-- @tparam table cur The current rectangle.
+-- @return The index for the rectangle in recttbl closer to cur in the given direction. nil if none found.
+function gears.geometry.rectangle.get_in_direction(dir, recttbl, cur)
+ local dist, dist_min
+ local target = nil
+
+ -- We check each object
+ for i, rect in pairs(recttbl) do
+ -- Check geometry to see if object is located in the right direction.
+ if is_in_direction(dir, cur, rect) then
+ -- Calculate distance between current and checked object.
+ dist = calculate_distance(dir, cur, rect)
+
+ -- If distance is shorter then keep the object.
+ if not target or dist < dist_min then
+ target = i
+ dist_min = dist
+ end
+ end
+ end
+ return target
+end
+
+--- Check if an area intersect another area.
+-- @param a The area.
+-- @param b The other area.
+-- @return True if they intersect, false otherwise.
+local function area_intersect_area(a, b)
+ return (b.x < a.x + a.width
+ and b.x + b.width > a.x
+ and b.y < a.y + a.height
+ and b.y + b.height > a.y)
+end
+
+--- Get the intersect area between a and b.
+-- @tparam table a The area.
+-- @tparam number a.x The horizontal coordinate
+-- @tparam number a.y The vertical coordinate
+-- @tparam number a.width The rectangle width
+-- @tparam number a.height The rectangle height
+-- @tparam table b The other area.
+-- @tparam number b.x The horizontal coordinate
+-- @tparam number b.y The vertical coordinate
+-- @tparam number b.width The rectangle width
+-- @tparam number b.height The rectangle height
+-- @treturn table The intersect area.
+function gears.geometry.rectangle.get_intersection(a, b)
+ local g = {}
+ g.x = math.max(a.x, b.x)
+ g.y = math.max(a.y, b.y)
+ g.width = math.min(a.x + a.width, b.x + b.width) - g.x
+ g.height = math.min(a.y + a.height, b.y + b.height) - g.y
+ return g
+end
+
+--- Remove an area from a list, splitting the space between several area that
+-- can overlap.
+-- @tparam table areas Table of areas.
+-- @tparam table elem Area to remove.
+-- @tparam number elem.x The horizontal coordinate
+-- @tparam number elem.y The vertical coordinate
+-- @tparam number elem.width The rectangle width
+-- @tparam number elem.height The rectangle height
+-- @return The new area list.
+function gears.geometry.rectangle.area_remove(areas, elem)
+ for i = #areas, 1, -1 do
+ -- Check if the 'elem' intersect
+ if area_intersect_area(areas[i], elem) then
+ -- It does? remove it
+ local r = table.remove(areas, i)
+ local inter = gears.geometry.rectangle.get_intersection(r, elem)
+
+ if inter.x > r.x then
+ table.insert(areas, {
+ x = r.x,
+ y = r.y,
+ width = inter.x - r.x,
+ height = r.height
+ })
+ end
+
+ if inter.y > r.y then
+ table.insert(areas, {
+ x = r.x,
+ y = r.y,
+ width = r.width,
+ height = inter.y - r.y
+ })
+ end
+
+ if inter.x + inter.width < r.x + r.width then
+ table.insert(areas, {
+ x = inter.x + inter.width,
+ y = r.y,
+ width = (r.x + r.width) - (inter.x + inter.width),
+ height = r.height
+ })
+ end
+
+ if inter.y + inter.height < r.y + r.height then
+ table.insert(areas, {
+ x = r.x,
+ y = inter.y + inter.height,
+ width = r.width,
+ height = (r.y + r.height) - (inter.y + inter.height)
+ })
+ end
+ end
+ end
+
+ return areas
+end
+
+return gears.geometry
diff --git a/lib/gears/init.lua b/lib/gears/init.lua
new file mode 100644
index 0000000..eae92ee
--- /dev/null
+++ b/lib/gears/init.lua
@@ -0,0 +1,23 @@
+---------------------------------------------------------------------------
+-- @author Uli Schlachter
+-- @copyright 2010 Uli Schlachter
+-- @module gears
+---------------------------------------------------------------------------
+
+
+return
+{
+ color = require("gears.color");
+ debug = require("gears.debug");
+ object = require("gears.object");
+ surface = require("gears.surface");
+ wallpaper = require("gears.wallpaper");
+ timer = require("gears.timer");
+ cache = require("gears.cache");
+ matrix = require("gears.matrix");
+ shape = require("gears.shape");
+ protected_call = require("gears.protected_call");
+ geometry = require("gears.geometry");
+}
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/gears/matrix.lua b/lib/gears/matrix.lua
new file mode 100644
index 0000000..a6bc975
--- /dev/null
+++ b/lib/gears/matrix.lua
@@ -0,0 +1,219 @@
+---------------------------------------------------------------------------
+-- An implementation of matrices for describing and working with affine
+-- transformations.
+-- @author Uli Schlachter
+-- @copyright 2015 Uli Schlachter
+-- @classmod gears.matrix
+---------------------------------------------------------------------------
+
+local cairo = require("lgi").cairo
+local matrix = {}
+
+-- Metatable for matrix instances. This is set up near the end of the file.
+local matrix_mt = {}
+
+--- Create a new matrix instance
+-- @tparam number xx The xx transformation part.
+-- @tparam number yx The yx transformation part.
+-- @tparam number xy The xy transformation part.
+-- @tparam number yy The yy transformation part.
+-- @tparam number x0 The x0 transformation part.
+-- @tparam number y0 The y0 transformation part.
+-- @return A new matrix describing the given transformation.
+function matrix.create(xx, yx, xy, yy, x0, y0)
+ return setmetatable({
+ xx = xx, xy = xy, x0 = x0,
+ yx = yx, yy = yy, y0 = y0
+ }, matrix_mt)
+end
+
+--- Create a new translation matrix
+-- @tparam number x The translation in x direction.
+-- @tparam number y The translation in y direction.
+-- @return A new matrix describing the given transformation.
+function matrix.create_translate(x, y)
+ return matrix.create(1, 0, 0, 1, x, y)
+end
+
+--- Create a new scaling matrix
+-- @tparam number sx The scaling in x direction.
+-- @tparam number sy The scaling in y direction.
+-- @return A new matrix describing the given transformation.
+function matrix.create_scale(sx, sy)
+ return matrix.create(sx, 0, 0, sy, 0, 0)
+end
+
+--- Create a new rotation matrix
+-- @tparam number angle The angle of the rotation in radians.
+-- @return A new matrix describing the given transformation.
+function matrix.create_rotate(angle)
+ local c, s = math.cos(angle), math.sin(angle)
+ return matrix.create(c, s, -s, c, 0, 0)
+end
+
+--- Create a new rotation matrix rotating around a custom point
+-- @tparam number x The horizontal rotation point
+-- @tparam number y The vertical rotation point
+-- @tparam number angle The angle of the rotation in radians.
+-- @return A new matrix describing the given transformation.
+function matrix.create_rotate_at(x, y, angle)
+ return matrix.create_translate( -x, -y )
+ * matrix.create_rotate ( angle )
+ * matrix.create_translate( x, y )
+end
+
+--- Translate this matrix
+-- @tparam number x The translation in x direction.
+-- @tparam number y The translation in y direction.
+-- @return A new matrix describing the new transformation.
+function matrix:translate(x, y)
+ return matrix.create_translate(x, y):multiply(self)
+end
+
+--- Scale this matrix
+-- @tparam number sx The scaling in x direction.
+-- @tparam number sy The scaling in y direction.
+-- @return A new matrix describing the new transformation.
+function matrix:scale(sx, sy)
+ return matrix.create_scale(sx, sy):multiply(self)
+end
+
+--- Rotate this matrix
+-- @tparam number angle The angle of the rotation in radians.
+-- @return A new matrix describing the new transformation.
+function matrix:rotate(angle)
+ return matrix.create_rotate(angle):multiply(self)
+end
+
+--- Rotate a shape from a custom point
+-- @tparam number x The horizontal rotation point
+-- @tparam number y The vertical rotation point
+-- @tparam number angle The angle (in radiant: -2*math.pi to 2*math.pi)
+-- @return A transformation object
+function matrix:rotate_at(x, y, angle)
+ return self * matrix.create_rotate_at(x, y, angle)
+end
+
+--- Invert this matrix
+-- @return A new matrix describing the inverse transformation.
+function matrix:invert()
+ -- Beware of math! (I just copied the algorithm from cairo's source code)
+ local a, b, c, d, x0, y0 = self.xx, self.yx, self.xy, self.yy, self.x0, self.y0
+ local inv_det = 1/(a*d - b*c)
+ return matrix.create(inv_det * d, inv_det * -b,
+ inv_det * -c, inv_det * a,
+ inv_det * (c * y0 - d * x0), inv_det * (b * x0 - a * y0))
+end
+
+--- Multiply this matrix with another matrix.
+-- The resulting matrix describes a transformation that is equivalent to first
+-- applying this transformation and then the transformation from `other`.
+-- Note that this function can also be called by directly multiplicating two
+-- matrix instances: `a * b == a:multiply(b)`.
+-- @tparam gears.matrix|cairo.Matrix other The other matrix to multiply with.
+-- @return The multiplication result.
+function matrix:multiply(other)
+ local ret = matrix.create(self.xx * other.xx + self.yx * other.xy,
+ self.xx * other.yx + self.yx * other.yy,
+ self.xy * other.xx + self.yy * other.xy,
+ self.xy * other.yx + self.yy * other.yy,
+ self.x0 * other.xx + self.y0 * other.xy + other.x0,
+ self.x0 * other.yx + self.y0 * other.yy + other.y0)
+
+ return ret
+end
+
+--- Check if two matrices are equal.
+-- Note that this function cal also be called by directly comparing two matrix
+-- instances: `a == b`.
+-- @tparam gears.matrix|cairo.Matrix other The matrix to compare with.
+-- @return True if this and the other matrix are equal.
+function matrix:equals(other)
+ for _, k in pairs{ "xx", "xy", "yx", "yy", "x0", "y0" } do
+ if self[k] ~= other[k] then
+ return false
+ end
+ end
+ return true
+end
+
+--- Get a string representation of this matrix
+-- @return A string showing this matrix in column form.
+function matrix:tostring()
+ return string.format("[[%g, %g], [%g, %g], [%g, %g]]",
+ self.xx, self.yx, self.xy,
+ self.yy, self.x0, self.y0)
+end
+
+--- Transform a distance by this matrix.
+-- The difference to @{matrix:transform_point} is that the translation part of
+-- this matrix is ignored.
+-- @tparam number x The x coordinate of the point.
+-- @tparam number y The y coordinate of the point.
+-- @treturn number The x coordinate of the transformed point.
+-- @treturn number The x coordinate of the transformed point.
+function matrix:transform_distance(x, y)
+ return self.xx * x + self.xy * y, self.yx * x + self.yy * y
+end
+
+--- Transform a point by this matrix.
+-- @tparam number x The x coordinate of the point.
+-- @tparam number y The y coordinate of the point.
+-- @treturn number The x coordinate of the transformed point.
+-- @treturn number The y coordinate of the transformed point.
+function matrix:transform_point(x, y)
+ x, y = self:transform_distance(x, y)
+ return self.x0 + x, self.y0 + y
+end
+
+--- Calculate a bounding rectangle for transforming a rectangle by a matrix.
+-- @tparam number x The x coordinate of the rectangle.
+-- @tparam number y The y coordinate of the rectangle.
+-- @tparam number width The width of the rectangle.
+-- @tparam number height The height of the rectangle.
+-- @treturn number X coordinate of the bounding rectangle.
+-- @treturn number Y coordinate of the bounding rectangle.
+-- @treturn number Width of the bounding rectangle.
+-- @treturn number Height of the bounding rectangle.
+function matrix:transform_rectangle(x, y, width, height)
+ -- Transform all four corners of the rectangle
+ local x1, y1 = self:transform_point(x, y)
+ local x2, y2 = self:transform_point(x, y + height)
+ local x3, y3 = self:transform_point(x + width, y + height)
+ local x4, y4 = self:transform_point(x + width, y)
+ -- Find the extremal points of the result
+ x = math.min(x1, x2, x3, x4)
+ y = math.min(y1, y2, y3, y4)
+ width = math.max(x1, x2, x3, x4) - x
+ height = math.max(y1, y2, y3, y4) - y
+
+ return x, y, width, height
+end
+
+--- Convert to a cairo matrix
+-- @treturn cairo.Matrix A cairo matrix describing the same transformation.
+function matrix:to_cairo_matrix()
+ local ret = cairo.Matrix()
+ ret:init(self.xx, self.yx, self.xy, self.yy, self.x0, self.y0)
+ return ret
+end
+
+--- Convert to a cairo matrix
+-- @tparam cairo.Matrix mat A cairo matrix describing the sought transformation
+-- @treturn gears.matrix A matrix instance describing the same transformation.
+function matrix.from_cairo_matrix(mat)
+ return matrix.create(mat.xx, mat.yx, mat.xy, mat.yy, mat.x0, mat.y0)
+end
+
+matrix_mt.__index = matrix
+matrix_mt.__newindex = error
+matrix_mt.__eq = matrix.equals
+matrix_mt.__mul = matrix.multiply
+matrix_mt.__tostring = matrix.tostring
+
+--- A constant for the identity matrix.
+matrix.identity = matrix.create(1, 0, 0, 1, 0, 0)
+
+return matrix
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/gears/object.lua b/lib/gears/object.lua
new file mode 100644
index 0000000..e6436e3
--- /dev/null
+++ b/lib/gears/object.lua
@@ -0,0 +1,285 @@
+---------------------------------------------------------------------------
+-- The object oriented programming base class used by various Awesome
+-- widgets and components.
+--
+-- It provide basic observer pattern, signaling and dynamic properties.
+--
+-- @author Uli Schlachter
+-- @copyright 2010 Uli Schlachter
+-- @classmod gears.object
+---------------------------------------------------------------------------
+
+local setmetatable = setmetatable
+local pairs = pairs
+local type = type
+local error = error
+local properties = require("gears.object.properties")
+
+local object = { properties = properties, mt = {} }
+
+--- Verify that obj is indeed a valid object as returned by new()
+local function check(obj)
+ if type(obj) ~= "table" or type(obj._signals) ~= "table" then
+ error("called on non-object")
+ end
+end
+
+--- Find a given signal
+-- @tparam table obj The object to search in
+-- @tparam string name The signal to find
+-- @treturn table The signal table
+local function find_signal(obj, name)
+ check(obj)
+ if not obj._signals[name] then
+ assert(type(name) == "string", "name must be a string, got: " .. type(name))
+ obj._signals[name] = {
+ strong = {},
+ weak = setmetatable({}, { __mode = "kv" })
+ }
+ end
+ return obj._signals[name]
+end
+
+function object.add_signal()
+ require("awful.util").deprecate("Use signals without explicitly adding them. This is now done implicitly.")
+end
+
+--- Connect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+function object:connect_signal(name, func)
+ assert(type(func) == "function", "callback must be a function, got: " .. type(func))
+ local sig = find_signal(self, name)
+ assert(sig.weak[func] == nil, "Trying to connect a strong callback which is already connected weakly")
+ sig.strong[func] = true
+end
+
+local function make_the_gc_obey(func)
+ if _VERSION <= "Lua 5.1" then
+ -- Lua 5.1 only has the behaviour we want if a userdata is used as the
+ -- value in a weak table. Thus, do some magic so that we get a userdata.
+
+ -- luacheck: globals newproxy getfenv setfenv
+ local userdata = newproxy(true)
+ getmetatable(userdata).__gc = function() end
+ -- Now bind the lifetime of userdata to the lifetime of func. For this,
+ -- we mess with the function's environment and add a table for all the
+ -- various userdata that it should keep alive.
+ local key = "_secret_key_used_by_gears_object_in_Lua51"
+ local old_env = getfenv(func)
+ if old_env[key] then
+ -- Assume the code in the else branch added this and the function
+ -- already has its own, private environment
+ table.insert(old_env[key], userdata)
+ else
+ -- No table yet, add it
+ local new_env = { [key] = { userdata } }
+ setmetatable(new_env, { __index = old_env, __newindex = old_env })
+ setfenv(func, new_env)
+ end
+ assert(_G[key] == nil, "Something broke, things escaped to _G")
+ return userdata
+ end
+ -- Lua 5.2+ already behaves the way we want with functions directly, no magic
+ return func
+end
+
+--- Connect to a signal weakly. This allows the callback function to be garbage
+-- collected and automatically disconnects the signal when that happens.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+function object:weak_connect_signal(name, func)
+ assert(type(func) == "function", "callback must be a function, got: " .. type(func))
+ local sig = find_signal(self, name)
+ assert(sig.strong[func] == nil, "Trying to connect a weak callback which is already connected strongly")
+ sig.weak[func] = make_the_gc_obey(func)
+end
+
+--- Disonnect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback that should be disconnected
+function object:disconnect_signal(name, func)
+ local sig = find_signal(self, name)
+ sig.weak[func] = nil
+ sig.strong[func] = nil
+end
+
+--- Emit a signal.
+--
+-- @tparam string name The name of the signal
+-- @param ... Extra arguments for the callback functions. Each connected
+-- function receives the object as first argument and then any extra arguments
+-- that are given to emit_signal()
+function object:emit_signal(name, ...)
+ local sig = find_signal(self, name)
+ for func in pairs(sig.strong) do
+ func(self, ...)
+ end
+ for func in pairs(sig.weak) do
+ func(self, ...)
+ end
+end
+
+local function get_miss(self, key)
+ local class = rawget(self, "_class")
+
+ if rawget(self, "get_"..key) then
+ return rawget(self, "get_"..key)(self)
+ elseif class and class["get_"..key] then
+ return class["get_"..key](self)
+ elseif class then
+ return class[key]
+ end
+
+end
+
+local function set_miss(self, key, value)
+ local class = rawget(self, "_class")
+
+ if rawget(self, "set_"..key) then
+ return rawget(self, "set_"..key)(self, value)
+ elseif class and class["set_"..key] then
+ return class["set_"..key](self, value)
+ elseif rawget(self, "_enable_auto_signals") then
+ local changed = class[key] ~= value
+ class[key] = value
+
+ if changed then
+ self:emit_signal("property::"..key, value)
+ end
+ elseif (not rawget(self, "get_"..key))
+ and not (class and class["get_"..key]) then
+ return rawset(self, key, value)
+ else
+ error("Cannot set '" .. tostring(key) .. "' on " .. tostring(self)
+ .. " because it is read-only")
+ end
+end
+
+--- Returns a new object. You can call `:emit_signal()`, `:disconnect_signal()`
+-- and `:connect_signal()` on the resulting object.
+--
+-- Note that `args.enable_auto_signals` is only supported when
+-- `args.enable_properties` is true.
+--
+--
+--
+--
+--**Usage example output**:
+--
+-- In get foo bar
+-- bar
+-- In set foo 42
+-- In get foo 42
+-- 42
+-- In a mathod 1 2 3
+-- nil
+-- In the connection handler! a cow
+-- a cow
+--
+--
+-- @usage
+-- -- Create a class for this object. It will be used as a backup source for
+-- -- methods and accessors. It is also possible to set them directly on the
+-- -- object.
+--local class = {}
+--function class:get_foo()
+-- print('In get foo', self._foo or 'bar')
+-- return self._foo or 'bar'
+--end
+--function class:set_foo(value)
+-- print('In set foo', value)
+-- -- In case it is necessary to bypass the object property system, use
+-- -- `rawset`
+-- rawset(self, '_foo', value)
+-- -- When using custom accessors, the signals need to be handled manually
+-- self:emit_signal('property::foo', value)
+--end
+--function class:method(a, b, c)
+-- print('In a mathod', a, b, c)
+--end
+--local o = gears.object {
+-- class = class,
+-- enable_properties = true,
+-- enable_auto_signals = true,
+--}
+--print(o.foo)
+--o.foo = 42
+--print(o.foo)
+--o:method(1, 2, 3)
+-- -- Random properties can also be added, the signal will be emitted automatically.
+--o:connect_signal('property::something', function(obj, value)
+-- assert(obj == o)
+-- print('In the connection handler!', value)
+--end)
+--print(o.something)
+--o.something = 'a cow'
+--print(o.something)
+-- @tparam[opt={}] table args The arguments
+-- @tparam[opt=false] boolean args.enable_properties Automatically call getters and setters
+-- @tparam[opt=false] boolean args.enable_auto_signals Generate "property::xxxx" signals
+-- when an unknown property is set.
+-- @tparam[opt=nil] table args.class
+-- @treturn table A new object
+-- @function gears.object
+local function new(args)
+ args = args or {}
+ local ret = {}
+
+ -- Automatic signals cannot work without both miss handlers.
+ assert(not (args.enable_auto_signals and args.enable_properties ~= true))
+
+ -- Copy all our global functions to our new object
+ for k, v in pairs(object) do
+ if type(v) == "function" then
+ ret[k] = v
+ end
+ end
+
+ ret._signals = {}
+
+ local mt = {}
+
+ -- Look for methods in another table
+ ret._class = args.class
+ ret._enable_auto_signals = args.enable_auto_signals
+
+ -- To catch all changes, a proxy is required
+ if args.enable_auto_signals then
+ ret._class = ret._class and setmetatable({}, {__index = args.class}) or {}
+ end
+
+ if args.enable_properties then
+ -- Check got existing get_xxxx and set_xxxx
+ mt.__index = get_miss
+ mt.__newindex = set_miss
+ elseif args.class then
+ -- Use the class table a miss handler
+ mt.__index = ret._class
+ end
+
+ return setmetatable(ret, mt)
+end
+
+function object.mt.__call(_, ...)
+ return new(...)
+end
+
+--- Helper function to get the module name out of `debug.getinfo`.
+-- @usage
+-- local mt = {}
+-- mt.__tostring = function(o)
+-- return require("gears.object").modulename(2)
+-- end
+-- return setmetatable(ret, mt)
+--
+-- @tparam[opt=2] integer level Level for `debug.getinfo(level, "S")`.
+-- Typically 2 or 3.
+-- @treturn string The module name, e.g. "wibox.container.background".
+function object.modulename(level)
+ return debug.getinfo(level, "S").source:gsub(".*/lib/", ""):gsub("/", "."):gsub("%.lua", "")
+end
+
+return setmetatable(object, object.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/gears/object/properties.lua b/lib/gears/object/properties.lua
new file mode 100644
index 0000000..36b8fcb
--- /dev/null
+++ b/lib/gears/object/properties.lua
@@ -0,0 +1,88 @@
+---------------------------------------------------------------------------
+--- An helper module to map userdata __index and __newindex entries to
+-- lua classes.
+--
+-- @author Emmanuel Lepage-Vallee &lt;elv1313@gmail.com&gt;
+-- @copyright 2016 Emmanuel Lepage-Vallee
+-- @module gears.object.properties
+---------------------------------------------------------------------------
+
+local object = {}
+
+
+--- Add the missing properties handler to a CAPI object such as client/tag/screen.
+-- Valid args:
+--
+-- * **getter**: A smart getter (handle property getter itself)
+-- * **getter_fallback**: A dumb getter method (don't handle individual property getter)
+-- * **getter_class**: A module with individual property getter/setter
+-- * **getter_prefix**: A special getter prefix (like "get" or "get_" (default))
+-- * **setter**: A smart setter (handle property setter itself)
+-- * **setter_fallback**: A dumb setter method (don't handle individual property setter)
+-- * **setter_class**: A module with individual property getter/setter
+-- * **setter_prefix**: A special setter prefix (like "set" or "set_" (default))
+-- * **auto_emit**: Emit "property::___" automatically (default: false). This is
+-- ignored when setter_fallback is set or a setter is found
+--
+-- @param class A standard luaobject derived object
+-- @tparam[opt={}] table args A set of accessors configuration parameters
+function object.capi_index_fallback(class, args)
+ args = args or {}
+
+ local getter_prefix = args.getter_prefix or "get_"
+ local setter_prefix = args.setter_prefix or "set_"
+
+ local getter = args.getter or function(cobj, prop)
+ -- Look for a getter method
+ if args.getter_class and args.getter_class[getter_prefix..prop] then
+ return args.getter_class[getter_prefix..prop](cobj)
+ elseif args.getter_class and args.getter_class["is_"..prop] then
+ return args.getter_class["is_"..prop](cobj)
+ end
+
+ -- Make sure something like c:a_mutator() works
+ if args.getter_class and args.getter_class[prop] then
+ return args.getter_class[prop]
+ end
+ -- In case there is already a "dumb" getter like `awful.tag.getproperty'
+ if args.getter_fallback then
+ return args.getter_fallback(cobj, prop)
+ end
+
+ -- Use the fallback property table
+ return cobj.data[prop]
+ end
+
+ local setter = args.setter or function(cobj, prop, value)
+ -- Look for a setter method
+ if args.setter_class and args.setter_class[setter_prefix..prop] then
+ return args.setter_class[setter_prefix..prop](cobj, value)
+ end
+
+ -- In case there is already a "dumb" setter like `awful.client.property.set'
+ if args.setter_fallback then
+ return args.setter_fallback(cobj, prop, value)
+ end
+
+ -- If a getter exists but not a setter, then the property is read-only
+ if args.getter_class and args.getter_class[getter_prefix..prop] then
+ return
+ end
+
+ -- Use the fallback property table
+ cobj.data[prop] = value
+
+ -- Emit the signal
+ if args.auto_emit then
+ cobj:emit_signal("property::"..prop, value)
+ end
+ end
+
+ -- Attach the accessor methods
+ class.set_index_miss_handler(getter)
+ class.set_newindex_miss_handler(setter)
+end
+
+return setmetatable( object, {__call = function(_,...) object.capi_index_fallback(...) end})
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/gears/protected_call.lua b/lib/gears/protected_call.lua
new file mode 100644
index 0000000..c182e14
--- /dev/null
+++ b/lib/gears/protected_call.lua
@@ -0,0 +1,57 @@
+---------------------------------------------------------------------------
+-- @author Uli Schlachter
+-- @copyright 2016 Uli Schlachter
+-- @module gears.protected_call
+---------------------------------------------------------------------------
+
+local gdebug = require("gears.debug")
+local tostring = tostring
+local traceback = debug.traceback
+local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
+local xpcall = xpcall
+
+local protected_call = {}
+
+local function error_handler(err)
+ gdebug.print_error(traceback("Error during a protected call: " .. tostring(err), 2))
+end
+
+local function handle_result(success, ...)
+ if success then
+ return ...
+ end
+end
+
+local do_pcall
+if _VERSION <= "Lua 5.1" then
+ -- Lua 5.1 doesn't support arguments in xpcall :-(
+ do_pcall = function(func, ...)
+ local args = { ... }
+ return handle_result(xpcall(function()
+ return func(unpack(args))
+ end, error_handler))
+ end
+else
+ do_pcall = function(func, ...)
+ return handle_result(xpcall(func, error_handler, ...))
+ end
+end
+
+--- Call a function in protected mode and handle error-reporting.
+-- If the function call succeeds, all results of the function are returned.
+-- Otherwise, an error message is printed and nothing is returned.
+-- @tparam function func The function to call
+-- @param ... Arguments to the function
+-- @return The result of the given function, or nothing if an error occurred.
+function protected_call.call(func, ...)
+ return do_pcall(func, ...)
+end
+
+local pcall_mt = {}
+function pcall_mt:__call(...)
+ return do_pcall(...)
+end
+
+return setmetatable(protected_call, pcall_mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/gears/shape.lua b/lib/gears/shape.lua
new file mode 100644
index 0000000..4962d78
--- /dev/null
+++ b/lib/gears/shape.lua
@@ -0,0 +1,785 @@
+---------------------------------------------------------------------------
+--- Module dedicated to gather common shape painters.
+--
+-- It add the concept of "shape" to Awesome. A shape can be applied to a
+-- background, a margin, a mask or a drawable shape bounding.
+--
+-- The functions exposed by this module always take a context as first
+-- parameter followed by the widget and height and additional parameters.
+--
+-- The functions provided by this module only create a path in the content.
+-- to actually draw the content, use `cr:fill()`, `cr:mask()`, `cr:clip()` or
+-- `cr:stroke()`
+--
+-- In many case, it is necessary to apply the shape using a transformation
+-- such as a rotation. The preferred way to do this is to wrap the function
+-- in another function calling `cr:rotate()` (or any other transformation
+-- matrix).
+--
+-- To specialize a shape where the API doesn't allows extra arguments to be
+-- passed, it is possible to wrap the shape function like:
+--
+-- local new_shape = function(cr, width, height)
+-- gears.shape.rounded_rect(cr, width, height, 2)
+-- end
+--
+-- Many elements can be shaped. This include:
+--
+-- * `client`s (see `gears.surface.apply_shape_bounding`)
+-- * `wibox`es (see `wibox.shape`)
+-- * All widgets (see `wibox.container.background`)
+-- * The progressbar (see `wibox.widget.progressbar.bar_shape`)
+-- * The graph (see `wibox.widget.graph.step_shape`)
+-- * The checkboxes (see `wibox.widget.checkbox.check_shape`)
+-- * Images (see `wibox.widget.imagebox.clip_shape`)
+-- * The taglist tags (see `awful.widget.taglist`)
+-- * The tasklist clients (see `awful.widget.tasklist`)
+-- * The tooltips (see `awful.tooltip`)
+--
+-- @author Emmanuel Lepage Vallee
+-- @copyright 2011-2016 Emmanuel Lepage Vallee
+-- @module gears.shape
+---------------------------------------------------------------------------
+local g_matrix = require( "gears.matrix" )
+local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
+local atan2 = math.atan2 or math.atan -- lua 5.3 compat
+
+local module = {}
+
+--- Add a rounded rectangle to the current path.
+-- Note: If the radius is bigger than either half side, it will be reduced.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_gears_shape_rounded_rect.svg)
+--
+-- @usage
+--shape.rounded_rect(cr, 70, 70, 10)
+--shape.rounded_rect(cr,20,70, 5)
+--shape.transform(shape.rounded_rect) : translate(0,25) (cr,70,20, 5)
+--
+-- @param cr A cairo content
+-- @tparam number width The rectangle width
+-- @tparam number height The rectangle height
+-- @tparam number radius the corner radius
+function module.rounded_rect(cr, width, height, radius)
+
+ radius = radius or 10
+
+ if width / 2 < radius then
+ radius = width / 2
+ end
+
+ if height / 2 < radius then
+ radius = height / 2
+ end
+
+ cr:move_to(0, radius)
+
+ cr:arc( radius , radius , radius, math.pi , 3*(math.pi/2) )
+ cr:arc( width-radius, radius , radius, 3*(math.pi/2), math.pi*2 )
+ cr:arc( width-radius, height-radius, radius, math.pi*2 , math.pi/2 )
+ cr:arc( radius , height-radius, radius, math.pi/2 , math.pi )
+
+ cr:close_path()
+end
+
+--- Add a rectangle delimited by 2 180 degree arcs to the path.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_gears_shape_rounded_bar.svg)
+--
+-- @usage
+--shape.rounded_bar(cr, 70, 70)
+--shape.rounded_bar(cr, 20, 70)
+--shape.rounded_bar(cr, 70, 20)
+--
+-- @param cr A cairo content
+-- @param width The rectangle width
+-- @param height The rectangle height
+function module.rounded_bar(cr, width, height)
+ module.rounded_rect(cr, width, height, height / 2)
+end
+
+--- A rounded rect with only some of the corners rounded.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_gears_shape_partially_rounded_rect.svg)
+--
+-- @usage
+--shape.partially_rounded_rect(cr, 70, 70)
+--shape.partially_rounded_rect(cr, 70, 70, true)
+--shape.partially_rounded_rect(cr, 70, 70, true, true, false, true, 30)
+--
+-- @param cr A cairo context
+-- @tparam number width The shape width
+-- @tparam number height The shape height
+-- @tparam boolean tl If the top left corner is rounded
+-- @tparam boolean tr If the top right corner is rounded
+-- @tparam boolean br If the bottom right corner is rounded
+-- @tparam boolean bl If the bottom left corner is rounded
+-- @tparam number rad The corner radius
+function module.partially_rounded_rect(cr, width, height, tl, tr, br, bl, rad)
+ rad = rad or 10
+ if width / 2 < rad then
+ rad = width / 2
+ end
+
+ if height / 2 < rad then
+ rad = height / 2
+ end
+
+ -- Top left
+ if tl then
+ cr:arc( rad, rad, rad, math.pi, 3*(math.pi/2))
+ else
+ cr:move_to(0,0)
+ end
+
+ -- Top right
+ if tr then
+ cr:arc( width-rad, rad, rad, 3*(math.pi/2), math.pi*2)
+ else
+ cr:line_to(width, 0)
+ end
+
+ -- Bottom right
+ if br then
+ cr:arc( width-rad, height-rad, rad, math.pi*2 , math.pi/2)
+ else
+ cr:line_to(width, height)
+ end
+
+ -- Bottom left
+ if bl then
+ cr:arc( rad, height-rad, rad, math.pi/2, math.pi)
+ else
+ cr:line_to(0, height)
+ end
+
+ cr:close_path()
+end
+
+--- A rounded rectangle with a triangle at the top.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_gears_shape_infobubble.svg)
+--
+-- @usage
+--shape.infobubble(cr, 70, 70)
+--shape.transform(shape.infobubble) : translate(0, 20)
+-- : rotate_at(35,35,math.pi) (cr,70,20,10, 5, 35 - 5)
+--shape.transform(shape.infobubble)
+-- : rotate_at(35,35,3*math.pi/2) (cr,70,70, nil, nil, 40)
+--
+-- @param cr A cairo context
+-- @tparam number width The shape width
+-- @tparam number height The shape height
+-- @tparam[opt=5] number corner_radius The corner radius
+-- @tparam[opt=10] number arrow_size The width and height of the arrow
+-- @tparam[opt=width/2 - arrow_size/2] number arrow_position The position of the arrow
+function module.infobubble(cr, width, height, corner_radius, arrow_size, arrow_position)
+ arrow_size = arrow_size or 10
+ corner_radius = math.min((height-arrow_size)/2, corner_radius or 5)
+ arrow_position = arrow_position or width/2 - arrow_size/2
+
+
+ cr:move_to(0 ,corner_radius+arrow_size)
+
+ -- Top left corner
+ cr:arc(corner_radius, corner_radius+arrow_size, (corner_radius), math.pi, 3*(math.pi/2))
+
+ -- The arrow triangle (still at the top)
+ cr:line_to(arrow_position , arrow_size )
+ cr:line_to(arrow_position + arrow_size , 0 )
+ cr:line_to(arrow_position + 2*arrow_size , arrow_size )
+
+ -- Complete the rounded rounded rectangle
+ cr:arc(width-corner_radius, corner_radius+arrow_size , (corner_radius) , 3*(math.pi/2) , math.pi*2 )
+ cr:arc(width-corner_radius, height-(corner_radius) , (corner_radius) , math.pi*2 , math.pi/2 )
+ cr:arc(corner_radius , height-(corner_radius) , (corner_radius) , math.pi/2 , math.pi )
+
+ -- Close path
+ cr:close_path()
+end
+
+--- A rectangle terminated by an arrow.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_gears_shape_rectangular_tag.svg)
+--
+-- @usage
+--shape.rectangular_tag(cr, 70, 70)
+--shape.transform(shape.rectangular_tag) : translate(0, 30) (cr, 70, 10, 10)
+--shape.transform(shape.rectangular_tag) : translate(0, 30) (cr, 70, 10, -10)
+--
+-- @param cr A cairo context
+-- @tparam number width The shape width
+-- @tparam number height The shape height
+-- @tparam[opt=height/2] number arrow_length The length of the arrow part
+function module.rectangular_tag(cr, width, height, arrow_length)
+ arrow_length = arrow_length or height/2
+ if arrow_length > 0 then
+ cr:move_to(0 , height/2 )
+ cr:line_to(arrow_length , 0 )
+ cr:line_to(width , 0 )
+ cr:line_to(width , height )
+ cr:line_to(arrow_length , height )
+ else
+ cr:move_to(0 , 0 )
+ cr:line_to(-arrow_length, height/2 )
+ cr:line_to(0 , height )
+ cr:line_to(width , height )
+ cr:line_to(width , 0 )
+ end
+
+ cr:close_path()
+end
+
+--- A simple arrow shape.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_gears_shape_arrow.svg)
+--
+-- @usage
+--shape.arrow(cr, 70, 70)
+--shape.arrow(cr,70,70, 30, 10, 60)
+--shape.transform(shape.arrow) : rotate_at(35,35,math.pi/2)(cr,70,70)
+--
+-- @param cr A cairo context
+-- @tparam number width The shape width
+-- @tparam number height The shape height
+-- @tparam[opt=head_width] number head_width The width of the head (/\) of the arrow
+-- @tparam[opt=width /2] number shaft_width The width of the shaft of the arrow
+-- @tparam[opt=height/2] number shaft_length The head_length of the shaft (the rest is the head)
+function module.arrow(cr, width, height, head_width, shaft_width, shaft_length)
+ shaft_length = shaft_length or height / 2
+ shaft_width = shaft_width or width / 2
+ head_width = head_width or width
+ local head_length = height - shaft_length
+
+ cr:move_to ( width/2 , 0 )
+ cr:rel_line_to( head_width/2 , head_length )
+ cr:rel_line_to( -(head_width-shaft_width)/2 , 0 )
+ cr:rel_line_to( 0 , shaft_length )
+ cr:rel_line_to( -shaft_width , 0 )
+ cr:rel_line_to( 0 , -shaft_length )
+ cr:rel_line_to( -(head_width-shaft_width)/2 , 0 )
+
+ cr:close_path()
+end
+
+--- A squeezed hexagon filling the rectangle.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_gears_shape_hexagon.svg)
+--
+-- @usage
+--shape.hexagon(cr, 70, 70)
+--shape.transform(shape.hexagon) : translate(0,15)(cr,70,20)
+--shape.transform(shape.hexagon) : rotate_at(35,35,math.pi/2)(cr,70,40)
+--
+-- @param cr A cairo context
+-- @tparam number width The shape width
+-- @tparam number height The shape height
+function module.hexagon(cr, width, height)
+ cr:move_to(height/2,0)
+ cr:line_to(width-height/2,0)
+ cr:line_to(width,height/2)
+ cr:line_to(width-height/2,height)
+ cr:line_to(height/2,height)
+ cr:line_to(0,height/2)
+ cr:line_to(height/2,0)
+ cr:close_path()
+end
+
+--- Double arrow popularized by the vim-powerline module.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_gears_shape_powerline.svg)
+--
+-- @usage
+--shape.powerline(cr, 70, 70)
+--shape.transform(shape.powerline) : translate(0, 25) (cr,70,20)
+--shape.transform(shape.powerline) : translate(0, 25) (cr,70,20, -20)
+--
+-- @param cr A cairo context
+-- @tparam number width The shape width
+-- @tparam number height The shape height
+-- @tparam[opt=height/2] number arrow_depth The width of the arrow part of the shape
+function module.powerline(cr, width, height, arrow_depth)
+ arrow_depth = arrow_depth or height/2
+ local offset = 0
+
+ -- Avoid going out of the (potential) clip area
+ if arrow_depth < 0 then
+ width = width + 2*arrow_depth
+ offset = -arrow_depth
+ end
+
+ cr:move_to(offset , 0 )
+ cr:line_to(offset + width - arrow_depth , 0 )
+ cr:line_to(offset + width , height/2 )
+ cr:line_to(offset + width - arrow_depth , height )
+ cr:line_to(offset , height )
+ cr:line_to(offset + arrow_depth , height/2 )
+
+ cr:close_path()
+end
+
+--- An isosceles triangle.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_gears_shape_isosceles_triangle.svg)
+--
+-- @usage
+--shape.isosceles_triangle(cr, 70, 70)
+--shape.isosceles_triangle(cr,20,70)
+--shape.transform(shape.isosceles_triangle) : rotate_at(35, 35, math.pi/2)(cr,70,70)
+--
+-- @param cr A cairo context
+-- @tparam number width The shape width
+-- @tparam number height The shape height
+function module.isosceles_triangle(cr, width, height)
+ cr:move_to( width/2, 0 )
+ cr:line_to( width , height )
+ cr:line_to( 0 , height )
+ cr:close_path()
+end
+
+--- A cross (**+**) symbol.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_gears_shape_cross.svg)
+--
+-- @usage
+--shape.cross(cr, 70, 70)
+--shape.cross(cr,20,70)
+--shape.transform(shape.cross) : scale(0.5, 1)(cr,70,70)
+--
+-- @param cr A cairo context
+-- @tparam number width The shape width
+-- @tparam number height The shape height
+-- @tparam[opt=width/3] number thickness The cross section thickness
+function module.cross(cr, width, height, thickness)
+ thickness = thickness or width/3
+ local xpadding = (width - thickness) / 2
+ local ypadding = (height - thickness) / 2
+ cr:move_to(xpadding, 0)
+ cr:line_to(width - xpadding, 0)
+ cr:line_to(width - xpadding, ypadding)
+ cr:line_to(width , ypadding)
+ cr:line_to(width , height-ypadding)
+ cr:line_to(width - xpadding, height-ypadding)
+ cr:line_to(width - xpadding, height )
+ cr:line_to(xpadding , height )
+ cr:line_to(xpadding , height-ypadding)
+ cr:line_to(0 , height-ypadding)
+ cr:line_to(0 , ypadding )
+ cr:line_to(xpadding , ypadding )
+ cr:close_path()
+end
+
+--- A similar shape to the `rounded_rect`, but with sharp corners.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_gears_shape_octogon.svg)
+--
+-- @usage
+--shape.octogon(cr, 70, 70)
+--shape.octogon(cr,70,70,70/2.5)
+--shape.transform(shape.octogon) : translate(0, 25) (cr,70,20)
+--
+-- @param cr A cairo context
+-- @tparam number width The shape width
+-- @tparam number height The shape height
+-- @tparam number corner_radius
+function module.octogon(cr, width, height, corner_radius)
+ corner_radius = corner_radius or math.min(10, math.min(width, height)/4)
+ local offset = math.sqrt( (corner_radius*corner_radius) / 2 )
+
+ cr:move_to(offset, 0)
+ cr:line_to(width-offset, 0)
+ cr:line_to(width, offset)
+ cr:line_to(width, height-offset)
+ cr:line_to(width-offset, height)
+ cr:line_to(offset, height)
+ cr:line_to(0, height-offset)
+ cr:line_to(0, offset)
+ cr:close_path()
+end
+
+--- A circle shape.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_gears_shape_circle.svg)
+--
+-- @usage
+--shape.circle(cr, 70, 70)
+--shape.circle(cr,20,70)
+--shape.transform(shape.circle) : scale(0.5, 1)(cr,70,70)
+--
+-- @param cr A cairo context
+-- @tparam number width The shape width
+-- @tparam number height The shape height
+-- @tparam[opt=math.min(width height) / 2)] number radius The radius
+function module.circle(cr, width, height, radius)
+ radius = radius or math.min(width, height) / 2
+ cr:move_to(width/2+radius, height/2)
+ cr:arc(width / 2, height / 2, radius, 0, 2*math.pi)
+ cr:close_path()
+end
+
+--- A simple rectangle.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_gears_shape_rectangle.svg)
+--
+-- @usage
+--shape.rectangle(cr, 70, 70)
+--shape.rectangle(cr,20,70)
+--shape.transform(shape.rectangle) : scale(0.5, 1)(cr,70,70)
+--
+-- @param cr A cairo context
+-- @tparam number width The shape width
+-- @tparam number height The shape height
+function module.rectangle(cr, width, height)
+ cr:rectangle(0, 0, width, height)
+end
+
+--- A diagonal parallelogram with the bottom left corner at x=0 and top right
+-- at x=width.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_gears_shape_parallelogram.svg)
+--
+-- @usage
+--shape.parallelogram(cr, 70, 70)
+--shape.parallelogram(cr,70,20)
+--shape.transform(shape.parallelogram) : scale(0.5, 1)(cr,70,70)
+--
+-- @param cr A cairo context
+-- @tparam number width The shape width
+-- @tparam number height The shape height
+-- @tparam[opt=width/3] number base_width The parallelogram base width
+function module.parallelogram(cr, width, height, base_width)
+ base_width = base_width or width/3
+ cr:move_to(width-base_width, 0 )
+ cr:line_to(width , 0 )
+ cr:line_to(base_width , height )
+ cr:line_to(0 , height )
+ cr:close_path()
+end
+
+--- A losange.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_gears_shape_losange.svg)
+--
+-- @usage
+--shape.losange(cr, 70, 70)
+--shape.losange(cr,20,70)
+--shape.transform(shape.losange) : scale(0.5, 1)(cr,70,70)
+--
+-- @param cr A cairo context
+-- @tparam number width The shape width
+-- @tparam number height The shape height
+function module.losange(cr, width, height)
+ cr:move_to(width/2 , 0 )
+ cr:line_to(width , height/2 )
+ cr:line_to(width/2 , height )
+ cr:line_to(0 , height/2 )
+ cr:close_path()
+end
+
+--- A pie.
+--
+-- The pie center is the center of the area.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_gears_shape_pie.svg)
+--
+-- @usage
+--shape.pie(cr, 70, 70)
+--shape.pie(cr,70,70, 1.0471975511966, 4.1887902047864)
+--shape.pie(cr,70,70, 0, 2*math.pi, 10)
+--
+-- @param cr A cairo context
+-- @tparam number width The shape width
+-- @tparam number height The shape height
+-- @tparam[opt=0] number start_angle The start angle (in radian)
+-- @tparam[opt=math.pi/2] number end_angle The end angle (in radian)
+-- @tparam[opt=math.min(width height)/2] number radius The shape height
+function module.pie(cr, width, height, start_angle, end_angle, radius)
+ radius = radius or math.floor(math.min(width, height)/2)
+ start_angle, end_angle = start_angle or 0, end_angle or math.pi/2
+
+ -- If the shape is a circle, then avoid the lines
+ if math.abs(start_angle + end_angle - 2*math.pi) <= 0.01 then
+ cr:arc(width/2, height/2, radius, 0, 2*math.pi)
+ else
+ cr:move_to(width/2, height/2)
+ cr:line_to(
+ width/2 + math.cos(start_angle)*radius,
+ height/2 + math.sin(start_angle)*radius
+ )
+ cr:arc(width/2, height/2, radius, start_angle, end_angle)
+ end
+
+ cr:close_path()
+end
+
+--- A rounded arc.
+--
+-- The pie center is the center of the area.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_gears_shape_arc.svg)
+--
+-- @usage
+--shape.arc(cr,70,70, 10)
+--shape.arc(cr,70,70, 10, nil, nil, true, true)
+--shape.arc(cr,70,70, nil, 0, 2*math.pi)
+--
+-- @param cr A cairo context
+-- @tparam number width The shape width
+-- @tparam number height The shape height
+-- @tparam[opt=math.min(width height)/2] number thickness The arc thickness
+-- @tparam[opt=0] number start_angle The start angle (in radian)
+-- @tparam[opt=math.pi/2] number end_angle The end angle (in radian)
+-- @tparam[opt=false] boolean start_rounded if the arc start rounded
+-- @tparam[opt=false] boolean end_rounded if the arc end rounded
+function module.arc(cr, width, height, thickness, start_angle, end_angle, start_rounded, end_rounded)
+ start_angle = start_angle or 0
+ end_angle = end_angle or math.pi/2
+
+ -- This shape is a partial circle
+ local radius = math.min(width, height)/2
+
+ thickness = thickness or radius/2
+
+ local inner_radius = radius - thickness
+
+ -- As the edge of the small arc need to touch the [start_p1, start_p2]
+ -- line, a small subset of the arc circumference has to be substracted
+ -- that's (less or more) equal to the thickness/2 (a little longer given
+ -- it is an arc and not a line, but it wont show)
+ local arc_percent = math.abs(end_angle-start_angle)/(2*math.pi)
+ local arc_length = ((radius-thickness/2)*2*math.pi)*arc_percent
+
+ if start_rounded then
+ arc_length = arc_length - thickness/2
+
+ -- And back to angles
+ start_angle = end_angle - (arc_length/(radius - thickness/2))
+ end
+
+ if end_rounded then
+ arc_length = arc_length - thickness/2
+
+ -- And back to angles
+ end_angle = start_angle + (arc_length/(radius - thickness/2))
+ end
+
+ -- The path is a curcular arc joining 4 points
+
+ -- Outer first corner
+ local start_p1 = {
+ width /2 + math.cos(start_angle)*radius,
+ height/2 + math.sin(start_angle)*radius
+ }
+
+ if start_rounded then
+
+ -- Inner first corner
+ local start_p2 = {
+ width /2 + math.cos(start_angle)*inner_radius,
+ height/2 + math.sin(start_angle)*inner_radius
+ }
+
+ local median_angle = atan2(
+ start_p2[1] - start_p1[1],
+ -(start_p2[2] - start_p1[2])
+ )
+
+ local arc_center = {
+ (start_p1[1] + start_p2[1])/2,
+ (start_p1[2] + start_p2[2])/2,
+ }
+
+ cr:arc(arc_center[1], arc_center[2], thickness/2,
+ median_angle-math.pi/2, median_angle+math.pi/2
+ )
+
+ else
+ cr:move_to(unpack(start_p1))
+ end
+
+ cr:arc(width/2, height/2, radius, start_angle, end_angle)
+
+ if end_rounded then
+
+ -- Outer second corner
+ local end_p1 = {
+ width /2 + math.cos(end_angle)*radius,
+ height/2 + math.sin(end_angle)*radius
+ }
+
+ -- Inner first corner
+ local end_p2 = {
+ width /2 + math.cos(end_angle)*inner_radius,
+ height/2 + math.sin(end_angle)*inner_radius
+ }
+ local median_angle = atan2(
+ end_p2[1] - end_p1[1],
+ -(end_p2[2] - end_p1[2])
+ ) - math.pi
+
+ local arc_center = {
+ (end_p1[1] + end_p2[1])/2,
+ (end_p1[2] + end_p2[2])/2,
+ }
+
+ cr:arc(arc_center[1], arc_center[2], thickness/2,
+ median_angle-math.pi/2, median_angle+math.pi/2
+ )
+
+ end
+
+ cr:arc_negative(width/2, height/2, inner_radius, end_angle, start_angle)
+
+ cr:close_path()
+end
+
+--- A partial rounded bar. How much of the rounded bar is visible depends on
+-- the given percentage value.
+--
+-- Note that this shape is not closed and thus filling it doesn't make much
+-- sense.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_gears_shape_radial_progress.svg)
+--
+-- @usage
+--shape.radial_progress(cr, 70, 20, .3)
+--shape.radial_progress(cr, 70, 20, .6)
+--shape.radial_progress(cr, 70, 20, .9)
+--
+-- @param cr A cairo context
+-- @tparam number w The shape width
+-- @tparam number h The shape height
+-- @tparam number percent The progressbar percent
+-- @tparam boolean hide_left Do not draw the left side of the shape
+function module.radial_progress(cr, w, h, percent, hide_left)
+ percent = percent or 1
+ local total_length = (2*(w-h))+2*((h/2)*math.pi)
+ local bar_percent = (w-h)/total_length
+ local arc_percent = ((h/2)*math.pi)/total_length
+
+ -- Bottom line
+ if percent > bar_percent then
+ cr:move_to(h/2,h)
+ cr:line_to((h/2) + (w-h),h)
+ cr:stroke()
+ elseif percent < bar_percent then
+ cr:move_to(h/2,h)
+ cr:line_to(h/2+(total_length*percent),h)
+ cr:stroke()
+ end
+
+ -- Right arc
+ if percent >= bar_percent+arc_percent then
+ cr:arc(w-h/2 , h/2, h/2,3*(math.pi/2),math.pi/2)
+ cr:stroke()
+ elseif percent > bar_percent and percent < bar_percent+(arc_percent/2) then
+ cr:arc(w-h/2 , h/2, h/2,(math.pi/2)-((math.pi/2)*((percent-bar_percent)/(arc_percent/2))),math.pi/2)
+ cr:stroke()
+ elseif percent >= bar_percent+arc_percent/2 and percent < bar_percent+arc_percent then
+ cr:arc(w-h/2 , h/2, h/2,0,math.pi/2)
+ cr:stroke()
+ local add = (math.pi/2)*((percent-bar_percent-arc_percent/2)/(arc_percent/2))
+ cr:arc(w-h/2 , h/2, h/2,2*math.pi-add,0)
+ cr:stroke()
+ end
+
+ -- Top line
+ if percent > 2*bar_percent+arc_percent then
+ cr:move_to((h/2) + (w-h),0)
+ cr:line_to(h/2,0)
+ cr:stroke()
+ elseif percent > bar_percent+arc_percent and percent < 2*bar_percent+arc_percent then
+ cr:move_to((h/2) + (w-h),0)
+ cr:line_to(((h/2) + (w-h))-total_length*(percent-bar_percent-arc_percent),0)
+ cr:stroke()
+ end
+
+ -- Left arc
+ if not hide_left then
+ if percent > 0.985 then
+ cr:arc(h/2, h/2, h/2,math.pi/2,3*(math.pi/2))
+ cr:stroke()
+ elseif percent > 2*bar_percent+arc_percent then
+ local relpercent = (percent - 2*bar_percent - arc_percent)/arc_percent
+ cr:arc(h/2, h/2, h/2,3*(math.pi/2)-(math.pi)*relpercent,3*(math.pi/2))
+ cr:stroke()
+ end
+ end
+end
+
+--- Adjust the shape using a transformation object
+--
+-- Apply various transformations to the shape
+--
+-- @usage gears.shape.transform(gears.shape.rounded_bar)
+-- : rotate(math.pi/2)
+-- : translate(10, 10)
+--
+-- @param shape A shape function
+-- @return A transformation handle, also act as a shape function
+function module.transform(shape)
+
+ -- Apply the transformation matrix and apply the shape, then restore
+ local function apply(self, cr, width, height, ...)
+ cr:save()
+ cr:transform(self.matrix:to_cairo_matrix())
+ shape(cr, width, height, ...)
+ cr:restore()
+ end
+ -- Redirect function calls like :rotate() to the underlying matrix
+ local function index(_, key)
+ return function(self, ...)
+ self.matrix = self.matrix[key](self.matrix, ...)
+ return self
+ end
+ end
+
+ local result = setmetatable({
+ matrix = g_matrix.identity
+ }, {
+ __call = apply,
+ __index = index
+ })
+
+ return result
+end
+
+return module
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/gears/surface.lua b/lib/gears/surface.lua
new file mode 100644
index 0000000..78f2216
--- /dev/null
+++ b/lib/gears/surface.lua
@@ -0,0 +1,252 @@
+---------------------------------------------------------------------------
+-- @author Uli Schlachter
+-- @copyright 2012 Uli Schlachter
+-- @module gears.surface
+---------------------------------------------------------------------------
+
+local setmetatable = setmetatable
+local type = type
+local capi = { awesome = awesome }
+local cairo = require("lgi").cairo
+local color = nil
+local gdebug = require("gears.debug")
+local hierarchy = require("wibox.hierarchy")
+
+-- Keep this in sync with build-utils/lgi-check.sh!
+local ver_major, ver_minor, ver_patch = string.match(require('lgi.version'), '(%d)%.(%d)%.(%d)')
+if tonumber(ver_major) <= 0 and (tonumber(ver_minor) < 7 or (tonumber(ver_minor) == 7 and tonumber(ver_patch) < 1)) then
+ error("lgi too old, need at least version 0.7.1")
+end
+
+local surface = { mt = {} }
+local surface_cache = setmetatable({}, { __mode = 'v' })
+
+local function get_default(arg)
+ if type(arg) == 'nil' then
+ return cairo.ImageSurface(cairo.Format.ARGB32, 0, 0)
+ end
+ return arg
+end
+
+--- Try to convert the argument into an lgi cairo surface.
+-- This is usually needed for loading images by file name.
+-- @param _surface The surface to load or nil
+-- @param default The default value to return on error; when nil, then a surface
+-- in an error state is returned.
+-- @return The loaded surface, or the replacement default
+-- @return An error message, or nil on success
+function surface.load_uncached_silently(_surface, default)
+ local file
+ -- On nil, return some sane default
+ if not _surface then
+ return get_default(default)
+ end
+ -- lgi cairo surfaces don't get changed either
+ if cairo.Surface:is_type_of(_surface) then
+ return _surface
+ end
+ -- Strings are assumed to be file names and get loaded
+ if type(_surface) == "string" then
+ local err
+ file = _surface
+ _surface, err = capi.awesome.load_image(file)
+ if not _surface then
+ return get_default(default), err
+ end
+ end
+ -- Everything else gets forced into a surface
+ return cairo.Surface(_surface, true)
+end
+
+--- Try to convert the argument into an lgi cairo surface.
+-- This is usually needed for loading images by file name and uses a cache.
+-- In contrast to `load()`, errors are returned to the caller.
+-- @param _surface The surface to load or nil
+-- @param default The default value to return on error; when nil, then a surface
+-- in an error state is returned.
+-- @return The loaded surface, or the replacement default, or nil if called with
+-- nil.
+-- @return An error message, or nil on success
+function surface.load_silently(_surface, default)
+ if type(_surface) == "string" then
+ local cache = surface_cache[_surface]
+ if cache then
+ return cache
+ end
+ local result, err = surface.load_uncached_silently(_surface, default)
+ if not err then
+ -- Cache the file
+ surface_cache[_surface] = result
+ end
+ return result, err
+ end
+ return surface.load_uncached_silently(_surface, default)
+end
+
+local function do_load_and_handle_errors(_surface, func)
+ if type(_surface) == 'nil' then
+ return get_default()
+ end
+ local result, err = func(_surface, false)
+ if result then
+ return result
+ end
+ gdebug.print_error(debug.traceback(
+ "Failed to load '" .. tostring(_surface) .. "': " .. tostring(err)))
+ return get_default()
+end
+
+--- Try to convert the argument into an lgi cairo surface.
+-- This is usually needed for loading images by file name. Errors are handled
+-- via `gears.debug.print_error`.
+-- @param _surface The surface to load or nil
+-- @return The loaded surface, or nil
+function surface.load_uncached(_surface)
+ return do_load_and_handle_errors(_surface, surface.load_uncached_silently)
+end
+
+--- Try to convert the argument into an lgi cairo surface.
+-- This is usually needed for loading images by file name. Errors are handled
+-- via `gears.debug.print_error`.
+-- @param _surface The surface to load or nil
+-- @return The loaded surface, or nil
+function surface.load(_surface)
+ return do_load_and_handle_errors(_surface, surface.load_silently)
+end
+
+function surface.mt.__call(_, ...)
+ return surface.load(...)
+end
+
+--- Get the size of a cairo surface
+-- @param surf The surface you are interested in
+-- @return The surface's width and height
+function surface.get_size(surf)
+ local cr = cairo.Context(surf)
+ local x, y, w, h = cr:clip_extents()
+ return w - x, h - y
+end
+
+--- Create a copy of a cairo surface.
+-- The surfaces returned by `surface.load` are cached and must not be
+-- modified to avoid unintended side-effects. This function allows to create
+-- a copy of a cairo surface. This copy can then be freely modified.
+-- The surface returned will be as compatible as possible to the input
+-- surface. For example, it will likely be of the same surface type as the
+-- input. The details are explained in the `create_similar` function on a cairo
+-- surface.
+-- @param s Source surface.
+-- @return The surface's duplicate.
+function surface.duplicate_surface(s)
+ s = surface.load(s)
+
+ -- Figure out surface size (this does NOT work for unbounded recording surfaces)
+ local cr = cairo.Context(s)
+ local x, y, w, h = cr:clip_extents()
+
+ -- Create a copy
+ local result = s:create_similar(s.content, w - x, h - y)
+ cr = cairo.Context(result)
+ cr:set_source_surface(s, 0, 0)
+ cr.operator = cairo.Operator.SOURCE
+ cr:paint()
+ return result
+end
+
+--- Create a surface from a `gears.shape`
+-- Any additional parameters will be passed to the shape function
+-- @tparam number width The surface width
+-- @tparam number height The surface height
+-- @param shape A `gears.shape` compatible function
+-- @param[opt=white] shape_color The shape color or pattern
+-- @param[opt=transparent] bg_color The surface background color
+-- @treturn cairo.surface the new surface
+function surface.load_from_shape(width, height, shape, shape_color, bg_color, ...)
+ color = color or require("gears.color")
+
+ local img = cairo.ImageSurface(cairo.Format.ARGB32, width, height)
+ local cr = cairo.Context(img)
+
+ cr:set_source(color(bg_color or "#00000000"))
+ cr:paint()
+
+ cr:set_source(color(shape_color or "#000000"))
+
+ shape(cr, width, height, ...)
+
+ cr:fill()
+
+ return img
+end
+
+--- Apply a shape to a client or a wibox.
+--
+-- If the wibox or client size change, this function need to be called
+-- again.
+-- @param draw A wibox or a client
+-- @param shape or gears.shape function or a custom function with a context,
+-- width and height as parameter.
+-- @param[opt] Any additional parameters will be passed to the shape function
+function surface.apply_shape_bounding(draw, shape, ...)
+ local geo = draw:geometry()
+
+ local img = cairo.ImageSurface(cairo.Format.A1, geo.width, geo.height)
+ local cr = cairo.Context(img)
+
+ cr:set_operator(cairo.Operator.CLEAR)
+ cr:set_source_rgba(0,0,0,1)
+ cr:paint()
+ cr:set_operator(cairo.Operator.SOURCE)
+ cr:set_source_rgba(1,1,1,1)
+
+ shape(cr, geo.width, geo.height, ...)
+
+ cr:fill()
+
+ draw.shape_bounding = img._native
+end
+
+local function no_op() end
+
+local function run_in_hierarchy(self, cr, width, height)
+ local context = {dpi=96}
+ local h = hierarchy.new(context, self, width, height, no_op, no_op, {})
+ h:draw(context, cr)
+ return h
+end
+
+--- Create an SVG file with this widget content.
+-- This is dynamic, so the SVG will be updated along with the widget content.
+-- because of this, the painting may happen hover multiple event loop cycles.
+-- @tparam widget widget A widget
+-- @tparam string path The output file path
+-- @tparam number width The surface width
+-- @tparam number height The surface height
+-- @return The cairo surface
+-- @return The hierarchy
+function surface.widget_to_svg(widget, path, width, height)
+ local img = cairo.SvgSurface.create(path, width, height)
+ local cr = cairo.Context(img)
+
+ return img, run_in_hierarchy(widget, cr, width, height)
+end
+
+--- Create a cairo surface with this widget content.
+-- This is dynamic, so the SVG will be updated along with the widget content.
+-- because of this, the painting may happen hover multiple event loop cycles.
+-- @tparam widget widget A widget
+-- @tparam number width The surface width
+-- @tparam number height The surface height
+-- @param[opt=cairo.Format.ARGB32] format The surface format
+-- @return The cairo surface
+-- @return The hierarchy
+function surface.widget_to_surface(widget, width, height, format)
+ local img = cairo.ImageSurface(format or cairo.Format.ARGB32, width, height)
+ local cr = cairo.Context(img)
+
+ return img, run_in_hierarchy(widget, cr, width, height)
+end
+
+return setmetatable(surface, surface.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/gears/timer.lua b/lib/gears/timer.lua
new file mode 100644
index 0000000..110c39a
--- /dev/null
+++ b/lib/gears/timer.lua
@@ -0,0 +1,187 @@
+---------------------------------------------------------------------------
+--- Timer objects and functions.
+--
+-- @author Uli Schlachter
+-- @copyright 2014 Uli Schlachter
+-- @classmod gears.timer
+---------------------------------------------------------------------------
+
+local capi = { awesome = awesome }
+local ipairs = ipairs
+local pairs = pairs
+local setmetatable = setmetatable
+local table = table
+local tonumber = tonumber
+local traceback = debug.traceback
+local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
+local glib = require("lgi").GLib
+local object = require("gears.object")
+local protected_call = require("gears.protected_call")
+
+--- Timer objects. This type of object is useful when triggering events repeatedly.
+-- The timer will emit the "timeout" signal every N seconds, N being the timeout
+-- value. Note that a started timer will not be garbage collected. Call `:stop`
+-- to enable garbage collection.
+-- @tfield number timeout Interval in seconds to emit the timeout signal.
+-- Can be any value, including floating point ones (e.g. 1.5 seconds).
+-- @tfield boolean started Read-only boolean field indicating if the timer has been
+-- started.
+-- @table timer
+
+--- When the timer is started.
+-- @signal .start
+
+--- When the timer is stopped.
+-- @signal .stop
+
+--- When the timer had a timeout event.
+-- @signal .timeout
+
+local timer = { mt = {} }
+
+--- Start the timer.
+function timer:start()
+ if self.data.source_id ~= nil then
+ print(traceback("timer already started"))
+ return
+ end
+ self.data.source_id = glib.timeout_add(glib.PRIORITY_DEFAULT, self.data.timeout * 1000, function()
+ protected_call(self.emit_signal, self, "timeout")
+ return true
+ end)
+ self:emit_signal("start")
+end
+
+--- Stop the timer.
+function timer:stop()
+ if self.data.source_id == nil then
+ print(traceback("timer not started"))
+ return
+ end
+ glib.source_remove(self.data.source_id)
+ self.data.source_id = nil
+ self:emit_signal("stop")
+end
+
+--- Restart the timer.
+-- This is equivalent to stopping the timer if it is running and then starting
+-- it.
+function timer:again()
+ if self.data.source_id ~= nil then
+ self:stop()
+ end
+ self:start()
+end
+
+--- The timer is started.
+-- @property started
+-- @param boolean
+
+--- The timer timeout value.
+-- **Signal:** property::timeout
+-- @property timeout
+-- @param number
+
+local timer_instance_mt = {
+ __index = function(self, property)
+ if property == "timeout" then
+ return self.data.timeout
+ elseif property == "started" then
+ return self.data.source_id ~= nil
+ end
+
+ return timer[property]
+ end,
+
+ __newindex = function(self, property, value)
+ if property == "timeout" then
+ self.data.timeout = tonumber(value)
+ self:emit_signal("property::timeout")
+ end
+ end
+}
+
+--- Create a new timer object.
+-- @tparam table args Arguments.
+-- @tparam number args.timeout Timeout in seconds (e.g. 1.5).
+-- @treturn timer
+-- @function gears.timer
+timer.new = function(args)
+ local ret = object()
+
+ ret.data = { timeout = 0 }
+ setmetatable(ret, timer_instance_mt)
+
+ for k, v in pairs(args) do
+ ret[k] = v
+ end
+
+ return ret
+end
+
+--- Create a timeout for calling some callback function.
+-- When the callback function returns true, it will be called again after the
+-- same timeout. If false is returned, no more calls will be done. If the
+-- callback function causes an error, no more calls are done.
+-- @tparam number timeout Timeout in seconds (e.g. 1.5).
+-- @tparam function callback Function to run.
+-- @treturn timer The timer object that was set up.
+-- @see timer.weak_start_new
+-- @function gears.timer.start_new
+function timer.start_new(timeout, callback)
+ local t = timer.new({ timeout = timeout })
+ t:connect_signal("timeout", function()
+ local cont = protected_call(callback)
+ if not cont then
+ t:stop()
+ end
+ end)
+ t:start()
+ return t
+end
+
+--- Create a timeout for calling some callback function.
+-- This function is almost identical to `timer.start_new`. The only difference
+-- is that this does not prevent the callback function from being garbage
+-- collected. After the callback function was collected, the timer returned
+-- will automatically be stopped.
+-- @tparam number timeout Timeout in seconds (e.g. 1.5).
+-- @tparam function callback Function to start.
+-- @treturn timer The timer object that was set up.
+-- @see timer.start_new
+-- @function gears.timer.weak_start_new
+function timer.weak_start_new(timeout, callback)
+ local indirection = setmetatable({}, { __mode = "v" })
+ indirection.callback = callback
+ return timer.start_new(timeout, function()
+ local cb = indirection.callback
+ if cb then
+ return cb()
+ end
+ end)
+end
+
+local delayed_calls = {}
+capi.awesome.connect_signal("refresh", function()
+ for _, callback in ipairs(delayed_calls) do
+ protected_call(unpack(callback))
+ end
+ delayed_calls = {}
+end)
+
+--- Call the given function at the end of the current main loop iteration
+-- @tparam function callback The function that should be called
+-- @param ... Arguments to the callback function
+-- @function gears.timer.delayed_call
+function timer.delayed_call(callback, ...)
+ assert(type(callback) == "function", "callback must be a function, got: " .. type(callback))
+ table.insert(delayed_calls, { callback, ... })
+end
+
+function timer.mt.__call(_, ...)
+ return timer.new(...)
+end
+
+return setmetatable(timer, timer.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/gears/wallpaper.lua b/lib/gears/wallpaper.lua
new file mode 100644
index 0000000..70ecf48
--- /dev/null
+++ b/lib/gears/wallpaper.lua
@@ -0,0 +1,221 @@
+---------------------------------------------------------------------------
+-- @author Uli Schlachter
+-- @copyright 2012 Uli Schlachter
+-- @module gears.wallpaper
+---------------------------------------------------------------------------
+
+local cairo = require("lgi").cairo
+local color = require("gears.color")
+local surface = require("gears.surface")
+local timer = require("gears.timer")
+local root = root
+
+local wallpaper = { mt = {} }
+
+local function root_geometry()
+ local width, height = root.size()
+ return { x = 0, y = 0, width = width, height = height }
+end
+
+-- Information about a pending wallpaper change, see prepare_context()
+local pending_wallpaper = nil
+
+local function get_screen(s)
+ return s and screen[s]
+end
+
+--- Prepare the needed state for setting a wallpaper.
+-- This function returns a cairo context through which a wallpaper can be drawn.
+-- The context is only valid for a short time and should not be saved in a
+-- global variable.
+-- @param s The screen to set the wallpaper on or nil for all screens
+-- @return[1] The available geometry (table with entries width and height)
+-- @return[1] A cairo context that the wallpaper should be drawn to
+function wallpaper.prepare_context(s)
+ s = get_screen(s)
+
+ local root_width, root_height = root.size()
+ local geom = s and s.geometry or root_geometry()
+ local source, target, cr
+
+ if not pending_wallpaper then
+ -- Prepare a pending wallpaper
+ source = surface(root.wallpaper())
+ target = source:create_similar(cairo.Content.COLOR, root_width, root_height)
+
+ -- Set the wallpaper (delayed)
+ timer.delayed_call(function()
+ local paper = pending_wallpaper
+ pending_wallpaper = nil
+ wallpaper.set(paper.surface)
+ paper.surface:finish()
+ end)
+ elseif root_width > pending_wallpaper.width or root_height > pending_wallpaper.height then
+ -- The root window was resized while a wallpaper is pending
+ source = pending_wallpaper.surface
+ target = source:create_similar(cairo.Content.COLOR, root_width, root_height)
+ else
+ -- Draw to the already-pending wallpaper
+ source = nil
+ target = pending_wallpaper.surface
+ end
+
+ cr = cairo.Context(target)
+
+ if source then
+ -- Copy the old wallpaper to the new one
+ cr:save()
+ cr.operator = cairo.Operator.SOURCE
+ cr:set_source_surface(source, 0, 0)
+ cr:paint()
+ cr:restore()
+ end
+
+ pending_wallpaper = {
+ surface = target,
+ width = root_width,
+ height = root_height
+ }
+
+ -- Only draw to the selected area
+ cr:translate(geom.x, geom.y)
+ cr:rectangle(0, 0, geom.width, geom.height)
+ cr:clip()
+
+ return geom, cr
+end
+
+--- Set the current wallpaper.
+-- @param pattern The wallpaper that should be set. This can be a cairo surface,
+-- a description for gears.color or a cairo pattern.
+-- @see gears.color
+function wallpaper.set(pattern)
+ if cairo.Surface:is_type_of(pattern) then
+ pattern = cairo.Pattern.create_for_surface(pattern)
+ end
+ if type(pattern) == "string" or type(pattern) == "table" then
+ pattern = color(pattern)
+ end
+ if not cairo.Pattern:is_type_of(pattern) then
+ error("wallpaper.set() called with an invalid argument")
+ end
+ root.wallpaper(pattern._native)
+end
+
+--- Set a centered wallpaper.
+-- @param surf The wallpaper to center. Either a cairo surface or a file name.
+-- @param s The screen whose wallpaper should be set. Can be nil, in which case
+-- all screens are set.
+-- @param background The background color that should be used. Gets handled via
+-- gears.color. The default is black.
+-- @see gears.color
+function wallpaper.centered(surf, s, background)
+ local geom, cr = wallpaper.prepare_context(s)
+ surf = surface.load_uncached(surf)
+ background = color(background)
+
+ -- Fill the area with the background
+ cr.operator = cairo.Operator.SOURCE
+ cr.source = background
+ cr:paint()
+
+ -- Now center the surface
+ local w, h = surface.get_size(surf)
+ cr:translate((geom.width - w) / 2, (geom.height - h) / 2)
+ cr:rectangle(0, 0, w, h)
+ cr:clip()
+ cr:set_source_surface(surf, 0, 0)
+ cr:paint()
+ surf:finish()
+end
+
+--- Set a tiled wallpaper.
+-- @param surf The wallpaper to tile. Either a cairo surface or a file name.
+-- @param s The screen whose wallpaper should be set. Can be nil, in which case
+-- all screens are set.
+-- @param offset This can be set to a table with entries x and y.
+function wallpaper.tiled(surf, s, offset)
+ local _, cr = wallpaper.prepare_context(s)
+
+ if offset then
+ cr:translate(offset.x, offset.y)
+ end
+
+ surf = surface.load_uncached(surf)
+ local pattern = cairo.Pattern.create_for_surface(surf)
+ pattern.extend = cairo.Extend.REPEAT
+ cr.source = pattern
+ cr.operator = cairo.Operator.SOURCE
+ cr:paint()
+ surf:finish()
+end
+
+--- Set a maximized wallpaper.
+-- @param surf The wallpaper to set. Either a cairo surface or a file name.
+-- @param s The screen whose wallpaper should be set. Can be nil, in which case
+-- all screens are set.
+-- @param ignore_aspect If this is true, the image's aspect ratio is ignored.
+-- The default is to honor the aspect ratio.
+-- @param offset This can be set to a table with entries x and y.
+function wallpaper.maximized(surf, s, ignore_aspect, offset)
+ local geom, cr = wallpaper.prepare_context(s)
+ surf = surface.load_uncached(surf)
+ local w, h = surface.get_size(surf)
+ local aspect_w = geom.width / w
+ local aspect_h = geom.height / h
+
+ if not ignore_aspect then
+ aspect_h = math.max(aspect_w, aspect_h)
+ aspect_w = math.max(aspect_w, aspect_h)
+ end
+ cr:scale(aspect_w, aspect_h)
+
+ if offset then
+ cr:translate(offset.x, offset.y)
+ elseif not ignore_aspect then
+ local scaled_width = geom.width / aspect_w
+ local scaled_height = geom.height / aspect_h
+ cr:translate((scaled_width - w) / 2, (scaled_height - h) / 2)
+ end
+
+ cr:set_source_surface(surf, 0, 0)
+ cr.operator = cairo.Operator.SOURCE
+ cr:paint()
+ surf:finish()
+end
+
+--- Set a fitting wallpaper.
+-- @param surf The wallpaper to set. Either a cairo surface or a file name.
+-- @param s The screen whose wallpaper should be set. Can be nil, in which case
+-- all screens are set.
+-- @param background The background color that should be used. Gets handled via
+-- gears.color. The default is black.
+-- @see gears.color
+function wallpaper.fit(surf, s, background)
+ local geom, cr = wallpaper.prepare_context(s)
+ surf = surface.load_uncached(surf)
+ background = color(background)
+
+ -- Fill the area with the background
+ cr.operator = cairo.Operator.SOURCE
+ cr.source = background
+ cr:paint()
+
+ -- Now fit the surface
+ local w, h = surface.get_size(surf)
+ local scale = geom.width / w
+ if h * scale > geom.height then
+ scale = geom.height / h
+ end
+ cr:translate((geom.width - (w * scale)) / 2, (geom.height - (h * scale)) / 2)
+ cr:rectangle(0, 0, w * scale, h * scale)
+ cr:clip()
+ cr:scale(scale, scale)
+ cr:set_source_surface(surf, 0, 0)
+ cr:paint()
+ surf:finish()
+end
+
+return wallpaper
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/menubar/icon_theme.lua b/lib/menubar/icon_theme.lua
new file mode 100644
index 0000000..f76252f
--- /dev/null
+++ b/lib/menubar/icon_theme.lua
@@ -0,0 +1,251 @@
+---------------------------------------------------------------------------
+--- Class module for icon lookup for menubar
+--
+-- @author Kazunobu Kuriyama
+-- @copyright 2015 Kazunobu Kuriyama
+-- @classmod menubar.icon_theme
+---------------------------------------------------------------------------
+
+-- This implementation is based on the specifications:
+-- Icon Theme Specification 0.12
+-- http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-0.12.html
+
+local beautiful = require("beautiful")
+local awful_util = require("awful.util")
+local GLib = require("lgi").GLib
+local index_theme = require("menubar.index_theme")
+
+local ipairs = ipairs
+local setmetatable = setmetatable
+local string = string
+local table = table
+local math = math
+
+local get_pragmatic_base_directories = function()
+ local dirs = {}
+
+ local dir = GLib.build_filenamev({GLib.get_home_dir(), ".icons"})
+ if awful_util.dir_readable(dir) then
+ table.insert(dirs, dir)
+ end
+
+ dir = GLib.build_filenamev({GLib.get_user_data_dir(), "icons"})
+ if awful_util.dir_readable(dir) then
+ table.insert(dirs, dir)
+ end
+
+ for _, v in ipairs(GLib.get_system_data_dirs()) do
+ dir = GLib.build_filenamev({v, "icons"})
+ if awful_util.dir_readable(dir) then
+ table.insert(dirs, dir)
+ end
+ end
+
+ local need_usr_share_pixmaps = true
+ for _, v in ipairs(GLib.get_system_data_dirs()) do
+ dir = GLib.build_filenamev({v, "pixmaps"})
+ if awful_util.dir_readable(dir) then
+ table.insert(dirs, dir)
+ end
+ if dir == "/usr/share/pixmaps" then
+ need_usr_share_pixmaps = false
+ end
+ end
+
+ dir = "/usr/share/pixmaps"
+ if need_usr_share_pixmaps and awful_util.dir_readable(dir) then
+ table.insert(dirs, dir)
+ end
+
+ return dirs
+end
+
+local get_default_icon_theme_name = function()
+ local icon_theme_names = { "Adwaita", "gnome", "hicolor" }
+ for _, dir in ipairs(get_pragmatic_base_directories()) do
+ for _, icon_theme_name in ipairs(icon_theme_names) do
+ local filename = string.format("%s/%s/index.theme", dir, icon_theme_name)
+ if awful_util.file_readable(filename) then
+ return icon_theme_name
+ end
+ end
+ end
+ return nil
+end
+
+local icon_theme = { mt = {} }
+
+local index_theme_cache = {}
+
+--- Class constructor of `icon_theme`
+-- @tparam string icon_theme_name Internal name of icon theme
+-- @tparam table base_directories Paths used for lookup
+-- @treturn table An instance of the class `icon_theme`
+icon_theme.new = function(icon_theme_name, base_directories)
+ icon_theme_name = icon_theme_name or beautiful.icon_theme or get_default_icon_theme_name()
+ base_directories = base_directories or get_pragmatic_base_directories()
+
+ local self = {}
+ self.icon_theme_name = icon_theme_name
+ self.base_directories = base_directories
+ self.extensions = { "png", "svg", "xpm" }
+
+ -- Instantiate index_theme (cached).
+ if not index_theme_cache[self.icon_theme_name] then
+ index_theme_cache[self.icon_theme_name] = {}
+ end
+ local cache_key = table.concat(self.base_directories, ':')
+ if not index_theme_cache[self.icon_theme_name][cache_key] then
+ index_theme_cache[self.icon_theme_name][cache_key] = index_theme(
+ self.icon_theme_name,
+ self.base_directories)
+ end
+ self.index_theme = index_theme_cache[self.icon_theme_name][cache_key]
+
+ return setmetatable(self, { __index = icon_theme })
+end
+
+local directory_matches_size = function(self, subdirectory, icon_size)
+ local kind, size, min_size, max_size, threshold = self.index_theme:get_per_directory_keys(subdirectory)
+
+ if kind == "Fixed" then
+ return icon_size == size
+ elseif kind == "Scalable" then
+ return icon_size >= min_size and icon_size <= max_size
+ elseif kind == "Threshold" then
+ return icon_size >= size - threshold and icon_size <= size + threshold
+ end
+
+ return false
+end
+
+local directory_size_distance = function(self, subdirectory, icon_size)
+ local kind, size, min_size, max_size, threshold = self.index_theme:get_per_directory_keys(subdirectory)
+
+ if kind == "Fixed" then
+ return math.abs(icon_size - size)
+ elseif kind == "Scalable" then
+ if icon_size < min_size then
+ return min_size - icon_size
+ elseif icon_size > max_size then
+ return icon_size - max_size
+ end
+ return 0
+ elseif kind == "Threshold" then
+ if icon_size < size - threshold then
+ return min_size - icon_size
+ elseif icon_size > size + threshold then
+ return icon_size - max_size
+ end
+ return 0
+ end
+
+ return 0xffffffff -- Any large number will do.
+end
+
+local lookup_icon = function(self, icon_name, icon_size)
+ local checked_already = {}
+ for _, subdir in ipairs(self.index_theme:get_subdirectories()) do
+ for _, basedir in ipairs(self.base_directories) do
+ for _, ext in ipairs(self.extensions) do
+ if directory_matches_size(self, subdir, icon_size) then
+ local filename = string.format("%s/%s/%s/%s.%s",
+ basedir, self.icon_theme_name, subdir,
+ icon_name, ext)
+ if awful_util.file_readable(filename) then
+ return filename
+ else
+ checked_already[filename] = true
+ end
+ end
+ end
+ end
+ end
+
+ local minimal_size = 0xffffffff -- Any large number will do.
+ local closest_filename = nil
+ for _, subdir in ipairs(self.index_theme:get_subdirectories()) do
+ local dist = directory_size_distance(self, subdir, icon_size)
+ if dist < minimal_size then
+ for _, basedir in ipairs(self.base_directories) do
+ for _, ext in ipairs(self.extensions) do
+ local filename = string.format("%s/%s/%s/%s.%s",
+ basedir, self.icon_theme_name, subdir,
+ icon_name, ext)
+ if not checked_already[filename] then
+ if awful_util.file_readable(filename) then
+ closest_filename = filename
+ minimal_size = dist
+ end
+ end
+ end
+ end
+ end
+ end
+ return closest_filename
+end
+
+local find_icon_path_helper -- Gets called recursively.
+find_icon_path_helper = function(self, icon_name, icon_size)
+ local filename = lookup_icon(self, icon_name, icon_size)
+ if filename then
+ return filename
+ end
+
+ for _, parent in ipairs(self.index_theme:get_inherits()) do
+ local parent_icon_theme = icon_theme(parent, self.base_directories)
+ filename = find_icon_path_helper(parent_icon_theme, icon_name, icon_size)
+ if filename then
+ return filename
+ end
+ end
+
+ return nil
+end
+
+local lookup_fallback_icon = function(self, icon_name)
+ for _, dir in ipairs(self.base_directories) do
+ for _, ext in ipairs(self.extensions) do
+ local filename = string.format("%s/%s.%s",
+ dir,
+ icon_name, ext)
+ if awful_util.file_readable(filename) then
+ return filename
+ end
+ end
+ end
+ return nil
+end
+
+--- Look up an image file based on a given icon name and/or a preferable size.
+-- @tparam string icon_name Icon name to be looked up
+-- @tparam number icon_size Prefereable icon size
+-- @treturn string Absolute path to the icon file, or nil if not found
+function icon_theme:find_icon_path(icon_name, icon_size)
+ icon_size = icon_size or 16
+ if not icon_name or icon_name == "" then
+ return nil
+ end
+
+ local filename = find_icon_path_helper(self, icon_name, icon_size)
+ if filename then
+ return filename
+ end
+
+ if self.icon_theme_name ~= "hicolor" then
+ filename = find_icon_path_helper(icon_theme("hicolor", self.base_directories), icon_name, icon_size)
+ if filename then
+ return filename
+ end
+ end
+
+ return lookup_fallback_icon(self, icon_name)
+end
+
+icon_theme.mt.__call = function(_, ...)
+ return icon_theme.new(...)
+end
+
+return setmetatable(icon_theme, icon_theme.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/menubar/index_theme.lua b/lib/menubar/index_theme.lua
new file mode 100644
index 0000000..633964a
--- /dev/null
+++ b/lib/menubar/index_theme.lua
@@ -0,0 +1,164 @@
+---------------------------------------------------------------------------
+--- Class module for parsing an index.theme file
+--
+-- @author Kazunobu Kuriyama
+-- @copyright 2015 Kazunobu Kuriyama
+-- @classmod menubar.index_theme
+---------------------------------------------------------------------------
+
+-- This implementation is based on the specifications:
+-- Icon Theme Specification 0.12
+-- http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-0.12.html
+
+local ipairs = ipairs
+local setmetatable = setmetatable
+local string = string
+local table = table
+local io = io
+
+-- index.theme groups
+local ICON_THEME = "Icon Theme"
+-- index.theme keys
+local DIRECTORIES = "Directories"
+local INHERITS = "Inherits"
+-- per-directory subkeys
+local TYPE = "Type"
+local SIZE = "Size"
+local MINSIZE = "MinSize"
+local MAXSIZE = "MaxSize"
+local THRESHOLD = "Threshold"
+
+local index_theme = { mt = {} }
+
+--- Class constructor of `index_theme`
+-- @tparam table cls Metatable that will be used. Should always be `index_theme.mt`.
+-- @tparam string icon_theme_name Internal name of icon theme
+-- @tparam table base_directories Paths used for lookup
+-- @treturn table An instance of the class `index_theme`
+index_theme.new = function(cls, icon_theme_name, base_directories)
+ local self = {}
+ setmetatable(self, { __index = cls })
+
+ -- Initialize the fields
+ self.icon_theme_name = icon_theme_name
+ self.base_directory = nil
+ self[DIRECTORIES] = {}
+ self[INHERITS] = {}
+ self.per_directory_keys = {}
+
+ -- base_directory
+ local basedir = nil
+ local handler = nil
+ for _, dir in ipairs(base_directories) do
+ basedir = dir .. "/" .. self.icon_theme_name
+ handler = io.open(basedir .. "/index.theme", "r")
+ if handler then
+ -- Use the index.theme which is found first.
+ break
+ end
+ end
+ if not handler then
+ return self
+ end
+ self.base_directory = basedir
+
+ -- Parse index.theme.
+ while true do
+ local line = handler:read()
+ if not line then
+ break
+ end
+
+ local group_header = "^%[(.+)%]$"
+ local group = line:match(group_header)
+ if group then
+ if group == ICON_THEME then
+ while true do
+ local item = handler:read()
+ if not item then
+ break
+ end
+ if item:match(group_header) then
+ handler:seek("cur", -string.len(item) - 1)
+ break
+ end
+
+ local k, v = item:match("^(%w+)=(.*)$")
+ if k == DIRECTORIES or k == INHERITS then
+ string.gsub(v, "([^,]+),?", function(match)
+ table.insert(self[k], match)
+ end)
+ end
+ end
+ else
+ -- This must be a 'per-directory keys' group
+ local keys = {}
+
+ while true do
+ local item = handler:read()
+ if not item then
+ break
+ end
+ if item:match(group_header) then
+ handler:seek("cur", -string.len(item) - 1)
+ break
+ end
+
+ local k, v = item:match("^(%w+)=(%w+)$")
+ if k == SIZE or k == MINSIZE or k == MAXSIZE or k == THRESHOLD then
+ keys[k] = tonumber(v)
+ elseif k == TYPE then
+ keys[k] = v
+ end
+ end
+
+ -- Size is a must. Other keys are optional.
+ if keys[SIZE] then
+ -- Set unset keys to the default values.
+ if not keys[TYPE] then keys[TYPE] = THRESHOLD end
+ if not keys[MINSIZE] then keys[MINSIZE] = keys[SIZE] end
+ if not keys[MAXSIZE] then keys[MAXSIZE] = keys[SIZE] end
+ if not keys[THRESHOLD] then keys[THRESHOLD] = 2 end
+
+ self.per_directory_keys[group] = keys
+ end
+ end
+ end
+ end
+
+ handler:close()
+
+ return self
+end
+
+--- Table of the values of the `Directories` key
+-- @treturn table Values of the `Directories` key
+index_theme.get_subdirectories = function(self)
+ return self[DIRECTORIES]
+end
+
+--- Table of the values of the `Inherits` key
+-- @treturn table Values of the `Inherits` key
+index_theme.get_inherits = function(self)
+ return self[INHERITS]
+end
+
+--- Query (part of) per-directory keys of a given subdirectory name.
+-- @tparam table subdirectory Icon theme's subdirectory
+-- @treturn[1] string Value of the `Type` key
+-- @treturn[2] number Value of the `Size` key
+-- @treturn[3] number VAlue of the `MinSize` key
+-- @treturn[4] number Value of the `MaxSize` key
+-- @treturn[5] number Value of the `Threshold` key
+function index_theme:get_per_directory_keys(subdirectory)
+ local keys = self.per_directory_keys[subdirectory]
+ return keys[TYPE], keys[SIZE], keys[MINSIZE], keys[MAXSIZE], keys[THRESHOLD]
+end
+
+index_theme.mt.__call = function(cls, icon_theme_name, base_directories)
+ return index_theme.new(cls, icon_theme_name, base_directories)
+end
+
+return setmetatable(index_theme, index_theme.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/menubar/init.lua b/lib/menubar/init.lua
new file mode 100644
index 0000000..10ad65c
--- /dev/null
+++ b/lib/menubar/init.lua
@@ -0,0 +1,480 @@
+---------------------------------------------------------------------------
+--- Menubar module, which aims to provide a freedesktop menu alternative
+--
+-- List of menubar keybindings:
+-- ---
+--
+-- * "Left" | "C-j" select an item on the left
+-- * "Right" | "C-k" select an item on the right
+-- * "Backspace" exit the current category if we are in any
+-- * "Escape" exit the current directory or exit menubar
+-- * "Home" select the first item
+-- * "End" select the last
+-- * "Return" execute the entry
+-- * "C-Return" execute the command with awful.spawn
+-- * "C-M-Return" execute the command in a terminal
+--
+-- @author Alexander Yakushev &lt;yakushev.alex@gmail.com&gt;
+-- @copyright 2011-2012 Alexander Yakushev
+-- @module menubar
+---------------------------------------------------------------------------
+
+-- Grab environment we need
+local capi = {
+ client = client,
+ mouse = mouse,
+ screen = screen
+}
+local awful = require("awful")
+local common = require("awful.widget.common")
+local theme = require("beautiful")
+local wibox = require("wibox")
+
+local function get_screen(s)
+ return s and capi.screen[s]
+end
+
+-- menubar
+local menubar = { mt = {}, menu_entries = {} }
+menubar.menu_gen = require("menubar.menu_gen")
+menubar.utils = require("menubar.utils")
+local compute_text_width = menubar.utils.compute_text_width
+
+-- Options section
+
+--- When true the .desktop files will be reparsed only when the
+-- extension is initialized. Use this if menubar takes much time to
+-- open.
+-- @tfield[opt=true] boolean cache_entries
+menubar.cache_entries = true
+
+--- When true the categories will be shown alongside application
+-- entries.
+-- @tfield[opt=true] boolean show_categories
+menubar.show_categories = true
+
+--- Specifies the geometry of the menubar. This is a table with the keys
+-- x, y, width and height. Missing values are replaced via the screen's
+-- geometry. However, missing height is replaced by the font size.
+-- @table geometry
+-- @tfield number geometry.x A forced horizontal position
+-- @tfield number geometry.y A forced vertical position
+-- @tfield number geometry.width A forced width
+-- @tfield number geometry.height A forced height
+menubar.geometry = { width = nil,
+ height = nil,
+ x = nil,
+ y = nil }
+
+--- Width of blank space left in the right side.
+-- @tfield number right_margin
+menubar.right_margin = theme.xresources.apply_dpi(8)
+
+--- Label used for "Next page", default "▶▶".
+-- @tfield[opt="▶▶"] string right_label
+menubar.right_label = "▶▶"
+
+--- Label used for "Previous page", default "◀◀".
+-- @tfield[opt="◀◀"] string left_label
+menubar.left_label = "◀◀"
+
+-- awful.widget.common.list_update adds three times a margin of dpi(4)
+-- for each item:
+-- @tfield number list_interspace
+local list_interspace = theme.xresources.apply_dpi(4) * 3
+
+--- Allows user to specify custom parameters for prompt.run function
+-- (like colors).
+-- @see awful.prompt
+menubar.prompt_args = {}
+
+-- Private section
+local current_item = 1
+local previous_item = nil
+local current_category = nil
+local shownitems = nil
+local instance = { prompt = nil,
+ widget = nil,
+ wibox = nil }
+
+local common_args = { w = wibox.layout.fixed.horizontal(),
+ data = setmetatable({}, { __mode = 'kv' }) }
+
+--- Wrap the text with the color span tag.
+-- @param s The text.
+-- @param c The desired text color.
+-- @return the text wrapped in a span tag.
+local function colortext(s, c)
+ return "<span color='" .. awful.util.ensure_pango_color(c) .. "'>" .. s .. "</span>"
+end
+
+--- Get how the menu item should be displayed.
+-- @param o The menu item.
+-- @return item name, item background color, background image, item icon.
+local function label(o)
+ if o.focused then
+ return colortext(o.name, (theme.menu_fg_focus or theme.fg_focus)), (theme.menu_bg_focus or theme.bg_focus), nil, o.icon
+ else
+ return o.name, (theme.menu_bg_normal or theme.bg_normal), nil, o.icon
+ end
+end
+
+local function load_count_table()
+ local count_file_name = awful.util.getdir("cache") .. "/menu_count_file"
+
+ local count_file = io.open (count_file_name, "r")
+ local count_table = {}
+
+ -- read weight file
+ if count_file then
+ io.input (count_file)
+ for line in io.lines() do
+ local name, count = string.match(line, "([^;]+);([^;]+)")
+ if name ~= nil and count ~= nil then
+ count_table[name] = count
+ end
+ end
+ end
+
+ return count_table
+end
+
+local function write_count_table(count_table)
+ local count_file_name = awful.util.getdir("cache") .. "/menu_count_file"
+
+ local count_file = io.open (count_file_name, "w")
+
+ if count_file then
+ io.output (count_file)
+
+ for name, count in pairs(count_table) do
+ local str = string.format("%s;%d\n", name, count)
+ io.write(str)
+ end
+ io.flush()
+ end
+end
+
+--- Perform an action for the given menu item.
+-- @param o The menu item.
+-- @return if the function processed the callback, new awful.prompt command, new awful.prompt prompt text.
+local function perform_action(o)
+ if not o then return end
+ if o.key then
+ current_category = o.key
+ local new_prompt = shownitems[current_item].name .. ": "
+ previous_item = current_item
+ current_item = 1
+ return true, "", new_prompt
+ elseif shownitems[current_item].cmdline then
+ awful.spawn(shownitems[current_item].cmdline)
+
+ -- load count_table from cache file
+ local count_table = load_count_table()
+
+ -- increase count
+ local curname = shownitems[current_item].name
+ if count_table[curname] ~= nil then
+ count_table[curname] = count_table[curname] + 1
+ else
+ count_table[curname] = 1
+ end
+
+ -- write updated count table to cache file
+ write_count_table(count_table)
+
+ -- Let awful.prompt execute dummy exec_callback and
+ -- done_callback to stop the keygrabber properly.
+ return false
+ end
+end
+
+-- Cut item list to return only current page.
+-- @tparam table all_items All items list.
+-- @tparam str query Search query.
+-- @tparam number|screen scr Screen
+-- @return table List of items for current page.
+local function get_current_page(all_items, query, scr)
+ scr = get_screen(scr)
+ if not instance.prompt.width then
+ instance.prompt.width = compute_text_width(instance.prompt.prompt, scr)
+ end
+ if not menubar.left_label_width then
+ menubar.left_label_width = compute_text_width(menubar.left_label, scr)
+ end
+ if not menubar.right_label_width then
+ menubar.right_label_width = compute_text_width(menubar.right_label, scr)
+ end
+ local available_space = instance.geometry.width - menubar.right_margin -
+ menubar.right_label_width - menubar.left_label_width -
+ compute_text_width(query, scr) - instance.prompt.width
+
+ local width_sum = 0
+ local current_page = {}
+ for i, item in ipairs(all_items) do
+ item.width = item.width or
+ compute_text_width(item.name, scr) +
+ (item.icon and instance.geometry.height or 0) + list_interspace
+ if width_sum + item.width > available_space then
+ if current_item < i then
+ table.insert(current_page, { name = menubar.right_label, icon = nil })
+ break
+ end
+ current_page = { { name = menubar.left_label, icon = nil }, item, }
+ width_sum = item.width
+ else
+ table.insert(current_page, item)
+ width_sum = width_sum + item.width
+ end
+ end
+ return current_page
+end
+
+--- Update the menubar according to the command entered by user.
+-- @tparam str query Search query.
+-- @tparam number|screen scr Screen
+local function menulist_update(query, scr)
+ query = query or ""
+ shownitems = {}
+ local pattern = awful.util.query_to_pattern(query)
+
+ -- All entries are added to a list that will be sorted
+ -- according to the priority (first) and weight (second) of its
+ -- entries.
+ -- If categories are used in the menu, we add the entries matching
+ -- the current query with high priority as to ensure they are
+ -- displayed first. Afterwards the non-category entries are added.
+ -- All entries are weighted according to the number of times they
+ -- have been executed previously (stored in count_table).
+
+ local count_table = load_count_table()
+ local command_list = {}
+
+ local PRIO_NONE = 0
+ local PRIO_CATEGORY_MATCH = 2
+
+ -- Add the categories
+ if menubar.show_categories then
+ for _, v in pairs(menubar.menu_gen.all_categories) do
+ v.focused = false
+ if not current_category and v.use then
+
+ -- check if current query matches a category
+ if string.match(v.name, pattern) then
+
+ v.weight = 0
+ v.prio = PRIO_CATEGORY_MATCH
+
+ -- get use count from count_table if present
+ -- and use it as weight
+ if string.len(pattern) > 0 and count_table[v.name] ~= nil then
+ v.weight = tonumber(count_table[v.name])
+ end
+
+ -- check for prefix match
+ if string.match(v.name, "^" .. pattern) then
+ -- increase default priority
+ v.prio = PRIO_CATEGORY_MATCH + 1
+ else
+ v.prio = PRIO_CATEGORY_MATCH
+ end
+
+ table.insert (command_list, v)
+ end
+ end
+ end
+ end
+
+ -- Add the applications according to their name and cmdline
+ for _, v in ipairs(menubar.menu_entries) do
+ v.focused = false
+ if not current_category or v.category == current_category then
+
+ -- check if the query matches either the name or the commandline
+ -- of some entry
+ if string.match(v.name, pattern)
+ or string.match(v.cmdline, pattern) then
+
+ v.weight = 0
+ v.prio = PRIO_NONE
+
+ -- get use count from count_table if present
+ -- and use it as weight
+ if string.len(pattern) > 0 and count_table[v.name] ~= nil then
+ v.weight = tonumber(count_table[v.name])
+ end
+
+ -- check for prefix match
+ if string.match(v.name, "^" .. pattern)
+ or string.match(v.cmdline, "^" .. pattern) then
+ -- increase default priority
+ v.prio = PRIO_NONE + 1
+ else
+ v.prio = PRIO_NONE
+ end
+
+ table.insert (command_list, v)
+ end
+ end
+ end
+
+ local function compare_counts(a, b)
+ if a.prio == b.prio then
+ return a.weight > b.weight
+ end
+ return a.prio > b.prio
+ end
+
+ -- sort command_list by weight (highest first)
+ table.sort(command_list, compare_counts)
+ -- copy into showitems
+ shownitems = command_list
+
+ if #shownitems > 0 then
+ -- Insert a run item value as the last choice
+ table.insert(shownitems, { name = "Exec: " .. query, cmdline = query, icon = nil })
+
+ if current_item > #shownitems then
+ current_item = #shownitems
+ end
+ shownitems[current_item].focused = true
+ else
+ table.insert(shownitems, { name = "", cmdline = query, icon = nil })
+ end
+
+ common.list_update(common_args.w, nil, label,
+ common_args.data,
+ get_current_page(shownitems, query, scr))
+end
+
+--- Create the menubar wibox and widgets.
+-- @tparam[opt] screen scr Screen.
+local function initialize(scr)
+ instance.wibox = wibox({})
+ instance.widget = menubar.get(scr)
+ instance.wibox.ontop = true
+ instance.prompt = awful.widget.prompt()
+ local layout = wibox.layout.fixed.horizontal()
+ layout:add(instance.prompt)
+ layout:add(instance.widget)
+ instance.wibox:set_widget(layout)
+end
+
+--- Refresh menubar's cache by reloading .desktop files.
+-- @tparam[opt] screen scr Screen.
+function menubar.refresh(scr)
+ menubar.menu_gen.generate(function(entries)
+ menubar.menu_entries = entries
+ menulist_update(nil, scr)
+ end)
+end
+
+--- Awful.prompt keypressed callback to be used when the user presses a key.
+-- @param mod Table of key combination modifiers (Control, Shift).
+-- @param key The key that was pressed.
+-- @param comm The current command in the prompt.
+-- @return if the function processed the callback, new awful.prompt command, new awful.prompt prompt text.
+local function prompt_keypressed_callback(mod, key, comm)
+ if key == "Left" or (mod.Control and key == "j") then
+ current_item = math.max(current_item - 1, 1)
+ return true
+ elseif key == "Right" or (mod.Control and key == "k") then
+ current_item = current_item + 1
+ return true
+ elseif key == "BackSpace" then
+ if comm == "" and current_category then
+ current_category = nil
+ current_item = previous_item
+ return true, nil, "Run: "
+ end
+ elseif key == "Escape" then
+ if current_category then
+ current_category = nil
+ current_item = previous_item
+ return true, nil, "Run: "
+ end
+ elseif key == "Home" then
+ current_item = 1
+ return true
+ elseif key == "End" then
+ current_item = #shownitems
+ return true
+ elseif key == "Return" or key == "KP_Enter" then
+ if mod.Control then
+ current_item = #shownitems
+ if mod.Mod1 then
+ -- add a terminal to the cmdline
+ shownitems[current_item].cmdline = menubar.utils.terminal
+ .. " -e " .. shownitems[current_item].cmdline
+ end
+ end
+ return perform_action(shownitems[current_item])
+ end
+ return false
+end
+
+--- Show the menubar on the given screen.
+-- @param scr Screen.
+function menubar.show(scr)
+ if not instance.wibox then
+ initialize(scr)
+ elseif instance.wibox.visible then -- Menu already shown, exit
+ return
+ elseif not menubar.cache_entries then
+ menubar.refresh(scr)
+ end
+
+ -- Set position and size
+ scr = scr or awful.screen.focused() or 1
+ scr = get_screen(scr)
+ local scrgeom = scr.workarea
+ local geometry = menubar.geometry
+ instance.geometry = {x = geometry.x or scrgeom.x,
+ y = geometry.y or scrgeom.y,
+ height = geometry.height or awful.util.round(theme.get_font_height() * 1.5),
+ width = geometry.width or scrgeom.width}
+ instance.wibox:geometry(instance.geometry)
+
+ current_item = 1
+ current_category = nil
+ menulist_update(nil, scr)
+
+ local prompt_args = menubar.prompt_args or {}
+
+ awful.prompt.run(setmetatable({
+ prompt = "Run: ",
+ textbox = instance.prompt.widget,
+ completion_callback = awful.completion.shell,
+ history_path = awful.util.get_cache_dir() .. "/history_menu",
+ done_callback = menubar.hide,
+ changed_callback = function(query) menulist_update(query, scr) end,
+ keypressed_callback = prompt_keypressed_callback
+ }, {__index=prompt_args}))
+
+ instance.wibox.visible = true
+end
+
+--- Hide the menubar.
+function menubar.hide()
+ instance.wibox.visible = false
+end
+
+--- Get a menubar wibox.
+-- @tparam[opt] screen scr Screen.
+-- @return menubar wibox.
+function menubar.get(scr)
+ menubar.refresh(scr)
+ -- Add to each category the name of its key in all_categories
+ for k, v in pairs(menubar.menu_gen.all_categories) do
+ v.key = k
+ end
+ return common_args.w
+end
+
+function menubar.mt.__call(_, ...)
+ return menubar.get(...)
+end
+
+return setmetatable(menubar, menubar.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/menubar/menu_gen.lua b/lib/menubar/menu_gen.lua
new file mode 100644
index 0000000..ed2aa14
--- /dev/null
+++ b/lib/menubar/menu_gen.lua
@@ -0,0 +1,141 @@
+---------------------------------------------------------------------------
+--- Menu generation module for menubar
+--
+-- @author Antonio Terceiro
+-- @copyright 2009, 2011-2012 Antonio Terceiro, Alexander Yakushev
+-- @module menubar.menu_gen
+---------------------------------------------------------------------------
+
+-- Grab environment
+local utils = require("menubar.utils")
+local icon_theme = require("menubar.icon_theme")
+local pairs = pairs
+local ipairs = ipairs
+local string = string
+local table = table
+
+local menu_gen = {}
+
+-- Options section
+
+local data_dir = os.getenv("XDG_DATA_HOME")
+if not data_dir then
+ data_dir = os.getenv("HOME") .. '/.local/share/'
+end
+
+--- Specifies all directories where menubar should look for .desktop
+-- files. The search is recursive.
+menu_gen.all_menu_dirs = { data_dir .. 'applications/', '/usr/share/applications/', '/usr/local/share/applications/' }
+
+--- Specify the mapping of .desktop Categories section to the
+-- categories in the menubar. If "use" flag is set to false then any of
+-- the applications that fall only to this category will not be shown.
+menu_gen.all_categories = {
+ multimedia = { app_type = "AudioVideo", name = "Multimedia",
+ icon_name = "applications-multimedia", use = true },
+ development = { app_type = "Development", name = "Development",
+ icon_name = "applications-development", use = true },
+ education = { app_type = "Education", name = "Education",
+ icon_name = "applications-science", use = true },
+ games = { app_type = "Game", name = "Games",
+ icon_name = "applications-games", use = true },
+ graphics = { app_type = "Graphics", name = "Graphics",
+ icon_name = "applications-graphics", use = true },
+ office = { app_type = "Office", name = "Office",
+ icon_name = "applications-office", use = true },
+ internet = { app_type = "Network", name = "Internet",
+ icon_name = "applications-internet", use = true },
+ settings = { app_type = "Settings", name = "Settings",
+ icon_name = "applications-utilities", use = true },
+ tools = { app_type = "System", name = "System Tools",
+ icon_name = "applications-system", use = true },
+ utility = { app_type = "Utility", name = "Accessories",
+ icon_name = "applications-accessories", use = true }
+}
+
+--- Find icons for category entries.
+function menu_gen.lookup_category_icons()
+ for _, v in pairs(menu_gen.all_categories) do
+ v.icon = icon_theme():find_icon_path(v.icon_name)
+ end
+end
+
+--- Get category key name and whether it is used by its app_type.
+-- @param app_type Application category as written in .desktop file.
+-- @return category key name in all_categories, whether the category is used
+local function get_category_name_and_usage_by_type(app_type)
+ for k, v in pairs(menu_gen.all_categories) do
+ if app_type == v.app_type then
+ return k, v.use
+ end
+ end
+end
+
+--- Remove CR\LF newline from the end of the string.
+-- @param s string to trim
+local function trim(s)
+ if not s then return end
+ if string.byte(s, #s) == 13 then
+ return string.sub(s, 1, #s - 1)
+ end
+ return s
+end
+
+--- Generate an array of all visible menu entries.
+-- @tparam function callback Will be fired when all menu entries were parsed
+-- with the resulting list of menu entries as argument.
+-- @tparam table callback.entries All menu entries.
+function menu_gen.generate(callback)
+ -- Update icons for category entries
+ menu_gen.lookup_category_icons()
+
+ local result = {}
+ local unique_entries = {}
+ local dirs_parsed = 0
+
+ for _, dir in ipairs(menu_gen.all_menu_dirs) do
+ utils.parse_dir(dir, function(entries)
+ entries = entries or {}
+ for _, entry in ipairs(entries) do
+ -- Check whether to include program in the menu
+ if entry.show and entry.Name and entry.cmdline then
+ local unique_key = entry.Name .. '\0' .. entry.cmdline
+ if not unique_entries[unique_key] then
+ local target_category = nil
+ -- Check if the program falls into at least one of the
+ -- usable categories. Set target_category to be the id
+ -- of the first category it finds.
+ if entry.categories then
+ for _, category in pairs(entry.categories) do
+ local cat_key, cat_use =
+ get_category_name_and_usage_by_type(category)
+ if cat_key and cat_use then
+ target_category = cat_key
+ break
+ end
+ end
+ end
+ if target_category then
+ local name = trim(entry.Name) or ""
+ local cmdline = trim(entry.cmdline) or ""
+ local icon = entry.icon_path or nil
+ table.insert(result, { name = name,
+ cmdline = cmdline,
+ icon = icon,
+ category = target_category })
+ unique_entries[unique_key] = true
+ end
+ end
+ end
+ end
+ dirs_parsed = dirs_parsed + 1
+ if dirs_parsed == #menu_gen.all_menu_dirs then
+ callback(result)
+ end
+ end)
+ end
+end
+
+return menu_gen
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/menubar/utils.lua b/lib/menubar/utils.lua
new file mode 100644
index 0000000..6f80e86
--- /dev/null
+++ b/lib/menubar/utils.lua
@@ -0,0 +1,316 @@
+---------------------------------------------------------------------------
+--- Utility module for menubar
+--
+-- @author Antonio Terceiro
+-- @copyright 2009, 2011-2012 Antonio Terceiro, Alexander Yakushev
+-- @module menubar.utils
+---------------------------------------------------------------------------
+
+-- Grab environment
+local io = io
+local table = table
+local ipairs = ipairs
+local string = string
+local screen = screen
+local awful_util = require("awful.util")
+local theme = require("beautiful")
+local lgi = require("lgi")
+local gio = lgi.Gio
+local glib = lgi.GLib
+local wibox = require("wibox")
+local debug = require("gears.debug")
+local protected_call = require("gears.protected_call")
+
+local utils = {}
+
+-- NOTE: This icons/desktop files module was written according to the
+-- following freedesktop.org specifications:
+-- Icons: http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-0.11.html
+-- Desktop files: http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.0.html
+
+-- Options section
+
+--- Terminal which applications that need terminal would open in.
+utils.terminal = 'xterm'
+
+--- The default icon for applications that don't provide any icon in
+-- their .desktop files.
+local default_icon = nil
+
+--- Name of the WM for the OnlyShownIn entry in the .desktop file.
+utils.wm_name = "awesome"
+
+-- Private section
+
+local all_icon_sizes = {
+ '128x128' ,
+ '96x96',
+ '72x72',
+ '64x64',
+ '48x48',
+ '36x36',
+ '32x32',
+ '24x24',
+ '22x22',
+ '16x16'
+}
+
+--- List of supported icon formats.
+local icon_formats = { "png", "xpm", "svg" }
+
+--- Check whether the icon format is supported.
+-- @param icon_file Filename of the icon.
+-- @return true if format is supported, false otherwise.
+local function is_format_supported(icon_file)
+ for _, f in ipairs(icon_formats) do
+ if icon_file:match('%.' .. f) then
+ return true
+ end
+ end
+ return false
+end
+
+local icon_lookup_path = nil
+--- Get a list of icon lookup paths.
+-- @treturn table A list of directories, without trailing slash.
+local function get_icon_lookup_path()
+ if not icon_lookup_path then
+ local add_if_readable = function(t, path)
+ if awful_util.dir_readable(path) then
+ table.insert(t, path)
+ end
+ end
+ icon_lookup_path = {}
+ local icon_theme_paths = {}
+ local icon_theme = theme.icon_theme
+ local paths = glib.get_system_data_dirs()
+ table.insert(paths, 1, glib.get_user_data_dir())
+ table.insert(paths, 1, glib.build_filenamev({glib.get_home_dir(),
+ '.icons'}))
+ for _,dir in ipairs(paths) do
+ local icons_dir = glib.build_filenamev({dir, 'icons'})
+ if awful_util.dir_readable(icons_dir) then
+ if icon_theme then
+ add_if_readable(icon_theme_paths,
+ glib.build_filenamev({icons_dir,
+ icon_theme}))
+ end
+ -- Fallback theme.
+ add_if_readable(icon_theme_paths,
+ glib.build_filenamev({icons_dir, 'hicolor'}))
+ end
+ end
+ for _, icon_theme_directory in ipairs(icon_theme_paths) do
+ for _, size in ipairs(all_icon_sizes) do
+ add_if_readable(icon_lookup_path,
+ glib.build_filenamev({icon_theme_directory,
+ size, 'apps'}))
+ end
+ end
+ for _,dir in ipairs(paths)do
+ -- lowest priority fallbacks
+ add_if_readable(icon_lookup_path,
+ glib.build_filenamev({dir, 'pixmaps'}))
+ add_if_readable(icon_lookup_path,
+ glib.build_filenamev({dir, 'icons'}))
+ end
+ end
+ return icon_lookup_path
+end
+
+--- Lookup an icon in different folders of the filesystem.
+-- @tparam string icon_file Short or full name of the icon.
+-- @treturn string|boolean Full name of the icon, or false on failure.
+function utils.lookup_icon_uncached(icon_file)
+ if not icon_file or icon_file == "" then
+ return false
+ end
+
+ if icon_file:sub(1, 1) == '/' and is_format_supported(icon_file) then
+ -- If the path to the icon is absolute and its format is
+ -- supported, do not perform a lookup.
+ return awful_util.file_readable(icon_file) and icon_file or nil
+ else
+ for _, directory in ipairs(get_icon_lookup_path()) do
+ if is_format_supported(icon_file) and
+ awful_util.file_readable(directory .. "/" .. icon_file) then
+ return directory .. "/" .. icon_file
+ else
+ -- Icon is probably specified without path and format,
+ -- like 'firefox'. Try to add supported extensions to
+ -- it and see if such file exists.
+ for _, format in ipairs(icon_formats) do
+ local possible_file = directory .. "/" .. icon_file .. "." .. format
+ if awful_util.file_readable(possible_file) then
+ return possible_file
+ end
+ end
+ end
+ end
+ return false
+ end
+end
+
+local lookup_icon_cache = {}
+--- Lookup an icon in different folders of the filesystem (cached).
+-- @param icon Short or full name of the icon.
+-- @return full name of the icon.
+function utils.lookup_icon(icon)
+ if not lookup_icon_cache[icon] and lookup_icon_cache[icon] ~= false then
+ lookup_icon_cache[icon] = utils.lookup_icon_uncached(icon)
+ end
+ return lookup_icon_cache[icon] or default_icon
+end
+
+--- Parse a .desktop file.
+-- @param file The .desktop file.
+-- @return A table with file entries.
+function utils.parse_desktop_file(file)
+ local program = { show = true, file = file }
+ local desktop_entry = false
+
+ -- Parse the .desktop file.
+ -- We are interested in [Desktop Entry] group only.
+ for line in io.lines(file) do
+ if line:find("^%s*#") then
+ -- Skip comments.
+ (function() end)() -- I haven't found a nice way to silence luacheck here
+ elseif not desktop_entry and line == "[Desktop Entry]" then
+ desktop_entry = true
+ else
+ if line:sub(1, 1) == "[" and line:sub(-1) == "]" then
+ -- A declaration of new group - stop parsing
+ break
+ end
+
+ -- Grab the values
+ for key, value in line:gmatch("(%w+)%s*=%s*(.+)") do
+ program[key] = value
+ end
+ end
+ end
+
+ -- In case [Desktop Entry] was not found
+ if not desktop_entry then return nil end
+
+ -- In case the (required) 'Name' entry was not found
+ if not program.Name or program.Name == '' then return nil end
+
+ -- Don't show program if NoDisplay attribute is false
+ if program.NoDisplay and string.lower(program.NoDisplay) == "true" then
+ program.show = false
+ end
+
+ -- Only show the program if there is no OnlyShowIn attribute
+ -- or if it's equal to utils.wm_name
+ if program.OnlyShowIn ~= nil and not program.OnlyShowIn:match(utils.wm_name) then
+ program.show = false
+ end
+
+ -- Look up for a icon.
+ if program.Icon then
+ program.icon_path = utils.lookup_icon(program.Icon)
+ end
+
+ -- Split categories into a table. Categories are written in one
+ -- line separated by semicolon.
+ if program.Categories then
+ program.categories = {}
+ for category in program.Categories:gmatch('[^;]+') do
+ table.insert(program.categories, category)
+ end
+ end
+
+ if program.Exec then
+ -- Substitute Exec special codes as specified in
+ -- http://standards.freedesktop.org/desktop-entry-spec/1.1/ar01s06.html
+ if program.Name == nil then
+ program.Name = '['.. file:match("([^/]+)%.desktop$") ..']'
+ end
+ local cmdline = program.Exec:gsub('%%c', program.Name)
+ cmdline = cmdline:gsub('%%[fuFU]', '')
+ cmdline = cmdline:gsub('%%k', program.file)
+ if program.icon_path then
+ cmdline = cmdline:gsub('%%i', '--icon ' .. program.icon_path)
+ else
+ cmdline = cmdline:gsub('%%i', '')
+ end
+ if program.Terminal == "true" then
+ cmdline = utils.terminal .. ' -e ' .. cmdline
+ end
+ program.cmdline = cmdline
+ end
+
+ return program
+end
+
+--- Parse a directory with .desktop files recursively.
+-- @tparam string dir_path The directory path.
+-- @tparam function callback Will be fired when all the files were parsed
+-- with the resulting list of menu entries as argument.
+-- @tparam table callback.programs Paths of found .desktop files.
+function utils.parse_dir(dir_path, callback)
+
+ local function parser(dir, programs)
+ local f = gio.File.new_for_path(dir)
+ -- Except for "NONE" there is also NOFOLLOW_SYMLINKS
+ local query = gio.FILE_ATTRIBUTE_STANDARD_NAME .. "," .. gio.FILE_ATTRIBUTE_STANDARD_TYPE
+ local enum, err = f:async_enumerate_children(query, gio.FileQueryInfoFlags.NONE)
+ if not enum then
+ debug.print_error(err)
+ return
+ end
+ local files_per_call = 100 -- Actual value is not that important
+ while true do
+ local list, enum_err = enum:async_next_files(files_per_call)
+ if enum_err then
+ debug.print_error(enum_err)
+ return
+ end
+ for _, info in ipairs(list) do
+ local file_type = info:get_file_type()
+ local file_path = enum:get_child(info):get_path()
+ if file_type == 'REGULAR' then
+ local program = utils.parse_desktop_file(file_path)
+ if program then
+ table.insert(programs, program)
+ end
+ elseif file_type == 'DIRECTORY' then
+ parser(file_path, programs)
+ end
+ end
+ if #list == 0 then
+ break
+ end
+ end
+ enum:async_close()
+ end
+
+ gio.Async.start(function()
+ local result = {}
+ parser(dir_path, result)
+ protected_call.call(callback, result)
+ end)()
+end
+
+--- Compute textbox width.
+-- @tparam wibox.widget.textbox textbox Textbox instance.
+-- @tparam number|screen s Screen
+-- @treturn int Text width.
+function utils.compute_textbox_width(textbox, s)
+ s = screen[s or mouse.screen]
+ local w, _ = textbox:get_preferred_size(s)
+ return w
+end
+
+--- Compute text width.
+-- @tparam str text Text.
+-- @tparam number|screen s Screen
+-- @treturn int Text width.
+function utils.compute_text_width(text, s)
+ return utils.compute_textbox_width(wibox.widget.textbox(awful_util.escape(text)), s)
+end
+
+return utils
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/naughty.lua b/lib/naughty.lua
new file mode 100644
index 0000000..89eac7c
--- /dev/null
+++ b/lib/naughty.lua
@@ -0,0 +1,7 @@
+-- Work-around for broken systems which are updated by overwriting the awesome
+-- installation. This would not remove naughty.lua from older awesome versions
+-- and thus breakage follows.
+-- The work-around is to use a pointless naughty.lua file.
+return require("naughty.init")
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/naughty/core.lua b/lib/naughty/core.lua
new file mode 100644
index 0000000..764afe2
--- /dev/null
+++ b/lib/naughty/core.lua
@@ -0,0 +1,688 @@
+----------------------------------------------------------------------------
+--- Notification library
+--
+-- @author koniu &lt;gkusnierz@gmail.com&gt;
+-- @copyright 2008 koniu
+-- @module naughty
+----------------------------------------------------------------------------
+
+-- Package environment
+local pairs = pairs
+local table = table
+local type = type
+local string = string
+local pcall = pcall
+local capi = { screen = screen,
+ awesome = awesome }
+local timer = require("gears.timer")
+local button = require("awful.button")
+local screen = require("awful.screen")
+local util = require("awful.util")
+local bt = require("beautiful")
+local wibox = require("wibox")
+local surface = require("gears.surface")
+local cairo = require("lgi").cairo
+local dpi = require("beautiful").xresources.apply_dpi
+
+local function get_screen(s)
+ return s and capi.screen[s]
+end
+
+local naughty = {}
+
+--[[--
+Naughty configuration - a table containing common popup settings.
+
+@table naughty.config
+@tfield[opt=apply_dpi(4)] int padding Space between popups and edge of the
+ workarea.
+@tfield[opt=apply_dpi(1)] int spacing Spacing between popups.
+@tfield[opt={"/usr/share/pixmaps/"}] table icon_dirs List of directories
+ that will be checked by `getIcon()`.
+@tfield[opt={ "png", "gif" }] table icon_formats List of formats that will be
+ checked by `getIcon()`.
+@tfield[opt] function notify_callback Callback used to modify or reject
+notifications, e.g.
+ naughty.config.notify_callback = function(args)
+ args.text = 'prefix: ' .. args.text
+ return args
+ end
+
+@tfield table presets Notification presets. See `config.presets`.
+
+@tfield table defaults Default values for the params to `notify()`. These can
+ optionally be overridden by specifying a preset. See `config.defaults`.
+
+--]]
+--
+naughty.config = {
+ padding = dpi(4),
+ spacing = dpi(1),
+ icon_dirs = { "/usr/share/pixmaps/", },
+ icon_formats = { "png", "gif" },
+ notify_callback = nil,
+}
+
+--- Notification presets for `naughty.notify`.
+-- This holds presets for different purposes. A preset is a table of any
+-- parameters for `notify()`, overriding the default values
+-- (`naughty.config.defaults`).
+--
+-- You have to pass a reference of a preset in your `notify()` as the `preset`
+-- argument.
+--
+-- The presets `"low"`, `"normal"` and `"critical"` are used for notifications
+-- over DBUS.
+--
+-- @table config.presets
+-- @tfield table low The preset for notifications with low urgency level.
+-- @tfield[opt=5] int low.timeout
+-- @tfield[opt=empty] table normal The default preset for every notification without a
+-- preset that will also be used for normal urgency level.
+-- @tfield table critical The preset for notifications with a critical urgency
+-- level.
+-- @tfield[opt="#ff0000"] string critical.bg
+-- @tfield[opt="#ffffff"] string critical.fg
+-- @tfield[opt=0] string critical.timeout
+naughty.config.presets = {
+ low = {
+ timeout = 5
+ },
+ normal = {},
+ critical = {
+ bg = "#ff0000",
+ fg = "#ffffff",
+ timeout = 0,
+ }
+}
+
+--- Defaults for `naughty.notify`.
+--
+-- @table config.defaults
+-- @tfield[opt=5] int timeout
+-- @tfield[opt=""] string text
+-- @tfield[opt] int screen Defaults to `awful.screen.focused`.
+-- @tfield[opt=true] boolean ontop
+-- @tfield[opt=apply_dpi(5)] int margin
+-- @tfield[opt=apply_dpi(1)] int border_width
+-- @tfield[opt="top_right"] string position
+naughty.config.defaults = {
+ timeout = 5,
+ text = "",
+ screen = nil,
+ ontop = true,
+ margin = dpi(5),
+ border_width = dpi(1),
+ position = "top_right"
+}
+
+naughty.notificationClosedReason = {
+ silent = -1,
+ expired = 1,
+ dismissedByUser = 2,
+ dismissedByCommand = 3,
+ undefined = 4
+}
+
+-- Counter for the notifications
+-- Required for later access via DBUS
+local counter = 1
+
+-- True if notifying is suspended
+local suspended = false
+
+--- Index of notifications per screen and position.
+-- See config table for valid 'position' values.
+-- Each element is a table consisting of:
+--
+-- @field box Wibox object containing the popup
+-- @field height Popup height
+-- @field width Popup width
+-- @field die Function to be executed on timeout
+-- @field id Unique notification id based on a counter
+-- @table notifications
+naughty.notifications = { suspended = { } }
+screen.connect_for_each_screen(function(s)
+ naughty.notifications[s] = {
+ top_left = {},
+ top_middle = {},
+ top_right = {},
+ bottom_left = {},
+ bottom_middle = {},
+ bottom_right = {},
+ }
+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
+end
+
+--- Suspend notifications
+function naughty.suspend()
+ suspended = true
+end
+
+--- Resume notifications
+function naughty.resume()
+ suspended = false
+ for _, v in pairs(naughty.notifications.suspended) do
+ v.box.visible = true
+ if v.timer then v.timer:start() end
+ end
+ naughty.notifications.suspended = { }
+end
+
+--- Toggle notification state
+function naughty.toggle()
+ if suspended then
+ naughty.resume()
+ else
+ naughty.suspend()
+ end
+end
+
+--- Evaluate desired position of the notification by index - internal
+--
+-- @param s Screen to use
+-- @param position top_right | top_left | bottom_right | bottom_left
+-- | top_middle | bottom_middle
+-- @param idx Index of the notification
+-- @param[opt] width Popup width.
+-- @param height Popup height
+-- @return Absolute position and index in { x = X, y = Y, idx = I } table
+local function get_offset(s, position, idx, width, height)
+ s = get_screen(s)
+ local ws = s.workarea
+ local v = {}
+ idx = idx or #naughty.notifications[s][position] + 1
+ width = width or naughty.notifications[s][position][idx].width
+
+ -- calculate x
+ if position:match("left") then
+ v.x = ws.x + naughty.config.padding
+ elseif position:match("middle") then
+ v.x = (ws.width / 2) - (width / 2)
+ else
+ v.x = ws.x + ws.width - (width + naughty.config.padding)
+ end
+
+ -- calculate existing popups' height
+ local existing = 0
+ for i = 1, idx-1, 1 do
+ existing = existing + naughty.notifications[s][position][i].height + naughty.config.spacing
+ end
+
+ -- calculate y
+ if position:match("top") then
+ v.y = ws.y + naughty.config.padding + existing
+ else
+ v.y = ws.y + ws.height - (naughty.config.padding + height + existing)
+ end
+
+ -- Find old notification to replace in case there is not enough room.
+ -- This tries to skip permanent notifications (without a timeout),
+ -- e.g. critical ones.
+ local find_old_to_replace = function()
+ for i = 1, idx-1 do
+ local n = naughty.notifications[s][position][i]
+ if n.timeout > 0 then
+ return n
+ end
+ end
+ -- Fallback to first one.
+ return naughty.notifications[s][position][1]
+ end
+
+ -- if positioned outside workarea, destroy oldest popup and recalculate
+ if v.y + height > ws.y + ws.height or v.y < ws.y then
+ naughty.destroy(find_old_to_replace())
+ idx = idx - 1
+ v = get_offset(s, position, idx, width, height)
+ end
+ if not v.idx then v.idx = idx end
+
+ return v
+end
+
+--- Re-arrange notifications according to their position and index - internal
+--
+-- @return None
+local function arrange(s)
+ for p in pairs(naughty.notifications[s]) do
+ for i,notification in pairs(naughty.notifications[s][p]) do
+ local offset = get_offset(s, p, i, notification.width, notification.height)
+ notification.box:geometry({ x = offset.x, y = offset.y })
+ notification.idx = offset.idx
+ end
+ end
+end
+
+--- Destroy notification by notification object
+--
+-- @param notification Notification object to be destroyed
+-- @param reason One of the reasons from notificationClosedReason
+-- @return True if the popup was successfully destroyed, nil otherwise
+function naughty.destroy(notification, reason)
+ if notification and notification.box.visible then
+ if suspended then
+ for k, v in pairs(naughty.notifications.suspended) do
+ if v.box == notification.box then
+ table.remove(naughty.notifications.suspended, k)
+ break
+ end
+ end
+ end
+ local scr = notification.screen
+ table.remove(naughty.notifications[scr][notification.position], notification.idx)
+ if notification.timer then
+ notification.timer:stop()
+ end
+ notification.box.visible = false
+ arrange(scr)
+ if notification.destroy_cb and reason ~= naughty.notificationClosedReason.silent then
+ notification.destroy_cb(reason or naughty.notificationClosedReason.undefined)
+ end
+ return true
+ end
+end
+
+--- Get notification by ID
+--
+-- @param id ID of the notification
+-- @return notification object if it was found, nil otherwise
+function naughty.getById(id)
+ -- iterate the notifications to get the notfications with the correct ID
+ for s in pairs(naughty.notifications) do
+ for p in pairs(naughty.notifications[s]) do
+ for _, notification in pairs(naughty.notifications[s][p]) do
+ if notification.id == id then
+ return notification
+ end
+ end
+ end
+ end
+end
+
+--- Install expiration timer for notification object.
+-- @tparam notification notification Notification object.
+-- @tparam number timeout Time in seconds to be set as expiration timeout.
+local function set_timeout(notification, timeout)
+ local die = function (reason)
+ naughty.destroy(notification, reason)
+ end
+ if timeout > 0 then
+ local timer_die = timer { timeout = timeout }
+ timer_die:connect_signal("timeout", function() die(naughty.notificationClosedReason.expired) end)
+ if not suspended then
+ timer_die:start()
+ end
+ notification.timer = timer_die
+ end
+ notification.die = die
+end
+
+--- Set new notification timeout.
+-- @tparam notification notification Notification object, which timer is to be reset.
+-- @tparam number new_timeout Time in seconds after which notification disappears.
+-- @return None.
+function naughty.reset_timeout(notification, new_timeout)
+ if notification.timer then notification.timer:stop() end
+
+ local timeout = new_timeout or notification.timeout
+ set_timeout(notification, timeout)
+ notification.timeout = timeout
+
+ notification.timer:start()
+end
+
+--- Escape and set title and text for notification object.
+-- @tparam notification notification Notification object.
+-- @tparam string title Title of notification.
+-- @tparam string text Main text of notification.
+-- @return None.
+local function set_text(notification, title, text)
+ local escape_pattern = "[<>&]"
+ local escape_subs = { ['<'] = "&lt;", ['>'] = "&gt;", ['&'] = "&amp;" }
+
+ local textbox = notification.textbox
+
+ local function setMarkup(pattern, replacements)
+ return textbox:set_markup_silently(string.format('<b>%s</b>%s', title, text:gsub(pattern, replacements)))
+ end
+ local function setText()
+ textbox:set_text(string.format('%s %s', title, text))
+ end
+
+ -- Since the title cannot contain markup, it must be escaped first so that
+ -- it is not interpreted by Pango later.
+ title = title:gsub(escape_pattern, escape_subs)
+ -- Try to set the text while only interpreting <br>.
+ if not setMarkup("<br.->", "\n") then
+ -- That failed, escape everything which might cause an error from pango
+ if not setMarkup(escape_pattern, escape_subs) then
+ -- Ok, just ignore all pango markup. If this fails, we got some invalid utf8
+ if not pcall(setText) then
+ textbox:set_markup("<i>&lt;Invalid markup or UTF8, cannot display message&gt;</i>")
+ end
+ end
+ end
+end
+
+--- Replace title and text of an existing notification.
+-- @tparam notification notification Notification object, which contents are to be replaced.
+-- @tparam string new_title New title of notification. If not specified, old title remains unchanged.
+-- @tparam string new_text New text of notification. If not specified, old text remains unchanged.
+-- @return None.
+function naughty.replace_text(notification, new_title, new_text)
+ local title = new_title
+
+ if title then title = title .. "\n" else title = "" end
+
+ set_text(notification, title, new_text)
+end
+
+--- Create a notification.
+--
+-- @tab args The argument table containing any of the arguments below.
+-- @string[opt=""] args.text Text of the notification.
+-- @string[opt] args.title Title of the notification.
+-- @int[opt=5] args.timeout Time in seconds after which popup expires.
+-- Set 0 for no timeout.
+-- @int[opt] args.hover_timeout Delay in seconds after which hovered popup disappears.
+-- @tparam[opt=focused] integer|screen args.screen Target screen for the notification.
+-- @string[opt="top_right"] args.position Corner of the workarea displaying the popups.
+-- Values: `"top_right"`, `"top_left"`, `"bottom_left"`,
+-- `"bottom_right"`, `"top_middle"`, `"bottom_middle"`.
+-- @bool[opt=true] args.ontop Boolean forcing popups to display on top.
+-- @int[opt=auto] args.height Popup height.
+-- @int[opt=auto] args.width Popup width.
+-- @string[opt=beautiful.font or awesome.font] args.font Notification font.
+-- @string[opt] args.icon Path to icon.
+-- @int[opt] args.icon_size Desired icon size in px.
+-- @string[opt=`beautiful.fg_focus` or `'#ffffff'`] args.fg Foreground color.
+-- @string[opt=`beautiful.bg_focus` or `'#535d6c'`] args.bg Background color.
+-- @int[opt=1] args.border_width Border width.
+-- @string[opt=`beautiful.border_focus` or `'#535d6c'`] args.border_color Border color.
+-- @tparam[opt] func args.run Function to run on left click. The notification
+-- object will be passed to it as an argument.
+-- You need to call e.g.
+-- `notification.die(naughty.notificationClosedReason.dismissedByUser)` from
+-- there to dismiss the notification yourself.
+-- @tparam[opt] func args.destroy Function to run when notification is destroyed.
+-- @tparam[opt] table args.preset Table with any of the above parameters.
+-- Note: Any parameters specified directly in args will override ones defined
+-- in the preset.
+-- @tparam[opt] int args.replaces_id Replace the notification with the given ID.
+-- @tparam[opt] func args.callback Function that will be called with all arguments.
+-- The notification will only be displayed if the function returns true.
+-- Note: this function is only relevant to notifications sent via dbus.
+-- @tparam[opt] table args.actions Mapping that maps a string to a callback when this
+-- action is selected.
+-- @usage naughty.notify({ title = "Achtung!", text = "You're idling", timeout = 0 })
+-- @treturn ?table The notification object, or nil in case a notification was
+-- not displayed.
+function naughty.notify(args)
+ if naughty.config.notify_callback then
+ args = naughty.config.notify_callback(args)
+ if not args then return end
+ end
+
+ -- gather variables together
+ local preset = util.table.join(naughty.config.defaults or {},
+ args.preset or naughty.config.presets.normal or {})
+ local timeout = args.timeout or preset.timeout
+ local icon = args.icon or preset.icon
+ local icon_size = args.icon_size or preset.icon_size
+ local text = args.text or preset.text
+ local title = args.title or preset.title
+ local s = get_screen(args.screen or preset.screen or screen.focused())
+ if not s then
+ local err = "naughty.notify: there is no screen available to display the following notification:"
+ err = string.format("%s title='%s' text='%s'", err, tostring(title or ""), tostring(text or ""))
+ require("gears.debug").print_warning(err)
+ return
+ end
+ local ontop = args.ontop or preset.ontop
+ local width = args.width or preset.width
+ local height = args.height or preset.height
+ local hover_timeout = args.hover_timeout or preset.hover_timeout
+ local opacity = args.opacity or preset.opacity
+ local margin = args.margin or preset.margin
+ local border_width = args.border_width or preset.border_width
+ local position = args.position or preset.position
+ local actions = args.actions
+ local destroy_cb = args.destroy
+
+ -- beautiful
+ local beautiful = bt.get()
+ local font = args.font or preset.font or beautiful.font or capi.awesome.font
+ local fg = args.fg or preset.fg or beautiful.fg_normal or '#ffffff'
+ local bg = args.bg or preset.bg or beautiful.bg_normal or '#535d6c'
+ local border_color = args.border_color or preset.border_color or beautiful.bg_focus or '#535d6c'
+ local notification = { screen = s, destroy_cb = destroy_cb, timeout = timeout }
+
+ -- replace notification if needed
+ if args.replaces_id then
+ local obj = naughty.getById(args.replaces_id)
+ if obj then
+ -- destroy this and ...
+ naughty.destroy(obj, naughty.notificationClosedReason.silent)
+ end
+ -- ... may use its ID
+ if args.replaces_id <= counter then
+ notification.id = args.replaces_id
+ else
+ counter = counter + 1
+ notification.id = counter
+ end
+ else
+ -- get a brand new ID
+ counter = counter + 1
+ notification.id = counter
+ end
+
+ notification.position = position
+
+ if title then title = title .. "\n" else title = "" end
+
+ -- hook destroy
+ set_timeout(notification, timeout)
+ local die = notification.die
+
+ local run = function ()
+ if args.run then
+ args.run(notification)
+ else
+ die(naughty.notificationClosedReason.dismissedByUser)
+ end
+ end
+
+ local hover_destroy = function ()
+ if hover_timeout == 0 then
+ die(naughty.notificationClosedReason.expired)
+ else
+ if notification.timer then notification.timer:stop() end
+ notification.timer = timer { timeout = hover_timeout }
+ notification.timer:connect_signal("timeout", function() die(naughty.notificationClosedReason.expired) end)
+ notification.timer:start()
+ end
+ end
+
+ -- create textbox
+ local textbox = wibox.widget.textbox()
+ local marginbox = wibox.container.margin()
+ marginbox:set_margins(margin)
+ marginbox:set_widget(textbox)
+ textbox:set_valign("middle")
+ textbox:set_font(font)
+
+ notification.textbox = textbox
+
+ set_text(notification, title, text)
+
+ local actionslayout = wibox.layout.fixed.vertical()
+ local actions_max_width = 0
+ local actions_total_height = 0
+ if actions then
+ for action, callback in pairs(actions) do
+ local actiontextbox = wibox.widget.textbox()
+ local actionmarginbox = wibox.container.margin()
+ actionmarginbox:set_margins(margin)
+ actionmarginbox:set_widget(actiontextbox)
+ actiontextbox:set_valign("middle")
+ actiontextbox:set_font(font)
+ actiontextbox:set_markup(string.format('☛ <u>%s</u>', action))
+ -- calculate the height and width
+ local w, h = actiontextbox:get_preferred_size(s)
+ local action_height = h + 2 * margin
+ local action_width = w + 2 * margin
+
+ actionmarginbox:buttons(util.table.join(
+ button({ }, 1, callback),
+ button({ }, 3, callback)
+ ))
+ actionslayout:add(actionmarginbox)
+
+ actions_total_height = actions_total_height + action_height
+ if actions_max_width < action_width then
+ actions_max_width = action_width
+ end
+ end
+ end
+
+ -- create iconbox
+ local iconbox = nil
+ local iconmargin = nil
+ local icon_w, icon_h = 0, 0
+ if icon then
+ -- Is this really an URI instead of a path?
+ if type(icon) == "string" and string.sub(icon, 1, 7) == "file://" then
+ icon = string.sub(icon, 8)
+ end
+ -- try to guess icon if the provided one is non-existent/readable
+ if type(icon) == "string" and not util.file_readable(icon) then
+ icon = util.geticonpath(icon, naughty.config.icon_formats, naughty.config.icon_dirs, icon_size) or icon
+ end
+
+ -- is the icon file readable?
+ icon = surface.load_uncached(icon)
+
+ -- if we have an icon, use it
+ if icon then
+ iconbox = wibox.widget.imagebox()
+ iconmargin = wibox.container.margin(iconbox, margin, margin, margin, margin)
+ if icon_size then
+ local scaled = cairo.ImageSurface(cairo.Format.ARGB32, icon_size, icon_size)
+ local cr = cairo.Context(scaled)
+ cr:scale(icon_size / icon:get_height(), icon_size / icon:get_width())
+ cr:set_source_surface(icon, 0, 0)
+ cr:paint()
+ icon = scaled
+ end
+ iconbox:set_resize(false)
+ iconbox:set_image(icon)
+ icon_w = icon:get_width()
+ icon_h = icon:get_height()
+ end
+ end
+
+ -- create container wibox
+ notification.box = wibox({ fg = fg,
+ bg = bg,
+ border_color = border_color,
+ border_width = border_width,
+ type = "notification" })
+
+ if hover_timeout then notification.box:connect_signal("mouse::enter", hover_destroy) end
+
+ -- calculate the width
+ if not width then
+ local w, _ = textbox:get_preferred_size(s)
+ width = w + (iconbox and icon_w + 2 * margin or 0) + 2 * margin
+ end
+
+ if width < actions_max_width then
+ width = actions_max_width
+ end
+
+ -- calculate the height
+ if not height then
+ local w = width - (iconbox and icon_w + 2 * margin or 0) - 2 * margin
+ local h = textbox:get_height_for_width(w, s)
+ if iconbox and icon_h + 2 * margin > h + 2 * margin then
+ height = icon_h + 2 * margin
+ else
+ height = h + 2 * margin
+ end
+ end
+
+ height = height + actions_total_height
+
+ -- crop to workarea size if too big
+ local workarea = s.workarea
+ if width > workarea.width - 2 * (border_width or 0) - 2 * (naughty.config.padding or 0) then
+ width = workarea.width - 2 * (border_width or 0) - 2 * (naughty.config.padding or 0)
+ end
+ if height > workarea.height - 2 * (border_width or 0) - 2 * (naughty.config.padding or 0) then
+ height = workarea.height - 2 * (border_width or 0) - 2 * (naughty.config.padding or 0)
+ end
+
+ -- set size in notification object
+ notification.height = height + 2 * (border_width or 0)
+ notification.width = width + 2 * (border_width or 0)
+
+ -- position the wibox
+ local offset = get_offset(s, notification.position, nil, notification.width, notification.height)
+ notification.box.ontop = ontop
+ notification.box:geometry({ width = width,
+ height = height,
+ x = offset.x,
+ y = offset.y })
+ notification.box.opacity = opacity
+ notification.box.visible = true
+ notification.idx = offset.idx
+
+ -- populate widgets
+ local layout = wibox.layout.fixed.horizontal()
+ if iconmargin then
+ layout:add(iconmargin)
+ end
+ layout:add(marginbox)
+
+ local completelayout = wibox.layout.fixed.vertical()
+ completelayout:add(layout)
+ completelayout:add(actionslayout)
+ notification.box:set_widget(completelayout)
+
+ -- Setup the mouse events
+ layout:buttons(util.table.join(button({ }, 1, run),
+ button({ }, 3, function()
+ die(naughty.notificationClosedReason.dismissedByUser)
+ end)))
+
+ -- insert the notification to the table
+ table.insert(naughty.notifications[s][notification.position], notification)
+
+ if suspended then
+ notification.box.visible = false
+ table.insert(naughty.notifications.suspended, notification)
+ end
+
+ -- return the notification
+ return notification
+end
+
+return naughty
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/naughty/dbus.lua b/lib/naughty/dbus.lua
new file mode 100644
index 0000000..c49dc7e
--- /dev/null
+++ b/lib/naughty/dbus.lua
@@ -0,0 +1,261 @@
+---------------------------------------------------------------------------
+-- DBUS/Notification support
+-- Notify
+--
+-- @author koniu &lt;gkusnierz@gmail.com&gt;
+-- @copyright 2008 koniu
+-- @module naughty.dbus
+---------------------------------------------------------------------------
+
+assert(dbus)
+
+-- Package environment
+local pairs = pairs
+local type = type
+local string = string
+local capi = { awesome = awesome,
+ dbus = dbus }
+local util = require("awful.util")
+local cairo = require("lgi").cairo
+
+local schar = string.char
+local sbyte = string.byte
+local tcat = table.concat
+local tins = table.insert
+local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
+local naughty = require("naughty.core")
+
+--- Notification library, dbus bindings
+local dbus = { config = {} }
+
+-- DBUS Notification constants
+local urgency = {
+ low = "\0",
+ normal = "\1",
+ critical = "\2"
+}
+
+--- DBUS notification to preset mapping.
+-- The first element is an object containing the filter.
+-- If the rules in the filter match, the associated preset will be applied.
+-- The rules object can contain the following keys: urgency, category, appname.
+-- The second element is the preset.
+-- @tfield table 1 low urgency
+-- @tfield table 2 normal urgency
+-- @tfield table 3 critical urgency
+-- @table config.mapping
+dbus.config.mapping = {
+ {{urgency = urgency.low}, naughty.config.presets.low},
+ {{urgency = urgency.normal}, naughty.config.presets.normal},
+ {{urgency = urgency.critical}, naughty.config.presets.critical}
+}
+
+local function sendActionInvoked(notificationId, action)
+ if capi.dbus then
+ capi.dbus.emit_signal("session", "/org/freedesktop/Notifications",
+ "org.freedesktop.Notifications", "ActionInvoked",
+ "u", notificationId,
+ "s", action)
+ end
+end
+
+local function sendNotificationClosed(notificationId, reason)
+ if capi.dbus then
+ capi.dbus.emit_signal("session", "/org/freedesktop/Notifications",
+ "org.freedesktop.Notifications", "NotificationClosed",
+ "u", notificationId,
+ "u", reason)
+ end
+end
+
+local function convert_icon(w, h, rowstride, channels, data)
+ -- Do the arguments look sane? (e.g. we have enough data)
+ local expected_length = rowstride * (h - 1) + w * channels
+ if w < 0 or h < 0 or rowstride < 0 or (channels ~= 3 and channels ~= 4) or
+ string.len(data) < expected_length then
+ w = 0
+ h = 0
+ end
+
+ local format = cairo.Format[channels == 4 and 'ARGB32' or 'RGB24']
+
+ -- Figure out some stride magic (cairo dictates rowstride)
+ local stride = cairo.Format.stride_for_width(format, w)
+ local append = schar(0):rep(stride - 4 * w)
+ local offset = 0
+
+ -- Now convert each row on its own
+ local rows = {}
+
+ for _ = 1, h do
+ local this_row = {}
+
+ for i = 1 + offset, w * channels + offset, channels do
+ local R, G, B, A = sbyte(data, i, i + channels - 1)
+ tins(this_row, schar(B, G, R, A or 255))
+ end
+
+ -- Handle rowstride, offset is stride for the input, append for output
+ tins(this_row, append)
+ tins(rows, tcat(this_row))
+
+ offset = offset + rowstride
+ end
+
+ return cairo.ImageSurface.create_for_data(tcat(rows), format, w, h, stride)
+end
+
+capi.dbus.connect_signal("org.freedesktop.Notifications", function (data, appname, replaces_id, icon, title, text, actions, hints, expire)
+ local args = { }
+ if data.member == "Notify" then
+ if text ~= "" then
+ args.text = text
+ if title ~= "" then
+ args.title = title
+ end
+ else
+ if title ~= "" then
+ args.text = title
+ else
+ return
+ end
+ end
+ if appname ~= "" then
+ args.appname = appname
+ end
+ for _, obj in pairs(dbus.config.mapping) do
+ local filter, preset = obj[1], obj[2]
+ if (not filter.urgency or filter.urgency == hints.urgency) and
+ (not filter.category or filter.category == hints.category) and
+ (not filter.appname or filter.appname == appname) then
+ args.preset = util.table.join(args.preset, preset)
+ end
+ end
+ local preset = args.preset or naughty.config.defaults
+ local notification
+ if actions then
+ args.actions = {}
+
+ for i = 1,#actions,2 do
+ local action_id = actions[i]
+ local action_text = actions[i + 1]
+
+ if action_id == "default" then
+ args.run = function()
+ sendActionInvoked(notification.id, "default")
+ naughty.destroy(notification, naughty.notificationClosedReason.dismissedByUser)
+ end
+ elseif action_id ~= nil and action_text ~= nil then
+ args.actions[action_text] = function()
+ sendActionInvoked(notification.id, action_id)
+ naughty.destroy(notification, naughty.notificationClosedReason.dismissedByUser)
+ end
+ end
+ end
+ end
+ args.destroy = function(reason)
+ sendNotificationClosed(notification.id, reason)
+ end
+ if not preset.callback or (type(preset.callback) == "function" and
+ preset.callback(data, appname, replaces_id, icon, title, text, actions, hints, expire)) then
+ if icon ~= "" then
+ args.icon = icon
+ elseif hints.icon_data or hints.image_data then
+ if hints.icon_data == nil then hints.icon_data = hints.image_data end
+
+ -- icon_data is an array:
+ -- 1 -> width
+ -- 2 -> height
+ -- 3 -> rowstride
+ -- 4 -> has alpha
+ -- 5 -> bits per sample
+ -- 6 -> channels
+ -- 7 -> data
+ local w, h, rowstride, _, _, channels, icon_data = unpack(hints.icon_data)
+ args.icon = convert_icon(w, h, rowstride, channels, icon_data)
+ end
+ if replaces_id and replaces_id ~= "" and replaces_id ~= 0 then
+ args.replaces_id = replaces_id
+ end
+ if expire and expire > -1 then
+ args.timeout = expire / 1000
+ end
+ notification = naughty.notify(args)
+ return "u", notification.id
+ end
+ return "u", "0"
+ elseif data.member == "CloseNotification" then
+ local obj = naughty.getById(appname)
+ if obj then
+ naughty.destroy(obj, naughty.notificationClosedReason.dismissedByCommand)
+ end
+ elseif data.member == "GetServerInfo" or data.member == "GetServerInformation" then
+ -- name of notification app, name of vender, version, specification version
+ return "s", "naughty", "s", "awesome", "s", capi.awesome.version, "s", "1.0"
+ elseif data.member == "GetCapabilities" then
+ -- We actually do display the body of the message, we support <b>, <i>
+ -- and <u> in the body and we handle static (non-animated) icons.
+ return "as", { "s", "body", "s", "body-markup", "s", "icon-static", "s", "actions" }
+ end
+end)
+
+capi.dbus.connect_signal("org.freedesktop.DBus.Introspectable", function (data)
+ if data.member == "Introspect" then
+ local xml = [=[<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object
+ Introspection 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+ <node>
+ <interface name="org.freedesktop.DBus.Introspectable">
+ <method name="Introspect">
+ <arg name="data" direction="out" type="s"/>
+ </method>
+ </interface>
+ <interface name="org.freedesktop.Notifications">
+ <method name="GetCapabilities">
+ <arg name="caps" type="as" direction="out"/>
+ </method>
+ <method name="CloseNotification">
+ <arg name="id" type="u" direction="in"/>
+ </method>
+ <method name="Notify">
+ <arg name="app_name" type="s" direction="in"/>
+ <arg name="id" type="u" direction="in"/>
+ <arg name="icon" type="s" direction="in"/>
+ <arg name="summary" type="s" direction="in"/>
+ <arg name="body" type="s" direction="in"/>
+ <arg name="actions" type="as" direction="in"/>
+ <arg name="hints" type="a{sv}" direction="in"/>
+ <arg name="timeout" type="i" direction="in"/>
+ <arg name="return_id" type="u" direction="out"/>
+ </method>
+ <method name="GetServerInformation">
+ <arg name="return_name" type="s" direction="out"/>
+ <arg name="return_vendor" type="s" direction="out"/>
+ <arg name="return_version" type="s" direction="out"/>
+ <arg name="return_spec_version" type="s" direction="out"/>
+ </method>
+ <method name="GetServerInfo">
+ <arg name="return_name" type="s" direction="out"/>
+ <arg name="return_vendor" type="s" direction="out"/>
+ <arg name="return_version" type="s" direction="out"/>
+ </method>
+ <signal name="NotificationClosed">
+ <arg name="id" type="u" direction="out"/>
+ <arg name="reason" type="u" direction="out"/>
+ </signal>
+ <signal name="ActionInvoked">
+ <arg name="id" type="u" direction="out"/>
+ <arg name="action_key" type="s" direction="out"/>
+ </signal>
+ </interface>
+ </node>]=]
+ return "s", xml
+ end
+end)
+
+-- listen for dbus notification requests
+capi.dbus.request_name("session", "org.freedesktop.Notifications")
+
+return dbus
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/naughty/init.lua b/lib/naughty/init.lua
new file mode 100644
index 0000000..71dee9f
--- /dev/null
+++ b/lib/naughty/init.lua
@@ -0,0 +1,14 @@
+---------------------------------------------------------------------------
+-- @author Uli Schlachter &lt;psychon@znc.in&gt;
+-- @copyright 2014 Uli Schlachter
+-- @module naughty
+---------------------------------------------------------------------------
+
+local naughty = require("naughty.core")
+if dbus then
+ naughty.dbus = require("naughty.dbus")
+end
+
+return naughty
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/shifty b/lib/shifty
new file mode 160000
+Subproject 8ca24ad1bfde153a9902209aa6b0027145541eb
diff --git a/lib/shifty.lua b/lib/shifty.lua
new file mode 100644
index 0000000..9320295
--- /dev/null
+++ b/lib/shifty.lua
@@ -0,0 +1,1138 @@
+--- Shifty: Dynamic tagging library, version for awesome v3.5
+-- @author koniu &lt;gkusnierz@gmail.com&gt;
+-- @author resixian (aka bioe007) &lt;resixian@gmail.com&gt;
+-- @author cdump &lt;andreevmaxim@gmail.com&gt;
+--
+-- https://github.com/cdump/awesome-shifty
+-- http://awesome.naquadah.org/wiki/index.php?title=Shifty
+
+-- environment
+local type = type
+local ipairs = ipairs
+local table = table
+local string = string
+local beautiful = require("beautiful")
+local awful = require("awful")
+local wibox = require("wibox")
+local pairs = pairs
+local io = io
+local math = math
+local tonumber = tonumber
+local dbg= dbg
+local capi = {
+ client = client,
+ tag = tag,
+ screen = screen,
+ button = button,
+ mouse = mouse,
+ root = root,
+ timer = timer
+}
+
+local shifty = {}
+
+-- variables
+shifty.config = {}
+shifty.config.tags = {}
+shifty.config.apps = {}
+shifty.config.defaults = {}
+shifty.config.float_bars = false
+shifty.config.guess_name = true
+shifty.config.guess_position = true
+shifty.config.remember_index = true
+shifty.config.sloppy = true
+shifty.config.default_name = "new"
+shifty.config.clientkeys = {}
+shifty.config.globalkeys = nil
+shifty.config.layouts = {}
+shifty.config.prompt_sources = {
+ "config_tags",
+ "config_apps",
+ "existing",
+ "history"
+}
+shifty.config.prompt_matchers = {
+ "^",
+ ":",
+ ""
+}
+shifty.config.delete_deserted = true
+
+local matchp = ""
+local index_cache = {}
+for i = 1, capi.screen.count() do index_cache[i] = {} end
+
+--name2tags: matches string 'name' to tag objects
+-- @param name : tag name to find
+-- @param scr : screen to look for tags on
+-- @return table of tag objects or nil
+function name2tags(name, scr)
+ local ret = {}
+ local a, b = scr or 1, scr or capi.screen.count()
+ for s = a, b do
+ for i, t in ipairs(awful.tag.gettags(s)) do
+ if name == t.name then
+ table.insert(ret, t)
+ end
+ end
+ end
+ if #ret > 0 then return ret end
+end
+
+function name2tag(name, scr, idx)
+ local ts = name2tags(name, scr)
+ if ts then return ts[idx or 1] end
+end
+
+--tag2index: finds index of a tag object
+-- @param scr : screen number to look for tag on
+-- @param tag : the tag object to find
+-- @return the index [or zero] or end of the list
+function tag2index(scr, tag)
+ for i, t in ipairs(awful.tag.gettags(scr)) do
+ if t == tag then return i end
+ end
+end
+
+--rename
+--@param tag: tag object to be renamed
+--@param prefix: if any prefix is to be added
+--@param no_selectall:
+function shifty.rename(tag, prefix, no_selectall)
+ local theme = beautiful.get()
+ local t = tag or awful.tag.selected(capi.mouse.screen)
+
+ if t == nil then return end
+
+ local scr = awful.tag.getscreen(t)
+ local bg = nil
+ local fg = nil
+ local text = prefix or t.name
+ local before = t.name
+
+ if t == awful.tag.selected(scr) then
+ bg = theme.bg_focus or '#535d6c'
+ fg = theme.fg_urgent or '#ffffff'
+ else
+ bg = theme.bg_normal or '#222222'
+ fg = theme.fg_urgent or '#ffffff'
+ end
+
+ local tag_index = tag2index(scr, t)
+ -- Access to textbox widget in taglist
+ local tb_widget = shifty.taglist[scr].widgets[tag_index].widget.widgets[2].widget
+ awful.prompt.run({
+ fg_cursor = fg, bg_cursor = bg, ul_cursor = "single",
+ text = text, selectall = not no_selectall},
+ tb_widget,
+ function (name) if name:len() > 0 then t.name = name; end end,
+ completion,
+ awful.util.getdir("cache") .. "/history_tags",
+ nil,
+ function ()
+ if t.name == before then
+ if awful.tag.getproperty(t, "initial") then shifty.del(t) end
+ else
+ awful.tag.setproperty(t, "initial", true)
+ set(t)
+ end
+ tagkeys(capi.screen[scr])
+ t:emit_signal("property::name")
+ end
+ )
+end
+
+--send: moves client to tag[idx]
+-- maybe this isn't needed here in shifty?
+-- @param idx the tag number to send a client to
+function send(idx)
+ local scr = capi.client.focus.screen or capi.mouse.screen
+ local sel = awful.tag.selected(scr)
+ local sel_idx = tag2index(scr, sel)
+ local tags = awful.tag.gettags(scr)
+ local target = awful.util.cycle(#tags, sel_idx + idx)
+ awful.client.movetotag(tags[target], capi.client.focus)
+ awful.tag.viewonly(tags[target])
+end
+
+function shifty.send_next() send(1) end
+function shifty.send_prev() send(-1) end
+
+--pos2idx: translate shifty position to tag index
+--@param pos: position (an integer)
+--@param scr: screen number
+function pos2idx(pos, scr)
+ local v = 1
+ if pos and scr then
+ local tags = awful.tag.gettags(scr)
+ for i = #tags , 1, -1 do
+ local t = tags[i]
+ if awful.tag.getproperty(t, "position") and
+ awful.tag.getproperty(t, "position") <= pos then
+ v = i + 1
+ break
+ end
+ end
+ end
+ return v
+end
+
+--select : helper function chooses the first non-nil argument
+--@param args - table of arguments
+local function select(args)
+ for i, a in pairs(args) do
+ if a ~= nil then
+ return a
+ end
+ end
+end
+
+--tagtoscr : move an entire tag to another screen
+--
+--@param scr : the screen to move tag to
+--@param t : the tag to be moved [awful.tag.selected()]
+--@return the tag
+function shifty.tagtoscr(scr, t)
+ -- break if called with an invalid screen number
+ if not scr or scr < 1 or scr > capi.screen.count() then return end
+ -- tag to move
+ local otag = t or awful.tag.selected()
+
+ awful.tag.setscreen(otag, scr)
+ -- set screen and then reset tag to order properly
+ if #otag:clients() > 0 then
+ for _ , c in ipairs(otag:clients()) do
+ if not c.sticky then
+ c.screen = scr
+ c:tags({otag})
+ else
+ awful.client.toggletag(otag, c)
+ end
+ end
+ end
+ return otag
+end
+
+--set : set a tags properties
+--@param t: the tag
+--@param args : a table of optional (?) tag properties
+--@return t - the tag object
+function set(t, args)
+ if not t then return end
+ if not args then args = {} end
+
+ -- set the name
+ t.name = args.name or t.name
+
+ -- attempt to load preset on initial run
+ local preset = (awful.tag.getproperty(t, "initial") and
+ shifty.config.tags[t.name]) or {}
+
+ -- pick screen and get its tag table
+ local scr = args.screen or
+ (not awful.tag.getscreen(t) and awful.tag.getscreen(preset)) or
+ awful.tag.getscreen(t) or
+ capi.mouse.screen
+
+ local clientstomove = nil
+ if scr > capi.screen.count() then scr = capi.screen.count() end
+ if awful.tag.getscreen(t) and scr ~= awful.tag.getscreen(t) then
+ shifty.tagtoscr(scr, t)
+ awful.tag.setscreen(t, nil)
+ end
+ local tags = awful.tag.gettags(scr)
+
+ -- try to guess position from the name
+ local guessed_position = nil
+ if not (args.position or preset.position) and shifty.config.guess_position then
+ local num = t.name:find('^[1-9]')
+ if num then guessed_position = tonumber(t.name:sub(1, 1)) end
+ end
+
+ -- allow preset.layout to be a table to provide a different layout per
+ -- screen for a given tag
+ local preset_layout = preset.layout
+ if preset_layout and preset_layout[scr] then
+ preset_layout = preset.layout[scr]
+ end
+
+ -- select from args, preset, getproperty,
+ -- config.defaults.configs or defaults
+ local props = {
+ layout = select{args.layout, preset_layout,
+ awful.tag.getproperty(t, "layout"),
+ shifty.config.defaults.layout, awful.layout.suit.tile},
+ mwfact = select{args.mwfact, preset.mwfact,
+ awful.tag.getproperty(t, "mwfact"),
+ shifty.config.defaults.mwfact, 0.55},
+ nmaster = select{args.nmaster, preset.nmaster,
+ awful.tag.getproperty(t, "nmaster"),
+ shifty.config.defaults.nmaster, 1},
+ ncol = select{args.ncol, preset.ncol,
+ awful.tag.getproperty(t, "ncol"),
+ shifty.config.defaults.ncol, 1},
+ matched = select{args.matched, awful.tag.getproperty(t, "matched")},
+ exclusive = select{args.exclusive, preset.exclusive,
+ awful.tag.getproperty(t, "exclusive"),
+ shifty.config.defaults.exclusive},
+ persist = select{args.persist, preset.persist,
+ awful.tag.getproperty(t, "persist"),
+ shifty.config.defaults.persist},
+ nopopup = select{args.nopopup, preset.nopopup,
+ awful.tag.getproperty(t, "nopopup"),
+ shifty.config.defaults.nopopup},
+ leave_kills = select{args.leave_kills, preset.leave_kills,
+ awful.tag.getproperty(t, "leave_kills"),
+ shifty.config.defaults.leave_kills},
+ max_clients = select{args.max_clients, preset.max_clients,
+ awful.tag.getproperty(t, "max_clients"),
+ shifty.config.defaults.max_clients},
+ position = select{args.position, preset.position, guessed_position,
+ awful.tag.getproperty(t, "position")},
+ icon = select{args.icon and args.icon,
+ preset.icon and preset.icon,
+ awful.tag.getproperty(t, "icon"),
+ shifty.config.defaults.icon and shifty.config.defaults.icon},
+ icon_only = select{args.icon_only, preset.icon_only,
+ awful.tag.getproperty(t, "icon_only"),
+ shifty.config.defaults.icon_only},
+ sweep_delay = select{args.sweep_delay, preset.sweep_delay,
+ awful.tag.getproperty(t, "sweep_delay"),
+ shifty.config.defaults.sweep_delay},
+ overload_keys = select{args.overload_keys, preset.overload_keys,
+ awful.tag.getproperty(t, "overload_keys"),
+ shifty.config.defaults.overload_keys},
+ }
+
+ -- get layout by name if given as string
+ if type(props.layout) == "string" then
+ props.layout = getlayout(props.layout)
+ end
+
+ -- set keys
+ if args.keys or preset.keys then
+ local keys = awful.util.table.join(shifty.config.globalkeys,
+ args.keys or preset.keys)
+ if props.overload_keys then
+ props.keys = keys
+ else
+ props.keys = squash_keys(keys)
+ end
+ end
+
+ -- calculate desired taglist index
+ local index = args.index or preset.index or shifty.config.defaults.index
+ local rel_index = args.rel_index or
+ preset.rel_index or
+ shifty.config.defaults.rel_index
+ local sel = awful.tag.selected(scr)
+ --TODO: what happens with rel_idx if no tags selected
+ local sel_idx = (sel and tag2index(scr, sel)) or 0
+ local t_idx = tag2index(scr, t)
+ local limit = (not t_idx and #tags + 1) or #tags
+ local idx = nil
+
+ if rel_index then
+ idx = awful.util.cycle(limit, (t_idx or sel_idx) + rel_index)
+ elseif index then
+ idx = awful.util.cycle(limit, index)
+ elseif props.position then
+ idx = pos2idx(props.position, scr)
+ if t_idx and t_idx < idx then idx = idx - 1 end
+ elseif shifty.config.remember_index and index_cache[scr][t.name] then
+ idx = math.min(index_cache[scr][t.name], #tags+1)
+ elseif not t_idx then
+ idx = #tags + 1
+ end
+
+ -- if we have a new index, remove from old index and insert
+ if idx then
+ if t_idx then table.remove(tags, t_idx) end
+ table.insert(tags, idx, t)
+ index_cache[scr][t.name] = idx
+ end
+
+ -- set tag properties and push the new tag table
+ for i, tmp_tag in ipairs(tags) do
+ awful.tag.setscreen(tmp_tag, scr)
+ awful.tag.setproperty(tmp_tag, "index", i)
+ end
+ for prop, val in pairs(props) do awful.tag.setproperty(t, prop, val) end
+
+ -- execute run/spawn
+ if awful.tag.getproperty(t, "initial") then
+ local spawn = args.spawn or preset.spawn or shifty.config.defaults.spawn
+ local run = args.run or preset.run or shifty.config.defaults.run
+ if spawn and args.matched ~= true then
+ awful.util.spawn_with_shell(spawn, scr)
+ end
+ if run then run(t) end
+ awful.tag.setproperty(t, "initial", nil)
+ end
+
+
+ return t
+end
+
+function shift_next() set(awful.tag.selected(), {rel_index = 1}) end
+function shift_prev() set(awful.tag.selected(), {rel_index = -1}) end
+
+--add : adds a tag
+--@param args: table of optional arguments
+function shifty.add(args)
+ if not args then args = {} end
+ local name = args.name or " "
+
+ -- initialize a new tag object and its data structure
+ local t = awful.tag.add(name, { initial = true })
+
+
+ -- apply tag settings
+ set(t, args)
+
+ -- unless forbidden or if first tag on the screen, show the tag
+ if not (awful.tag.getproperty(t, "nopopup") or args.noswitch) or
+ #awful.tag.gettags(awful.tag.getscreen(t)) == 1 then
+ awful.tag.viewonly(t)
+ end
+
+ -- get the name or rename
+ if args.name then
+ t.name = args.name
+ else
+ -- FIXME: hack to delay rename for un-named tags for
+ -- tackling taglist refresh which disabled prompt
+ -- from being rendered until input
+ awful.tag.setproperty(t, "initial", true)
+ local f
+ local tmr
+ if args.position then
+ f = function() shifty.rename(t, args.rename, true); tmr:stop() end
+ else
+ f = function() shifty.rename(t); tmr:stop() end
+ end
+ tmr = capi.timer({timeout = 0.01})
+ tmr:connect_signal("timeout", f)
+ tmr:start()
+ end
+
+ return t
+end
+
+--del : delete a tag
+--@param tag : the tag to be deleted [current tag]
+function shifty.del(tag)
+ local scr = (tag and awful.tag.getscreen(tag)) or capi.mouse.screen or 1
+ local tags = awful.tag.gettags(scr)
+ local sel = awful.tag.selected(scr)
+ local t = tag or sel
+ local idx = tag2index(scr, t)
+
+ -- return if tag not empty (except sticky)
+ local clients = t:clients()
+ local sticky = 0
+ for i, c in ipairs(clients) do
+ if c.sticky then sticky = sticky + 1 end
+ end
+ if #clients > sticky then return end
+
+ -- store index for later
+ index_cache[scr][t.name] = idx
+
+ -- remove tag
+ awful.tag.delete(t)
+
+ -- if the current tag is being deleted, restore from history
+ if t == sel and #tags > 1 then
+ awful.tag.history.restore(scr, 1)
+ -- this is supposed to cycle if history is invalid?
+ -- e.g. if many tags are deleted in a row
+ if not awful.tag.selected(scr) then
+ awful.tag.viewonly(tags[awful.util.cycle(#tags, idx - 1)])
+ end
+ end
+
+ -- FIXME: what is this for??
+ if capi.client.focus then capi.client.focus:raise() end
+end
+
+--is_client_tagged : replicate behavior in tag.c - returns true if the
+--given client is tagged with the given tag
+function is_client_tagged(tag, client)
+ for i, c in ipairs(tag:clients()) do
+ if c == client then
+ return true
+ end
+ end
+ return false
+end
+
+--match : handles app->tag matching, a replacement for the manage hook in
+-- rc.lua
+--@param c : client to be matched
+function match(c, startup)
+ local nopopup, intrusive, nofocus, run, slave
+ local wfact, struts, geom, float
+ local target_tag_names, target_tags = {}, {}
+ local typ = c.type
+ local cls = c.class
+ local inst = c.instance
+ local role = c.role
+ local name = c.name
+ local keys = shifty.config.clientkeys or c:keys() or {}
+ local target_screen = capi.mouse.screen
+
+ c.border_color = beautiful.border_normal
+ c.border_width = beautiful.border_width
+
+ -- try matching client to config.apps
+ for i, a in ipairs(shifty.config.apps) do
+ if a.match then
+ local matched = false
+ -- match only class
+ if not matched and cls and a.match.class then
+ for k, w in ipairs(a.match.class) do
+ matched = cls:find(w)
+ if matched then
+ break
+ end
+ end
+ end
+ -- match only instance
+ if not matched and inst and a.match.instance then
+ for k, w in ipairs(a.match.instance) do
+ matched = inst:find(w)
+ if matched then
+ break
+ end
+ end
+ end
+ -- match only name
+ if not matched and name and a.match.name then
+ for k, w in ipairs(a.match.name) do
+ matched = name:find(w)
+ if matched then
+ break
+ end
+ end
+ end
+ -- match only role
+ if not matched and role and a.match.role then
+ for k, w in ipairs(a.match.role) do
+ matched = role:find(w)
+ if matched then
+ break
+ end
+ end
+ end
+ -- match only type
+ if not matched and typ and a.match.type then
+ for k, w in ipairs(a.match.type) do
+ matched = typ:find(w)
+ if matched then
+ break
+ end
+ end
+ end
+ -- check everything else against all attributes
+ if not matched then
+ for k, w in ipairs(a.match) do
+ matched = (cls and cls:find(w)) or
+ (inst and inst:find(w)) or
+ (name and name:find(w)) or
+ (role and role:find(w)) or
+ (typ and typ:find(w))
+ if matched then
+ break
+ end
+ end
+ end
+ -- set attributes
+ if matched then
+ if a.screen then target_screen = a.screen end
+ if a.tag then
+ if type(a.tag) == "string" then
+ target_tag_names = {a.tag}
+ else
+ target_tag_names = a.tag
+ end
+ end
+ if a.startup and startup then
+ a = awful.util.table.join(a, a.startup)
+ end
+ if a.geometry ~=nil then
+ geom = {x = a.geometry[1],
+ y = a.geometry[2],
+ width = a.geometry[3],
+ height = a.geometry[4]}
+ end
+ if a.float ~= nil then float = a.float end
+ if a.slave ~=nil then slave = a.slave end
+ if a.border_width ~= nil then
+ c.border_width = a.border_width
+ end
+ if a.nopopup ~=nil then nopopup = a.nopopup end
+ if a.intrusive ~=nil then
+ intrusive = a.intrusive
+ end
+ if a.fullscreen ~=nil then
+ c.fullscreen = a.fullscreen
+ end
+ if a.honorsizehints ~=nil then
+ c.size_hints_honor = a.honorsizehints
+ end
+ if a.kill ~=nil then c:kill(); return end
+ if a.ontop ~= nil then c.ontop = a.ontop end
+ if a.above ~= nil then c.above = a.above end
+ if a.below ~= nil then c.below = a.below end
+ if a.buttons ~= nil then
+ c:buttons(a.buttons)
+ end
+ if a.nofocus ~= nil then nofocus = a.nofocus end
+ if a.keys ~= nil then
+ keys = awful.util.table.join(keys, a.keys)
+ end
+ if a.hidden ~= nil then c.hidden = a.hidden end
+ if a.minimized ~= nil then
+ c.minimized = a.minimized
+ end
+ if a.dockable ~= nil then
+ awful.client.dockable.set(c, a.dockable)
+ end
+ if a.urgent ~= nil then
+ c.urgent = a.urgent
+ end
+ if a.opacity ~= nil then
+ c.opacity = a.opacity
+ end
+ if a.run ~= nil then run = a.run end
+ if a.sticky ~= nil then c.sticky = a.sticky end
+ if a.wfact ~= nil then wfact = a.wfact end
+ if a.struts then struts = a.struts end
+ if a.skip_taskbar ~= nil then
+ c.skip_taskbar = a.skip_taskbar
+ end
+ if a.props then
+ for kk, vv in pairs(a.props) do
+ awful.client.property.set(c, kk, vv)
+ end
+ end
+ end
+ end
+ end
+
+ -- set key bindings
+ c:keys(keys)
+
+ -- Add titlebars to all clients when the float, remove when they are
+ -- tiled.
+ if shifty.config.float_bars then
+ shifty.create_titlebar(c)
+
+ c:connect_signal("property::floating", function(c)
+ if awful.client.floating.get(c) then
+ awful.titlebar(c)
+ else
+ awful.titlebar(c, { size = 0 })
+ end
+ awful.placement.no_offscreen(c)
+ end)
+ end
+
+ -- set properties of floating clients
+ if float ~= nil then
+ awful.client.floating.set(c, float)
+ awful.placement.no_offscreen(c)
+ end
+
+ local sel = awful.tag.selectedlist(target_screen)
+ if not target_tag_names or #target_tag_names == 0 then
+ -- if not matched to some names try putting
+ -- client in c.transient_for or current tags
+ if c.transient_for then
+ target_tags = c.transient_for:tags()
+ elseif #sel > 0 then
+ for i, t in ipairs(sel) do
+ local mc = awful.tag.getproperty(t, "max_clients")
+ if intrusive or
+ not (awful.tag.getproperty(t, "exclusive") or
+ (mc and mc >= #t:clients())) then
+ table.insert(target_tags, t)
+ end
+ end
+ end
+ end
+
+ if (not target_tag_names or #target_tag_names == 0) and
+ (not target_tags or #target_tags == 0) then
+ -- if we still don't know any target names/tags guess
+ -- name from class or use default
+ if shifty.config.guess_name and cls then
+ target_tag_names = {cls:lower()}
+ else
+ target_tag_names = {shifty.config.default_name}
+ end
+ end
+
+ if #target_tag_names > 0 and #target_tags == 0 then
+ -- translate target names to tag objects, creating
+ -- missing ones
+ for i, tn in ipairs(target_tag_names) do
+ local res = {}
+ for j, t in ipairs(name2tags(tn, target_screen) or
+ name2tags(tn) or {}) do
+ local mc = awful.tag.getproperty(t, "max_clients")
+ local tagged = is_client_tagged(t, c)
+ if intrusive or
+ not (mc and (((#t:clients() >= mc) and not
+ tagged) or
+ (#t:clients() > mc))) or
+ intrusive then
+ if awful.tag.getscreen(t) == mouse.screen then
+ table.insert(res, t)
+ end
+ end
+ end
+ if #res == 0 then
+ table.insert(target_tags,
+ shifty.add({name = tn,
+ noswitch = true,
+ matched = true}))
+ else
+ target_tags = awful.util.table.join(target_tags, res)
+ end
+ end
+ end
+
+ -- set client's screen/tag if needed
+ target_screen = awful.tag.getscreen(target_tags[1]) or target_screen
+ if c.screen ~= target_screen then c.screen = target_screen end
+ if slave then awful.client.setslave(c) end
+ c:tags(target_tags)
+
+ if wfact then awful.client.setwfact(wfact, c) end
+ if geom then c:geometry(geom) end
+ if struts then c:struts(struts) end
+
+ local showtags = {}
+ local u = nil
+ if #target_tags > 0 and not startup then
+ -- switch or highlight
+ for i, t in ipairs(target_tags) do
+ if not (nopopup or awful.tag.getproperty(t, "nopopup")) then
+ table.insert(showtags, t)
+ elseif not startup then
+ c.urgent = true
+ end
+ end
+ if #showtags > 0 then
+ local ident = false
+ -- iterate selected tags and and see if any targets
+ -- currently selected
+ for kk, vv in pairs(showtags) do
+ for _, tag in pairs(sel) do
+ if tag == vv then
+ ident = true
+ end
+ end
+ end
+ if not ident then
+ awful.tag.viewmore(showtags, c.screen)
+ end
+ end
+ end
+
+ if not (nofocus or c.hidden or c.minimized) then
+ --focus and raise accordingly or lower if supressed
+ if (target and target ~= sel) and
+ (awful.tag.getproperty(target, "nopopup") or nopopup) then
+ awful.client.focus.history.add(c)
+ else
+ capi.client.focus = c
+ end
+ c:raise()
+ else
+ c:lower()
+ end
+
+ if shifty.config.sloppy then
+ -- Enable sloppy focus
+ c:connect_signal("mouse::enter", function(c)
+ if awful.client.focus.filter(c) and
+ awful.layout.get(c.screen) ~= awful.layout.suit.magnifier then
+ capi.client.focus = c
+ end
+ end)
+ end
+
+ -- execute run function if specified
+ if run then run(c, target) end
+
+end
+
+--sweep : hook function that marks tags as used, visited,
+--deserted also handles deleting used and empty tags
+function sweep()
+ for s = 1, capi.screen.count() do
+ for i, t in ipairs(awful.tag.gettags(s)) do
+ local clients = t:clients()
+ local sticky = 0
+ for i, c in ipairs(clients) do
+ if c.sticky then sticky = sticky + 1 end
+ end
+ if #clients == sticky then
+ if awful.tag.getproperty(t, "used") and
+ not awful.tag.getproperty(t, "persist") then
+ if awful.tag.getproperty(t, "deserted") or
+ not awful.tag.getproperty(t, "leave_kills") then
+ local delay = awful.tag.getproperty(t, "sweep_delay")
+ if delay then
+ local tmr
+ local f = function()
+ shifty.del(t); tmr:stop()
+ end
+ tmr = capi.timer({timeout = delay})
+ tmr:connect_signal("timeout", f)
+ tmr:start()
+ else
+ if shifty.config.delete_deserted then
+ shifty.del(t)
+ end
+ end
+ else
+ if awful.tag.getproperty(t, "visited") and
+ not t.selected then
+ awful.tag.setproperty(t, "deserted", true)
+ end
+ end
+ end
+ else
+ awful.tag.setproperty(t, "used", true)
+ end
+ if t.selected then
+ awful.tag.setproperty(t, "visited", true)
+ end
+ end
+ end
+end
+
+--getpos : returns a tag to match position
+-- @param pos : the index to find
+-- @return v : the tag (found or created) at position == 'pos'
+function shifty.getpos(pos, scr_arg)
+ local v = nil
+ local existing = {}
+ local selected = nil
+ local scr = scr_arg or capi.mouse.screen or 1
+
+ -- search for existing tag assigned to pos
+ for i = 1, capi.screen.count() do
+ for j, t in ipairs(awful.tag.gettags(i)) do
+ if awful.tag.getproperty(t, "position") == pos then
+ table.insert(existing, t)
+ if t.selected and i == scr then
+ selected = #existing
+ end
+ end
+ end
+ end
+
+ if #existing > 0 then
+ -- if there is no selected tag on current screen, look for the first one
+ if not selected then
+ for _, tag in pairs(existing) do
+ if awful.tag.getscreen(tag) == scr then return tag end
+ end
+
+ -- no tag found, loop through the other tags
+ selected = #existing
+ end
+
+ -- look for the next unselected tag
+ i = selected
+ repeat
+ i = awful.util.cycle(#existing, i + 1)
+ tag = existing[i]
+
+ if (scr_arg == nil or awful.tag.getscreen(tag) == scr_arg) and not tag.selected then return tag end
+ until i == selected
+
+ -- if the screen is not specified or
+ -- if a selected tag exists on the specified screen
+ -- return the selected tag
+ if scr_arg == nil or awful.tag.getscreen(existing[selected]) == scr then return existing[selected] end
+
+ -- if scr_arg ~= nil and no tag exists on this screen, continue
+ end
+
+ local screens = {}
+ for s = 1, capi.screen.count() do table.insert(screens, s) end
+
+ -- search for preconf with 'pos' on current screen and create it
+ for i, j in pairs(shifty.config.tags) do
+ local tag_scr = j.screen or screens
+ if type(tag_scr) ~= 'table' then tag_scr = {tag_scr} end
+
+ if j.position == pos and awful.util.table.hasitem(tag_scr, scr) then
+ return shifty.add({name = i,
+ position = pos,
+ noswitch = not switch})
+ end
+ end
+
+ -- not existing, not preconfigured
+ return shifty.add({position = pos,
+ rename = pos .. ':',
+ no_selectall = true,
+ noswitch = not switch})
+end
+
+--init : search config.tags for initial set of
+--tags to open
+function shifty.init()
+ local numscr = capi.screen.count()
+
+ local screens = {}
+ for s = 1, capi.screen.count() do table.insert(screens, s) end
+
+ for i, j in pairs(shifty.config.tags) do
+ local scr = j.screen or screens
+ if type(scr) ~= 'table' then
+ scr = {scr}
+ end
+ for _, s in pairs(scr) do
+ if j.init and (s <= numscr) then
+ shifty.add({name = i,
+ persist = true,
+ screen = s,
+ layout = j.layout,
+ mwfact = j.mwfact})
+ end
+ end
+ end
+end
+
+-- Create a titlebar for the given client
+-- By default, make it invisible (size = 0)
+
+function shifty.create_titlebar(c)
+ -- Widgets that are aligned to the left
+ local left_layout = wibox.layout.fixed.horizontal()
+ left_layout:add(awful.titlebar.widget.iconwidget(c))
+
+ -- Widgets that are aligned to the right
+ local right_layout = wibox.layout.fixed.horizontal()
+ right_layout:add(awful.titlebar.widget.floatingbutton(c))
+ right_layout:add(awful.titlebar.widget.maximizedbutton(c))
+ right_layout:add(awful.titlebar.widget.stickybutton(c))
+ right_layout:add(awful.titlebar.widget.ontopbutton(c))
+ right_layout:add(awful.titlebar.widget.closebutton(c))
+
+ -- The title goes in the middle
+ local title = awful.titlebar.widget.titlewidget(c)
+ title: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)
+ ))
+
+ -- Now bring it all together
+ local layout = wibox.layout.align.horizontal()
+ layout:set_left(left_layout)
+ layout:set_right(right_layout)
+ layout:set_middle(title)
+
+ awful.titlebar(c, { size = 0 }):set_widget(layout)
+end
+
+--count : utility function returns the index of a table element
+--FIXME: this is currently used only in remove_dup, so is it really
+--necessary?
+function count(table, element)
+ local v = 0
+ for i, e in pairs(table) do
+ if element == e then v = v + 1 end
+ end
+ return v
+end
+
+--remove_dup : used by shifty.completion when more than one
+--tag at a position exists
+function remove_dup(table)
+ local v = {}
+ for i, entry in ipairs(table) do
+ if count(v, entry) == 0 then v[#v+ 1] = entry end
+ end
+ return v
+end
+
+--completion : prompt completion
+--
+function completion(cmd, cur_pos, ncomp, sources, matchers)
+
+ -- get sources and matches tables
+ sources = sources or shifty.config.prompt_sources
+ matchers = matchers or shifty.config.prompt_matchers
+
+ local get_source = {
+ -- gather names from config.tags
+ config_tags = function()
+ local ret = {}
+ for n, p in pairs(shifty.config.tags) do
+ table.insert(ret, n)
+ end
+ return ret
+ end,
+ -- gather names from config.apps
+ config_apps = function()
+ local ret = {}
+ for i, p in pairs(shifty.config.apps) do
+ if p.tag then
+ if type(p.tag) == "string" then
+ table.insert(ret, p.tag)
+ else
+ ret = awful.util.table.join(ret, p.tag)
+ end
+ end
+ end
+ return ret
+ end,
+ -- gather names from existing tags, starting with the
+ -- current screen
+ existing = function()
+ local ret = {}
+ for i = 1, capi.screen.count() do
+ local s = awful.util.cycle(capi.screen.count(),
+ capi.mouse.screen + i - 1)
+ local tags = awful.tag.gettags(s)
+ for j, t in pairs(tags) do
+ table.insert(ret, t.name)
+ end
+ end
+ return ret
+ end,
+ -- gather names from history
+ history = function()
+ local ret = {}
+ local f = io.open(awful.util.getdir("cache") ..
+ "/history_tags")
+ for name in f:lines() do table.insert(ret, name) end
+ f:close()
+ return ret
+ end,
+ }
+
+ -- if empty, match all
+ if #cmd == 0 or cmd == " " then cmd = "" end
+
+ -- match all up to the cursor if moved or no matchphrase
+ if matchp == "" or
+ cmd:sub(cur_pos, cur_pos+#matchp) ~= matchp then
+ matchp = cmd:sub(1, cur_pos)
+ end
+
+ -- find matching commands
+ local matches = {}
+ for i, src in ipairs(sources) do
+ local source = get_source[src]()
+ for j, matcher in ipairs(matchers) do
+ for k, name in ipairs(source) do
+ if name:find(matcher .. matchp) then
+ table.insert(matches, name)
+ end
+ end
+ end
+ end
+
+ -- no matches
+ if #matches == 0 then return cmd, cur_pos end
+
+ -- remove duplicates
+ matches = remove_dup(matches)
+
+ -- cycle
+ while ncomp > #matches do ncomp = ncomp - #matches end
+
+ -- put cursor at the end of the matched phrase
+ if #matches == 1 then
+ cur_pos = #matches[ncomp] + 1
+ else
+ cur_pos = matches[ncomp]:find(matchp) + #matchp
+ end
+
+ -- return match and position
+ return matches[ncomp], cur_pos
+end
+
+-- tagkeys : hook function that sets keybindings per tag
+function tagkeys(s)
+ local sel = awful.tag.selected(s.index)
+ local keys = awful.tag.getproperty(sel, "keys") or
+ shifty.config.globalkeys
+ if keys and sel.selected then capi.root.keys(keys) end
+end
+
+-- squash_keys: helper function which removes duplicate
+-- keybindings by picking only the last one to be listed in keys
+-- table arg
+function squash_keys(keys)
+ local squashed = {}
+ local ret = {}
+ for i, k in ipairs(keys) do
+ squashed[table.concat(k.modifiers) .. k.key] = k
+ end
+ for i, k in pairs(squashed) do
+ table.insert(ret, k)
+ end
+ return ret
+end
+
+-- getlayout: returns a layout by name
+function getlayout(name)
+ for _, layout in ipairs(shifty.config.layouts) do
+ if awful.layout.getname(layout) == name then
+ return layout
+ end
+ end
+end
+
+-- add signals before using them
+-- Note: these signals are emitted when tag properties
+-- are accessed through awful.tag.setproperty
+capi.tag.add_signal("property::initial")
+capi.tag.add_signal("property::used")
+capi.tag.add_signal("property::visited")
+capi.tag.add_signal("property::deserted")
+capi.tag.add_signal("property::matched")
+capi.tag.add_signal("property::selected")
+capi.tag.add_signal("property::position")
+capi.tag.add_signal("property::exclusive")
+capi.tag.add_signal("property::persist")
+capi.tag.add_signal("property::index")
+capi.tag.add_signal("property::nopopup")
+capi.tag.add_signal("property::leave_kills")
+capi.tag.add_signal("property::max_clients")
+capi.tag.add_signal("property::icon_only")
+capi.tag.add_signal("property::sweep_delay")
+capi.tag.add_signal("property::overload_keys")
+
+-- replace awful's default hook
+capi.client.connect_signal("manage", match)
+capi.client.connect_signal("unmanage", sweep)
+capi.client.disconnect_signal("manage", awful.tag.withcurrent)
+
+for s = 1, capi.screen.count() do
+ awful.tag.attached_connect_signal(s, "property::selected", sweep)
+ awful.tag.attached_connect_signal(s, "tagged", sweep)
+ capi.screen[s]:connect_signal("tag::history::update", tagkeys)
+end
+
+return shifty
+
diff --git a/lib/wibox/container/arcchart.lua b/lib/wibox/container/arcchart.lua
new file mode 100644
index 0000000..3794630
--- /dev/null
+++ b/lib/wibox/container/arcchart.lua
@@ -0,0 +1,566 @@
+---------------------------------------------------------------------------
+--
+-- A circular chart (arc chart).
+--
+-- It can contain a central widget (or not) and display multiple values.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_container_defaults_arcchart.svg)
+--
+-- @author Emmanuel Lepage Vallee &lt;elv1313@gmail.com&gt;
+-- @copyright 2013 Emmanuel Lepage Vallee
+-- @classmod wibox.container.arcchart
+---------------------------------------------------------------------------
+
+local setmetatable = setmetatable
+local base = require("wibox.widget.base")
+local shape = require("gears.shape" )
+local util = require( "awful.util" )
+local color = require( "gears.color" )
+local beautiful = require("beautiful" )
+
+
+local arcchart = { mt = {} }
+
+--- The progressbar border background color.
+-- @beautiful beautiful.arcchart_border_color
+
+--- The progressbar foreground color.
+-- @beautiful beautiful.arcchart_color
+
+--- The progressbar border width.
+-- @beautiful beautiful.arcchart_border_width
+
+--- The padding between the outline and the progressbar.
+-- @beautiful beautiful.arcchart_paddings
+-- @tparam[opt=0] table|number paddings A number or a table
+-- @tparam[opt=0] number paddings.top
+-- @tparam[opt=0] number paddings.bottom
+-- @tparam[opt=0] number paddings.left
+-- @tparam[opt=0] number paddings.right
+
+--- The arc thickness.
+-- @beautiful beautiful.thickness
+-- @param number
+
+local function outline_workarea(width, height)
+ local x, y = 0, 0
+ local size = math.min(width, height)
+
+ return {x=x+(width-size)/2, y=y+(height-size)/2, width=size, height=size}
+end
+
+-- The child widget area
+local function content_workarea(self, width, height)
+ local padding = self._private.paddings or {}
+ local border_width = self:get_border_width() or 0
+ local wa = outline_workarea(width, height)
+ local thickness = math.max(border_width, self:get_thickness() or 5)
+
+ wa.x = wa.x + (padding.left or 0) + thickness + 2*border_width
+ wa.y = wa.y + (padding.top or 0) + thickness + 2*border_width
+ wa.width = wa.width - (padding.left or 0) - (padding.right or 0)
+ - 2*thickness - 4*border_width
+ wa.height = wa.height - (padding.top or 0) - (padding.bottom or 0)
+ - 2*thickness - 4*border_width
+
+ return wa
+end
+
+-- Draw the radial outline and progress
+function arcchart:after_draw_children(_, cr, width, height)
+ cr:restore()
+
+ local values = self:get_values() or {}
+ local border_width = self:get_border_width() or 0
+ local thickness = math.max(border_width, self:get_thickness() or 5)
+
+ local offset = thickness + 2*border_width
+
+ -- Draw a circular background
+ local bg = self:get_bg()
+ if bg then
+ cr:save()
+ cr:translate(offset/2, offset/2)
+ shape.circle(
+ cr,
+ width-offset,
+ height-offset
+ )
+ cr:set_line_width(thickness+2*border_width)
+ cr:set_source(color(bg))
+ cr:stroke()
+ cr:restore()
+ end
+
+ if #values == 0 then
+ return
+ end
+
+ local wa = outline_workarea(width, height)
+ cr:translate(wa.x+border_width/2, wa.y+border_width/2)
+
+
+ -- Get the min and max value
+ --local min_val = self:get_min_value() or 0 --TODO support min_values
+ local max_val = self:get_max_value()
+ local sum = 0
+
+ if not max_val then
+ for _, v in ipairs(values) do
+ sum = sum + v
+ end
+ max_val = sum
+ end
+
+ max_val = math.max(max_val, sum)
+
+ local use_rounded_edges = sum ~= max_val and self:get_rounded_edge()
+
+ -- Fallback to the current foreground color
+ local colors = self:get_colors() or {}
+
+ -- Draw the outline
+ local offset_angle = self:get_start_angle() or math.pi
+ local start_angle, end_angle = offset_angle, offset_angle
+
+ for k, v in ipairs(values) do
+ end_angle = start_angle + (v*2*math.pi) / max_val
+
+ if colors[k] then
+ cr:set_source(color(colors[k]))
+ end
+
+ shape.arc(cr, wa.width-border_width, wa.height-border_width,
+ thickness+border_width, math.pi-end_angle, math.pi-start_angle,
+ (use_rounded_edges and k == 1), (use_rounded_edges and k == #values)
+ )
+
+ cr:fill()
+ start_angle = end_angle
+ end
+
+ if border_width > 0 then
+ local border_color = self:get_border_color()
+
+ cr:set_source(color(border_color))
+ cr:set_line_width(border_width)
+
+ shape.arc(cr, wa.width-border_width, wa.height-border_width,
+ thickness+border_width, math.pi-end_angle, math.pi-offset_angle,
+ use_rounded_edges, use_rounded_edges
+ )
+ cr:stroke()
+ end
+
+end
+
+-- Set the clip
+function arcchart:before_draw_children(_, cr, width, height)
+ cr:save()
+ local wa = content_workarea(self, width, height)
+ cr:translate(wa.x, wa.y)
+ shape.circle(
+ cr,
+ wa.width,
+ wa.height
+ )
+ cr:clip()
+ cr:translate(-wa.x, -wa.y)
+end
+
+-- Layout this layout
+function arcchart:layout(_, width, height)
+ if self._private.widget then
+ local wa = content_workarea(self, width, height)
+
+ return { base.place_widget_at(
+ self._private.widget, wa.x, wa.y, wa.width, wa.height
+ ) }
+ end
+end
+
+-- Fit this layout into the given area
+function arcchart:fit(_, width, height)
+ local size = math.min(width, height)
+ return size, size
+end
+
+--- The widget to wrap in a radial proggressbar.
+-- @property widget
+-- @tparam widget widget The widget
+
+function arcchart:set_widget(widget)
+ if widget then
+ base.check_widget(widget)
+ end
+ self._private.widget = widget
+ self:emit_signal("widget::layout_changed")
+end
+
+--- Get the children elements.
+-- @treturn table The children
+function arcchart: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 arcchart:set_children(children)
+ self._private.widget = children and children[1]
+ self:emit_signal("widget::layout_changed")
+end
+
+--- Reset this layout. The widget will be removed and the rotation reset.
+function arcchart:reset()
+ self:set_widget(nil)
+end
+
+for _,v in ipairs {"left", "right", "top", "bottom"} do
+ arcchart["set_"..v.."_padding"] = function(self, val)
+ self._private.paddings = self._private.paddings or {}
+ self._private.paddings[v] = val
+ self:emit_signal("widget::redraw_needed")
+ self:emit_signal("widget::layout_changed")
+ end
+end
+
+--- The padding between the outline and the progressbar.
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_container_arcchart_paddings.svg)
+--
+-- @property paddings
+-- @tparam[opt=0] table|number paddings A number or a table
+-- @tparam[opt=0] number paddings.top
+-- @tparam[opt=0] number paddings.bottom
+-- @tparam[opt=0] number paddings.left
+-- @tparam[opt=0] number paddings.right
+
+--- The border background color.
+--
+-- @property border_color
+
+--- The border foreground color.
+--
+-- @property color
+
+--- The border width.
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_container_arcchart_border_width.svg)
+--
+-- @property border_width
+-- @tparam[opt=3] number border_width
+
+--- The minimum value.
+-- @property min_value
+
+--- The maximum value.
+-- @property max_value
+
+--- The radial background.
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_container_arcchart_bg.svg)
+--
+-- @property bg
+-- @param color
+-- @see gears.color
+
+--- The value.
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_container_arcchart_value.svg)
+--
+-- @property value
+-- @tparam number value Between min_value and max_value
+-- @see values
+
+--- The values.
+-- The arcchart is designed to display multiple values at once. Each will be
+-- shown in table order.
+--
+-- @property values
+-- @tparam table values An ordered set if values.
+-- @see value
+
+--- If the chart has rounded edges.
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_container_arcchart_rounded_edge.svg)
+--
+-- @property rounded_edge
+-- @param[opt=false] boolean
+
+--- The arc thickness.
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_container_arcchart_thickness.svg)
+--
+-- @property thickness
+-- @param number
+
+--- The (radiant) angle where the first value start.
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_container_arcchart_start_angle.svg)
+--
+-- @property start_angle
+-- @param[opt=math.pi] number A number between 0 and 2*math.pi
+
+for _, prop in ipairs {"border_width", "border_color", "paddings", "colors",
+ "rounded_edge", "bg", "thickness", "values", "min_value", "max_value",
+ "start_angle" } do
+ arcchart["set_"..prop] = function(self, value)
+ self._private[prop] = value
+ self:emit_signal("property::"..prop)
+ self:emit_signal("widget::redraw_needed")
+ end
+ arcchart["get_"..prop] = function(self)
+ return self._private[prop] or beautiful["arcchart_"..prop]
+ end
+end
+
+function arcchart:set_paddings(val)
+ self._private.paddings = type(val) == "number" and {
+ left = val,
+ right = val,
+ top = val,
+ bottom = val,
+ } or val or {}
+ self:emit_signal("property::paddings")
+ self:emit_signal("widget::redraw_needed")
+ self:emit_signal("widget::layout_changed")
+end
+
+function arcchart:set_value(value)
+ self:set_values {value}
+end
+
+--- Returns a new arcchart layout.
+-- @param[opt] widget The widget to display.
+-- @function wibox.container.arcchart
+local function new(widget)
+ local ret = base.make_widget(nil, nil, {
+ enable_properties = true,
+ })
+
+ util.table.crush(ret, arcchart)
+
+ ret:set_widget(widget)
+
+ return ret
+end
+
+function arcchart.mt:__call(...)
+ return new(...)
+end
+
+--Imported documentation
+
+
+--- Get a widex index.
+-- @param widget The widget to look for
+-- @param[opt] recursive Also check sub-widgets
+-- @param[opt] ... Aditional widgets to add at the end of the \"path\"
+-- @return The index
+-- @return The parent layout
+-- @return The path between \"self\" and \"widget\"
+-- @function index
+
+--- Get all direct and indirect children widgets.
+-- This will scan all containers recursively to find widgets
+-- Warning: This method it prone to stack overflow id the widget, or any of its
+-- children, contain (directly or indirectly) itself.
+-- @treturn table The children
+-- @function get_all_children
+
+--- Set a declarative widget hierarchy description.
+-- See [The declarative layout system](../documentation/03-declarative-layout.md.html)
+-- @param args An array containing the widgets disposition
+-- @function setup
+
+--- Force a widget height.
+-- @property forced_height
+-- @tparam number|nil height The height (`nil` for automatic)
+
+--- Force a widget width.
+-- @property forced_width
+-- @tparam number|nil width The width (`nil` for automatic)
+
+--- The widget opacity (transparency).
+-- @property opacity
+-- @tparam[opt=1] number opacity The opacity (between 0 and 1)
+
+--- The widget visibility.
+-- @property visible
+-- @param boolean
+
+--- Set/get a widget's buttons.
+-- @param _buttons The table of buttons that should bind to the widget.
+-- @function buttons
+
+--- Emit a signal and ensure all parent widgets in the hierarchies also
+-- forward the signal. This is useful to track signals when there is a dynamic
+-- set of containers and layouts wrapping the widget.
+-- @tparam string signal_name
+-- @param ... Other arguments
+-- @function emit_signal_recursive
+
+--- When the layout (size) change.
+-- This signal is emitted when the previous results of `:layout()` and `:fit()`
+-- are no longer valid. Unless this signal is emitted, `:layout()` and `:fit()`
+-- must return the same result when called with the same arguments.
+-- @signal widget::layout_changed
+-- @see widget::redraw_needed
+
+--- When the widget content changed.
+-- This signal is emitted when the content of the widget changes. The widget will
+-- be redrawn, it is not re-layouted. Put differently, it is assumed that
+-- `:layout()` and `:fit()` would still return the same results as before.
+-- @signal widget::redraw_needed
+-- @see widget::layout_changed
+
+--- When a mouse button is pressed over the widget.
+-- @signal button::press
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When a mouse button is released over the widget.
+-- @signal button::release
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse enter a widget.
+-- @signal mouse::enter
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse leave a widget.
+-- @signal mouse::leave
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+
+--Imported documentation
+
+
+--- Disconnect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback that should be disconnected
+-- @function disconnect_signal
+
+--- Emit a signal.
+--
+-- @tparam string name The name of the signal
+-- @param ... Extra arguments for the callback functions. Each connected
+-- function receives the object as first argument and then any extra arguments
+-- that are given to emit_signal()
+-- @function emit_signal
+
+--- Connect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function connect_signal
+
+--- Connect to a signal weakly. This allows the callback function to be garbage
+-- collected and automatically disconnects the signal when that happens.
+--
+-- **Warning:**
+-- Only use this function if you really, really, really know what you
+-- are doing.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function weak_connect_signal
+
+
+return setmetatable(arcchart, arcchart.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/wibox/container/background.lua b/lib/wibox/container/background.lua
new file mode 100644
index 0000000..2fb88c3
--- /dev/null
+++ b/lib/wibox/container/background.lua
@@ -0,0 +1,626 @@
+---------------------------------------------------------------------------
+-- A container capable of changing the background color, foreground color
+-- widget shape.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_container_defaults_background.svg)
+--
+-- @author Uli Schlachter
+-- @copyright 2010 Uli Schlachter
+-- @classmod wibox.container.background
+---------------------------------------------------------------------------
+
+local base = require("wibox.widget.base")
+local color = require("gears.color")
+local surface = require("gears.surface")
+local beautiful = require("beautiful")
+local cairo = require("lgi").cairo
+local util = require("awful.util")
+local setmetatable = setmetatable
+local type = type
+local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
+
+local background = { mt = {} }
+
+-- Draw this widget
+function background:draw(context, cr, width, height)
+ if not self._private.widget or not self._private.widget:get_visible() then
+ return
+ end
+
+ -- Keep the shape path in case there is a border
+ self._private.path = nil
+
+ if self._private.shape then
+ -- Only add the offset if there is something to draw
+ local offset = ((self._private.shape_border_width and self._private.shape_border_color)
+ and self._private.shape_border_width or 0) / 2
+
+ cr:translate(offset, offset)
+ self._private.shape(cr, width - 2*offset, height - 2*offset, unpack(self._private.shape_args or {}))
+ cr:translate(-offset, -offset)
+ self._private.path = cr:copy_path()
+ cr:clip()
+ end
+
+ if self._private.background then
+ cr:set_source(self._private.background)
+ cr:paint()
+ end
+ if self._private.bgimage then
+ if type(self._private.bgimage) == "function" then
+ self._private.bgimage(context, cr, width, height,unpack(self._private.bgimage_args))
+ else
+ local pattern = cairo.Pattern.create_for_surface(self._private.bgimage)
+ cr:set_source(pattern)
+ cr:paint()
+ end
+ end
+
+end
+
+-- Draw the border
+function background:after_draw_children(_, cr)
+ -- Draw the border
+ if self._private.path and self._private.shape_border_width and self._private.shape_border_width > 0 then
+ cr:append_path(self._private.path)
+ cr:set_source(color(self._private.shape_border_color or self._private.foreground or beautiful.fg_normal))
+
+ cr:set_line_width(self._private.shape_border_width)
+ cr:stroke()
+ self._private.path = nil
+ end
+end
+
+-- Prepare drawing the children of this widget
+function background:before_draw_children(_, cr)
+ if self._private.foreground then
+ cr:set_source(self._private.foreground)
+ end
+
+ -- Clip the shape
+ if self._private.path and self._private.shape_clip then
+ cr:append_path(self._private.path)
+ cr:clip()
+ end
+end
+
+-- Layout this widget
+function background:layout(_, width, height)
+ if self._private.widget then
+ return { base.place_widget_at(self._private.widget, 0, 0, width, height) }
+ end
+end
+
+-- Fit this widget into the given area
+function background:fit(context, width, height)
+ if not self._private.widget then
+ return 0, 0
+ end
+
+ return base.fit_widget(self, context, self._private.widget, width, height)
+end
+
+--- The widget displayed in the background widget.
+-- @property widget
+-- @tparam widget widget The widget to be disaplayed inside of the background
+-- area
+
+function background:set_widget(widget)
+ if widget then
+ base.check_widget(widget)
+ end
+ self._private.widget = widget
+ self:emit_signal("widget::layout_changed")
+end
+
+function background:get_widget()
+ return self._private.widget
+end
+
+-- Get children element
+-- @treturn table The children
+function background: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 background:set_children(children)
+ self:set_widget(children[1])
+end
+
+--- The background color/pattern/gradient to use.
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_container_background_bg.svg)
+--
+-- @usage
+--local text_widget = {
+-- text = 'Hello world!',
+-- widget = wibox.widget.textbox
+--}
+--parent : setup {
+-- {
+-- text_widget,
+-- bg = '#ff0000',
+-- widget = wibox.container.background
+-- },
+-- {
+-- text_widget,
+-- bg = '#00ff00',
+-- widget = wibox.container.background
+-- },
+-- {
+-- text_widget,
+-- bg = '#0000ff',
+-- widget = wibox.container.background
+-- },
+-- spacing = 10,
+-- layout = wibox.layout.fixed.vertical
+--}
+-- @property bg
+-- @param bg A color string, pattern or gradient
+-- @see gears.color
+
+function background:set_bg(bg)
+ if bg then
+ self._private.background = color(bg)
+ else
+ self._private.background = nil
+ end
+ self:emit_signal("widget::redraw_needed")
+end
+
+function background:get_bg()
+ return self._private.background
+end
+
+--- The foreground (text) color/pattern/gradient to use.
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_container_background_fg.svg)
+--
+-- @usage
+--local text_widget = {
+-- text = 'Hello world!',
+-- widget = wibox.widget.textbox
+--}
+--parent : setup {
+-- {
+-- text_widget,
+-- fg = '#ff0000',
+-- widget = wibox.container.background
+-- },
+-- {
+-- text_widget,
+-- fg = '#00ff00',
+-- widget = wibox.container.background
+-- },
+-- {
+-- text_widget,
+-- fg = '#0000ff',
+-- widget = wibox.container.background
+-- },
+-- spacing = 10,
+-- layout = wibox.layout.fixed.vertical
+--}
+-- @property fg
+-- @param fg A color string, pattern or gradient
+-- @see gears.color
+
+function background:set_fg(fg)
+ if fg then
+ self._private.foreground = color(fg)
+ else
+ self._private.foreground = nil
+ end
+ self:emit_signal("widget::redraw_needed")
+end
+
+function background:get_fg()
+ return self._private.foreground
+end
+
+--- The background shap e.
+--
+-- Use `set_shape` to set additional shape paramaters.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_container_background_shape.svg)
+--
+-- @usage
+--parent : setup {
+-- {
+-- -- Adding a shape without margin may result in cropped output
+-- {
+-- text = 'Hello world!',
+-- widget = wibox.widget.textbox
+-- },
+-- shape = gears.shape.hexagon,
+-- bg = beautiful.bg_normal,
+-- shape_border_color = beautiful.border_color,
+-- shape_border_width = beautiful.border_width,
+-- widget = wibox.container.background
+-- },
+-- {
+-- -- To solve this, use a margin
+-- {
+-- {
+-- text = 'Hello world!',
+-- widget = wibox.widget.textbox
+-- },
+-- left = 10,
+-- right = 10,
+-- top = 3,
+-- bottom = 3,
+-- widget = wibox.container.margin
+-- },
+-- shape = gears.shape.hexagon,
+-- bg = beautiful.bg_normal,
+-- shape_border_color = beautiful.border_color,
+-- shape_border_width = beautiful.border_width,
+-- widget = wibox.container.background
+-- },
+-- spacing = 10,
+-- layout = wibox.layout.fixed.vertical
+--}
+-- @property shape
+-- @param shape A function taking a context, width and height as arguments
+-- @see gears.shape
+-- @see set_shape
+
+--- Set the background shape.
+--
+-- Any other arguments will be passed to the shape function
+-- @param shape A function taking a context, width and height as arguments
+-- @see gears.shape
+-- @see shape
+function background:set_shape(shape, ...)
+ local args = {...}
+
+ if shape == self._private.shape and #args == 0 then return end
+
+ self._private.shape = shape
+ self._private.shape_args = {...}
+ self:emit_signal("widget::redraw_needed")
+end
+
+function background:get_shape()
+ return self._private.shape
+end
+
+--- When a `shape` is set, also draw a border.
+--
+-- See `wibox.container.background.shape` for an usage example.
+-- @property shape_border_width
+-- @tparam number width The border width
+
+function background:set_shape_border_width(width)
+ if self._private.shape_border_width == width then return end
+
+ self._private.shape_border_width = width
+ self:emit_signal("widget::redraw_needed")
+end
+
+function background:get_shape_border_width()
+ return self._private.shape_border_width
+end
+
+--- When a `shape` is set, also draw a border.
+--
+-- See `wibox.container.background.shape` for an usage example.
+-- @property shape_border_color
+-- @param[opt=self._private.foreground] fg The border color, pattern or gradient
+-- @see gears.color
+
+function background:set_shape_border_color(fg)
+ if self._private.shape_border_color == fg then return end
+
+ self._private.shape_border_color = fg
+ self:emit_signal("widget::redraw_needed")
+end
+
+function background:get_shape_border_color()
+ return self._private.shape_border_color
+end
+
+--- When a `shape` is set, make sure nothing is drawn outside of it.
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_container_background_clip.svg)
+--
+-- @usage
+--parent : setup {
+-- {
+-- -- Some content may be outside of the shape
+-- {
+-- text = 'Hello\nworld!',
+-- widget = wibox.widget.textbox
+-- },
+-- shape = gears.shape.circle,
+-- bg = beautiful.bg_normal,
+-- shape_border_color = beautiful.border_color,
+-- widget = wibox.container.background
+-- },
+-- {
+-- -- To solve this, clip the content
+-- {
+-- text = 'Hello\nworld!',
+-- widget = wibox.widget.textbox
+-- },
+-- shape_clip = true,
+-- shape = gears.shape.circle,
+-- bg = beautiful.bg_normal,
+-- shape_border_color = beautiful.border_color,
+-- widget = wibox.container.background
+-- },
+-- spacing = 10,
+-- layout = wibox.layout.fixed.vertical
+--}
+-- @property shape_clip
+-- @tparam boolean value If the shape clip is enable
+
+function background:set_shape_clip(value)
+ if self._private.shape_clip == value then return end
+
+ self._private.shape_clip = value
+ self:emit_signal("widget::redraw_needed")
+end
+
+function background:get_shape_clip()
+ return self._private.shape_clip or false
+end
+
+--- The background image to use
+-- If `image` is a function, it will be called with `(context, cr, width, height)`
+-- as arguments. Any other arguments passed to this method will be appended.
+-- @property bgimage
+-- @param image A background image or a function
+-- @see gears.surface
+
+function background:set_bgimage(image, ...)
+ self._private.bgimage = type(image) == "function" and image or surface.load(image)
+ self._private.bgimage_args = {...}
+ self:emit_signal("widget::redraw_needed")
+end
+
+function background:get_bgimage()
+ return self._private.bgimage
+end
+
+--- Returns a new background container.
+--
+-- A background container applies a background and foreground color
+-- to another widget.
+-- @param[opt] widget The widget to display.
+-- @param[opt] bg The background to use for that widget.
+-- @param[opt] shape A `gears.shape` compatible shape function
+-- @function wibox.container.background
+local function new(widget, bg, shape)
+ local ret = base.make_widget(nil, nil, {
+ enable_properties = true,
+ })
+
+ util.table.crush(ret, background, true)
+
+ ret._private.shape = shape
+
+ ret:set_widget(widget)
+ ret:set_bg(bg)
+
+ return ret
+end
+
+function background.mt:__call(...)
+ return new(...)
+end
+
+--Imported documentation
+
+
+--- Get a widex index.
+-- @param widget The widget to look for
+-- @param[opt] recursive Also check sub-widgets
+-- @param[opt] ... Aditional widgets to add at the end of the \"path\"
+-- @return The index
+-- @return The parent layout
+-- @return The path between \"self\" and \"widget\"
+-- @function index
+
+--- Get all direct and indirect children widgets.
+-- This will scan all containers recursively to find widgets
+-- Warning: This method it prone to stack overflow id the widget, or any of its
+-- children, contain (directly or indirectly) itself.
+-- @treturn table The children
+-- @function get_all_children
+
+--- Set a declarative widget hierarchy description.
+-- See [The declarative layout system](../documentation/03-declarative-layout.md.html)
+-- @param args An array containing the widgets disposition
+-- @function setup
+
+--- Force a widget height.
+-- @property forced_height
+-- @tparam number|nil height The height (`nil` for automatic)
+
+--- Force a widget width.
+-- @property forced_width
+-- @tparam number|nil width The width (`nil` for automatic)
+
+--- The widget opacity (transparency).
+-- @property opacity
+-- @tparam[opt=1] number opacity The opacity (between 0 and 1)
+
+--- The widget visibility.
+-- @property visible
+-- @param boolean
+
+--- Set/get a widget's buttons.
+-- @param _buttons The table of buttons that should bind to the widget.
+-- @function buttons
+
+--- Emit a signal and ensure all parent widgets in the hierarchies also
+-- forward the signal. This is useful to track signals when there is a dynamic
+-- set of containers and layouts wrapping the widget.
+-- @tparam string signal_name
+-- @param ... Other arguments
+-- @function emit_signal_recursive
+
+--- When the layout (size) change.
+-- This signal is emitted when the previous results of `:layout()` and `:fit()`
+-- are no longer valid. Unless this signal is emitted, `:layout()` and `:fit()`
+-- must return the same result when called with the same arguments.
+-- @signal widget::layout_changed
+-- @see widget::redraw_needed
+
+--- When the widget content changed.
+-- This signal is emitted when the content of the widget changes. The widget will
+-- be redrawn, it is not re-layouted. Put differently, it is assumed that
+-- `:layout()` and `:fit()` would still return the same results as before.
+-- @signal widget::redraw_needed
+-- @see widget::layout_changed
+
+--- When a mouse button is pressed over the widget.
+-- @signal button::press
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When a mouse button is released over the widget.
+-- @signal button::release
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse enter a widget.
+-- @signal mouse::enter
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse leave a widget.
+-- @signal mouse::leave
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+
+--Imported documentation
+
+
+--- Disconnect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback that should be disconnected
+-- @function disconnect_signal
+
+--- Emit a signal.
+--
+-- @tparam string name The name of the signal
+-- @param ... Extra arguments for the callback functions. Each connected
+-- function receives the object as first argument and then any extra arguments
+-- that are given to emit_signal()
+-- @function emit_signal
+
+--- Connect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function connect_signal
+
+--- Connect to a signal weakly. This allows the callback function to be garbage
+-- collected and automatically disconnects the signal when that happens.
+--
+-- **Warning:**
+-- Only use this function if you really, really, really know what you
+-- are doing.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function weak_connect_signal
+
+
+return setmetatable(background, background.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/wibox/container/constraint.lua b/lib/wibox/container/constraint.lua
new file mode 100644
index 0000000..a210387
--- /dev/null
+++ b/lib/wibox/container/constraint.lua
@@ -0,0 +1,371 @@
+---------------------------------------------------------------------------
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_container_defaults_constraint.svg)
+--
+-- @author Lukáš Hrázký
+-- @copyright 2012 Lukáš Hrázký
+-- @classmod wibox.container.constraint
+---------------------------------------------------------------------------
+
+local setmetatable = setmetatable
+local base = require("wibox.widget.base")
+local util = require("awful.util")
+local math = math
+
+local constraint = { mt = {} }
+
+-- Layout a constraint layout
+function constraint:layout(_, width, height)
+ if self._private.widget then
+ return { base.place_widget_at(self._private.widget, 0, 0, width, height) }
+ end
+end
+
+-- Fit a constraint layout into the given space
+function constraint:fit(context, width, height)
+ local w, h
+ if self._private.widget then
+ w = self._private.strategy(width, self._private.width)
+ h = self._private.strategy(height, self._private.height)
+
+ w, h = base.fit_widget(self, context, self._private.widget, w, h)
+ else
+ w, h = 0, 0
+ end
+
+ w = self._private.strategy(w, self._private.width)
+ h = self._private.strategy(h, self._private.height)
+
+ return w, h
+end
+
+--- The widget to be constrained.
+-- @property widget
+-- @tparam widget widget The widget
+
+function constraint:set_widget(widget)
+ self._private.widget = widget
+ self:emit_signal("widget::layout_changed")
+end
+
+function constraint:get_widget()
+ return self._private.widget
+end
+
+--- Get the number of children element
+-- @treturn table The children
+function constraint: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 constraint:set_children(children)
+ self:set_widget(children[1])
+end
+
+--- Set the strategy to use for the constraining. Valid values are 'max',
+-- 'min' or 'exact'. Throws an error on invalid values.
+-- @property strategy
+
+function constraint:set_strategy(val)
+ local func = {
+ min = function(real_size, limit)
+ return limit and math.max(limit, real_size) or real_size
+ end,
+ max = function(real_size, limit)
+ return limit and math.min(limit, real_size) or real_size
+ end,
+ exact = function(real_size, limit)
+ return limit or real_size
+ end
+ }
+
+ if not func[val] then
+ error("Invalid strategy for constraint layout: " .. tostring(val))
+ end
+
+ self._private.strategy = func[val]
+ self:emit_signal("widget::layout_changed")
+end
+
+function constraint:get_strategy()
+ return self._private.strategy
+end
+
+--- Set the maximum width to val. nil for no width limit.
+-- @property height
+-- @param number
+
+function constraint:set_width(val)
+ self._private.width = val
+ self:emit_signal("widget::layout_changed")
+end
+
+function constraint:get_width()
+ return self._private.width
+end
+
+--- Set the maximum height to val. nil for no height limit.
+-- @property width
+-- @param number
+
+function constraint:set_height(val)
+ self._private.height = val
+ self:emit_signal("widget::layout_changed")
+end
+
+function constraint:get_height()
+ return self._private.height
+end
+
+--- Reset this layout. The widget will be unreferenced, strategy set to "max"
+-- and the constraints set to nil.
+function constraint:reset()
+ self._private.width = nil
+ self._private.height = nil
+ self:set_strategy("max")
+ self:set_widget(nil)
+end
+
+--- Returns a new constraint container.
+-- This container will constraint the size of a
+-- widget according to the strategy. Note that this will only work for layouts
+-- that respect the widget's size, eg. fixed layout. In layouts that don't
+-- (fully) respect widget's requested size, the inner widget still might get
+-- drawn with a size that does not fit the constraint, eg. in flex layout.
+-- @param[opt] widget A widget to use.
+-- @param[opt] strategy How to constraint the size. 'max' (default), 'min' or
+-- 'exact'.
+-- @param[opt] width The maximum width of the widget. nil for no limit.
+-- @param[opt] height The maximum height of the widget. nil for no limit.
+-- @treturn table A new constraint container
+-- @function wibox.container.constraint
+local function new(widget, strategy, width, height)
+ local ret = base.make_widget(nil, nil, {enable_properties = true})
+
+ util.table.crush(ret, constraint, true)
+
+ ret:set_strategy(strategy or "max")
+ ret:set_width(width)
+ ret:set_height(height)
+
+ if widget then
+ ret:set_widget(widget)
+ end
+
+ return ret
+end
+
+function constraint.mt:__call(...)
+ return new(...)
+end
+
+--Imported documentation
+
+
+--- Get a widex index.
+-- @param widget The widget to look for
+-- @param[opt] recursive Also check sub-widgets
+-- @param[opt] ... Aditional widgets to add at the end of the \"path\"
+-- @return The index
+-- @return The parent layout
+-- @return The path between \"self\" and \"widget\"
+-- @function index
+
+--- Get all direct and indirect children widgets.
+-- This will scan all containers recursively to find widgets
+-- Warning: This method it prone to stack overflow id the widget, or any of its
+-- children, contain (directly or indirectly) itself.
+-- @treturn table The children
+-- @function get_all_children
+
+--- Set a declarative widget hierarchy description.
+-- See [The declarative layout system](../documentation/03-declarative-layout.md.html)
+-- @param args An array containing the widgets disposition
+-- @function setup
+
+--- Force a widget height.
+-- @property forced_height
+-- @tparam number|nil height The height (`nil` for automatic)
+
+--- Force a widget width.
+-- @property forced_width
+-- @tparam number|nil width The width (`nil` for automatic)
+
+--- The widget opacity (transparency).
+-- @property opacity
+-- @tparam[opt=1] number opacity The opacity (between 0 and 1)
+
+--- The widget visibility.
+-- @property visible
+-- @param boolean
+
+--- Set/get a widget's buttons.
+-- @param _buttons The table of buttons that should bind to the widget.
+-- @function buttons
+
+--- Emit a signal and ensure all parent widgets in the hierarchies also
+-- forward the signal. This is useful to track signals when there is a dynamic
+-- set of containers and layouts wrapping the widget.
+-- @tparam string signal_name
+-- @param ... Other arguments
+-- @function emit_signal_recursive
+
+--- When the layout (size) change.
+-- This signal is emitted when the previous results of `:layout()` and `:fit()`
+-- are no longer valid. Unless this signal is emitted, `:layout()` and `:fit()`
+-- must return the same result when called with the same arguments.
+-- @signal widget::layout_changed
+-- @see widget::redraw_needed
+
+--- When the widget content changed.
+-- This signal is emitted when the content of the widget changes. The widget will
+-- be redrawn, it is not re-layouted. Put differently, it is assumed that
+-- `:layout()` and `:fit()` would still return the same results as before.
+-- @signal widget::redraw_needed
+-- @see widget::layout_changed
+
+--- When a mouse button is pressed over the widget.
+-- @signal button::press
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When a mouse button is released over the widget.
+-- @signal button::release
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse enter a widget.
+-- @signal mouse::enter
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse leave a widget.
+-- @signal mouse::leave
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+
+--Imported documentation
+
+
+--- Disconnect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback that should be disconnected
+-- @function disconnect_signal
+
+--- Emit a signal.
+--
+-- @tparam string name The name of the signal
+-- @param ... Extra arguments for the callback functions. Each connected
+-- function receives the object as first argument and then any extra arguments
+-- that are given to emit_signal()
+-- @function emit_signal
+
+--- Connect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function connect_signal
+
+--- Connect to a signal weakly. This allows the callback function to be garbage
+-- collected and automatically disconnects the signal when that happens.
+--
+-- **Warning:**
+-- Only use this function if you really, really, really know what you
+-- are doing.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function weak_connect_signal
+
+
+return setmetatable(constraint, constraint.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/wibox/container/init.lua b/lib/wibox/container/init.lua
new file mode 100644
index 0000000..93158ab
--- /dev/null
+++ b/lib/wibox/container/init.lua
@@ -0,0 +1,21 @@
+---------------------------------------------------------------------------
+--- Collection of containers that can be used in widget boxes
+--
+-- @author Uli Schlachter
+-- @copyright 2010 Uli Schlachter
+-- @classmod wibox.container
+---------------------------------------------------------------------------
+local base = require("wibox.widget.base")
+
+return setmetatable({
+ rotate = require("wibox.container.rotate");
+ margin = require("wibox.container.margin");
+ mirror = require("wibox.container.mirror");
+ constraint = require("wibox.container.constraint");
+ scroll = require("wibox.container.scroll");
+ background = require("wibox.container.background");
+ radialprogressbar = require("wibox.container.radialprogressbar");
+ arcchart = require("wibox.container.arcchart");
+}, {__call = function(_, args) return base.make_widget_declarative(args) end})
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/wibox/container/margin.lua b/lib/wibox/container/margin.lua
new file mode 100644
index 0000000..edf9673
--- /dev/null
+++ b/lib/wibox/container/margin.lua
@@ -0,0 +1,419 @@
+---------------------------------------------------------------------------
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_container_defaults_margin.svg)
+--
+-- @author Uli Schlachter
+-- @copyright 2010 Uli Schlachter
+-- @classmod wibox.container.margin
+---------------------------------------------------------------------------
+
+local pairs = pairs
+local setmetatable = setmetatable
+local base = require("wibox.widget.base")
+local gcolor = require("gears.color")
+local cairo = require("lgi").cairo
+local util = require("awful.util")
+
+local margin = { mt = {} }
+
+-- Draw a margin layout
+function margin:draw(_, cr, width, height)
+ local x = self._private.left
+ local y = self._private.top
+ local w = self._private.right
+ local h = self._private.bottom
+ local color = self._private.color
+
+ if not self._private.widget or width <= x + w or height <= y + h then
+ return
+ end
+
+ if color then
+ cr:set_source(color)
+ cr:rectangle(0, 0, width, height)
+ cr:rectangle(x, y, width - x - w, height - y - h)
+ cr:set_fill_rule(cairo.FillRule.EVEN_ODD)
+ cr:fill()
+ end
+end
+
+-- Layout a margin layout
+function margin:layout(_, width, height)
+ if self._private.widget then
+ local x = self._private.left
+ local y = self._private.top
+ local w = self._private.right
+ local h = self._private.bottom
+
+ return { base.place_widget_at(self._private.widget, x, y, width - x - w, height - y - h) }
+ end
+end
+
+-- Fit a margin layout into the given space
+function margin:fit(context, width, height)
+ local extra_w = self._private.left + self._private.right
+ local extra_h = self._private.top + self._private.bottom
+ local w, h = 0, 0
+ if self._private.widget then
+ w, h = base.fit_widget(self, context, self._private.widget, width - extra_w, height - extra_h)
+ end
+
+ if self._private.draw_empty == false and (w == 0 or h == 0) then
+ return 0, 0
+ end
+
+ return w + extra_w, h + extra_h
+end
+
+--- The widget to be wrapped the the margins.
+-- @property widget
+-- @tparam widget widget The widget
+
+function margin:set_widget(widget)
+ if widget then
+ base.check_widget(widget)
+ end
+ self._private.widget = widget
+ self:emit_signal("widget::layout_changed")
+end
+
+function margin:get_widget()
+ return self._private.widget
+end
+
+-- Get the number of children element
+-- @treturn table The children
+function margin: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 margin:set_children(children)
+ self:set_widget(children[1])
+end
+
+--- Set all the margins to val.
+-- @property margins
+-- @tparam number val The margin value
+
+function margin:set_margins(val)
+ if self._private.left == val and
+ self._private.right == val and
+ self._private.top == val and
+ self._private.bottom == val then
+ return
+ end
+
+ self._private.left = val
+ self._private.right = val
+ self._private.top = val
+ self._private.bottom = val
+ self:emit_signal("widget::layout_changed")
+end
+
+--- Set the margins color to create a border.
+-- @property color
+-- @param color A color used to fill the margin.
+
+function margin:set_color(color)
+ self._private.color = color and gcolor(color)
+ self:emit_signal("widget::redraw_needed")
+end
+
+function margin:get_color()
+ return self._private.color
+end
+
+--- Draw the margin even if the content size is 0x0 (default: true)
+-- @function draw_empty
+-- @tparam boolean draw_empty Draw nothing is content is 0x0 or draw the margin anyway
+
+function margin:set_draw_empty(draw_empty)
+ self._private.draw_empty = draw_empty
+ self:emit_signal("widget::layout_changed")
+end
+
+function margin:get_draw_empty()
+ return self._private.draw_empty
+end
+
+--- Reset this layout. The widget will be unreferenced, the margins set to 0
+-- and the color erased
+function margin:reset()
+ self:set_widget(nil)
+ self:set_margins(0)
+ self:set_color(nil)
+end
+
+--- Set the left margin that this layout adds to its widget.
+-- @param margin The new margin to use.
+-- @property left
+
+--- Set the right margin that this layout adds to its widget.
+-- @param margin The new margin to use.
+-- @property right
+
+--- Set the top margin that this layout adds to its widget.
+-- @param margin The new margin to use.
+-- @property top
+
+--- Set the bottom margin that this layout adds to its widget.
+-- @param margin The new margin to use.
+-- @property bottom
+
+-- Create setters for each direction
+for _, v in pairs({ "left", "right", "top", "bottom" }) do
+ margin["set_" .. v] = function(layout, val)
+ if layout._private[v] == val then return end
+ layout._private[v] = val
+ layout:emit_signal("widget::layout_changed")
+ end
+
+ margin["get_" .. v] = function(layout)
+ return layout._private[v]
+ end
+end
+
+--- Returns a new margin container.
+-- @param[opt] widget A widget to use.
+-- @param[opt] left A margin to use on the left side of the widget.
+-- @param[opt] right A margin to use on the right side of the widget.
+-- @param[opt] top A margin to use on the top side of the widget.
+-- @param[opt] bottom A margin to use on the bottom side of the widget.
+-- @param[opt] color A color for the margins.
+-- @param[opt] draw_empty whether or not to draw the margin when the content is empty
+-- @treturn table A new margin container
+-- @function wibox.container.margin
+local function new(widget, left, right, top, bottom, color, draw_empty)
+ local ret = base.make_widget(nil, nil, {enable_properties = true})
+
+ util.table.crush(ret, margin, true)
+
+ ret:set_left(left or 0)
+ ret:set_right(right or 0)
+ ret:set_top(top or 0)
+ ret:set_bottom(bottom or 0)
+ ret:set_draw_empty(draw_empty)
+
+ ret:set_color(color)
+
+ if widget then
+ ret:set_widget(widget)
+ end
+
+ return ret
+end
+
+function margin.mt:__call(...)
+ return new(...)
+end
+
+--Imported documentation
+
+
+--- Get a widex index.
+-- @param widget The widget to look for
+-- @param[opt] recursive Also check sub-widgets
+-- @param[opt] ... Aditional widgets to add at the end of the \"path\"
+-- @return The index
+-- @return The parent layout
+-- @return The path between \"self\" and \"widget\"
+-- @function index
+
+--- Get all direct and indirect children widgets.
+-- This will scan all containers recursively to find widgets
+-- Warning: This method it prone to stack overflow id the widget, or any of its
+-- children, contain (directly or indirectly) itself.
+-- @treturn table The children
+-- @function get_all_children
+
+--- Set a declarative widget hierarchy description.
+-- See [The declarative layout system](../documentation/03-declarative-layout.md.html)
+-- @param args An array containing the widgets disposition
+-- @function setup
+
+--- Force a widget height.
+-- @property forced_height
+-- @tparam number|nil height The height (`nil` for automatic)
+
+--- Force a widget width.
+-- @property forced_width
+-- @tparam number|nil width The width (`nil` for automatic)
+
+--- The widget opacity (transparency).
+-- @property opacity
+-- @tparam[opt=1] number opacity The opacity (between 0 and 1)
+
+--- The widget visibility.
+-- @property visible
+-- @param boolean
+
+--- Set/get a widget's buttons.
+-- @param _buttons The table of buttons that should bind to the widget.
+-- @function buttons
+
+--- Emit a signal and ensure all parent widgets in the hierarchies also
+-- forward the signal. This is useful to track signals when there is a dynamic
+-- set of containers and layouts wrapping the widget.
+-- @tparam string signal_name
+-- @param ... Other arguments
+-- @function emit_signal_recursive
+
+--- When the layout (size) change.
+-- This signal is emitted when the previous results of `:layout()` and `:fit()`
+-- are no longer valid. Unless this signal is emitted, `:layout()` and `:fit()`
+-- must return the same result when called with the same arguments.
+-- @signal widget::layout_changed
+-- @see widget::redraw_needed
+
+--- When the widget content changed.
+-- This signal is emitted when the content of the widget changes. The widget will
+-- be redrawn, it is not re-layouted. Put differently, it is assumed that
+-- `:layout()` and `:fit()` would still return the same results as before.
+-- @signal widget::redraw_needed
+-- @see widget::layout_changed
+
+--- When a mouse button is pressed over the widget.
+-- @signal button::press
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When a mouse button is released over the widget.
+-- @signal button::release
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse enter a widget.
+-- @signal mouse::enter
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse leave a widget.
+-- @signal mouse::leave
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+
+--Imported documentation
+
+
+--- Disconnect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback that should be disconnected
+-- @function disconnect_signal
+
+--- Emit a signal.
+--
+-- @tparam string name The name of the signal
+-- @param ... Extra arguments for the callback functions. Each connected
+-- function receives the object as first argument and then any extra arguments
+-- that are given to emit_signal()
+-- @function emit_signal
+
+--- Connect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function connect_signal
+
+--- Connect to a signal weakly. This allows the callback function to be garbage
+-- collected and automatically disconnects the signal when that happens.
+--
+-- **Warning:**
+-- Only use this function if you really, really, really know what you
+-- are doing.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function weak_connect_signal
+
+
+return setmetatable(margin, margin.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/wibox/container/mirror.lua b/lib/wibox/container/mirror.lua
new file mode 100644
index 0000000..fc275cf
--- /dev/null
+++ b/lib/wibox/container/mirror.lua
@@ -0,0 +1,340 @@
+---------------------------------------------------------------------------
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_container_defaults_mirror.svg)
+--
+-- @author dodo
+-- @copyright 2012 dodo
+-- @classmod wibox.container.mirror
+---------------------------------------------------------------------------
+
+local type = type
+local error = error
+local ipairs = ipairs
+local setmetatable = setmetatable
+local base = require("wibox.widget.base")
+local matrix = require("gears.matrix")
+local util = require("awful.util")
+
+local mirror = { mt = {} }
+
+-- Layout this layout
+function mirror:layout(_, width, height)
+ if not self._private.widget then return end
+
+ local m = matrix.identity
+ local t = { x = 0, y = 0 } -- translation
+ local s = { x = 1, y = 1 } -- scale
+ if self._private.horizontal then
+ t.x = width
+ s.x = -1
+ end
+ if self._private.vertical then
+ t.y = height
+ s.y = -1
+ end
+ m = m:translate(t.x, t.y)
+ m = m:scale(s.x, s.y)
+
+ return { base.place_widget_via_matrix(self._private.widget, m, width, height) }
+end
+
+-- Fit this layout into the given area
+function mirror:fit(context, ...)
+ if not self._private.widget then
+ return 0, 0
+ end
+ return base.fit_widget(self, context, self._private.widget, ...)
+end
+
+--- The widget to be reflected.
+-- @property widget
+-- @tparam widget widget The widget
+
+function mirror:set_widget(widget)
+ if widget then
+ base.check_widget(widget)
+ end
+ self._private.widget = widget
+ self:emit_signal("widget::layout_changed")
+end
+
+function mirror:get_widget()
+ return self._private.widget
+end
+
+--- Get the number of children element
+-- @treturn table The children
+function mirror: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 mirror:set_children(children)
+ self:set_widget(children[1])
+end
+
+--- Reset this layout. The widget will be removed and the axes reset.
+function mirror:reset()
+ self._private.horizontal = false
+ self._private.vertical = false
+ self:set_widget(nil)
+end
+
+function mirror:set_reflection(reflection)
+ if type(reflection) ~= 'table' then
+ error("Invalid type of reflection for mirror layout: " ..
+ type(reflection) .. " (should be a table)")
+ end
+ for _, ref in ipairs({"horizontal", "vertical"}) do
+ if reflection[ref] ~= nil then
+ self._private[ref] = reflection[ref]
+ end
+ end
+ self:emit_signal("widget::layout_changed")
+end
+
+--- Get the reflection of this mirror layout.
+-- @property reflection
+-- @param table reflection A table of booleans with the keys "horizontal", "vertical".
+-- @param boolean reflection.horizontal
+-- @param boolean reflection.vertical
+
+function mirror:get_reflection()
+ return { horizontal = self._private.horizontal, vertical = self._private.vertical }
+end
+
+--- Returns a new mirror container.
+-- A mirror container mirrors a given widget. Use
+-- `:set_widget()` to set the widget and
+-- `:set_horizontal()` and `:set_vertical()` for the direction.
+-- horizontal and vertical are by default false which doesn't change anything.
+-- @param[opt] widget The widget to display.
+-- @param[opt] reflection A table describing the reflection to apply.
+-- @treturn table A new mirror container
+-- @function wibox.container.mirror
+local function new(widget, reflection)
+ local ret = base.make_widget(nil, nil, {enable_properties = true})
+ ret._private.horizontal = false
+ ret._private.vertical = false
+
+ util.table.crush(ret, mirror, true)
+
+ ret:set_widget(widget)
+ ret:set_reflection(reflection or {})
+
+ return ret
+end
+
+function mirror.mt:__call(...)
+ return new(...)
+end
+
+--Imported documentation
+
+
+--- Get a widex index.
+-- @param widget The widget to look for
+-- @param[opt] recursive Also check sub-widgets
+-- @param[opt] ... Aditional widgets to add at the end of the \"path\"
+-- @return The index
+-- @return The parent layout
+-- @return The path between \"self\" and \"widget\"
+-- @function index
+
+--- Get all direct and indirect children widgets.
+-- This will scan all containers recursively to find widgets
+-- Warning: This method it prone to stack overflow id the widget, or any of its
+-- children, contain (directly or indirectly) itself.
+-- @treturn table The children
+-- @function get_all_children
+
+--- Set a declarative widget hierarchy description.
+-- See [The declarative layout system](../documentation/03-declarative-layout.md.html)
+-- @param args An array containing the widgets disposition
+-- @function setup
+
+--- Force a widget height.
+-- @property forced_height
+-- @tparam number|nil height The height (`nil` for automatic)
+
+--- Force a widget width.
+-- @property forced_width
+-- @tparam number|nil width The width (`nil` for automatic)
+
+--- The widget opacity (transparency).
+-- @property opacity
+-- @tparam[opt=1] number opacity The opacity (between 0 and 1)
+
+--- The widget visibility.
+-- @property visible
+-- @param boolean
+
+--- Set/get a widget's buttons.
+-- @param _buttons The table of buttons that should bind to the widget.
+-- @function buttons
+
+--- Emit a signal and ensure all parent widgets in the hierarchies also
+-- forward the signal. This is useful to track signals when there is a dynamic
+-- set of containers and layouts wrapping the widget.
+-- @tparam string signal_name
+-- @param ... Other arguments
+-- @function emit_signal_recursive
+
+--- When the layout (size) change.
+-- This signal is emitted when the previous results of `:layout()` and `:fit()`
+-- are no longer valid. Unless this signal is emitted, `:layout()` and `:fit()`
+-- must return the same result when called with the same arguments.
+-- @signal widget::layout_changed
+-- @see widget::redraw_needed
+
+--- When the widget content changed.
+-- This signal is emitted when the content of the widget changes. The widget will
+-- be redrawn, it is not re-layouted. Put differently, it is assumed that
+-- `:layout()` and `:fit()` would still return the same results as before.
+-- @signal widget::redraw_needed
+-- @see widget::layout_changed
+
+--- When a mouse button is pressed over the widget.
+-- @signal button::press
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When a mouse button is released over the widget.
+-- @signal button::release
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse enter a widget.
+-- @signal mouse::enter
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse leave a widget.
+-- @signal mouse::leave
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+
+--Imported documentation
+
+
+--- Disconnect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback that should be disconnected
+-- @function disconnect_signal
+
+--- Emit a signal.
+--
+-- @tparam string name The name of the signal
+-- @param ... Extra arguments for the callback functions. Each connected
+-- function receives the object as first argument and then any extra arguments
+-- that are given to emit_signal()
+-- @function emit_signal
+
+--- Connect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function connect_signal
+
+--- Connect to a signal weakly. This allows the callback function to be garbage
+-- collected and automatically disconnects the signal when that happens.
+--
+-- **Warning:**
+-- Only use this function if you really, really, really know what you
+-- are doing.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function weak_connect_signal
+
+
+return setmetatable(mirror, mirror.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/wibox/container/radialprogressbar.lua b/lib/wibox/container/radialprogressbar.lua
new file mode 100644
index 0000000..7c67906
--- /dev/null
+++ b/lib/wibox/container/radialprogressbar.lua
@@ -0,0 +1,481 @@
+---------------------------------------------------------------------------
+--
+-- A circular progressbar wrapper.
+--
+-- If no child `widget` is set, then the radialprogressbar will take all the
+-- available size. Use a `wibox.container.constraint` to prevent this.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_container_defaults_radialprogressbar.svg)
+--
+-- @author Emmanuel Lepage Vallee &lt;elv1313@gmail.com&gt;
+-- @copyright 2013 Emmanuel Lepage Vallee
+-- @classmod wibox.container.radialprogressbar
+---------------------------------------------------------------------------
+
+local setmetatable = setmetatable
+local base = require("wibox.widget.base")
+local shape = require("gears.shape" )
+local util = require( "awful.util" )
+local color = require( "gears.color" )
+local beautiful = require("beautiful" )
+
+local default_outline_width = 2
+
+local radialprogressbar = { mt = {} }
+
+--- The progressbar border background color.
+-- @beautiful beautiful.radialprogressbar_border_color
+
+--- The progressbar foreground color.
+-- @beautiful beautiful.radialprogressbar_color
+
+--- The progressbar border width.
+-- @beautiful beautiful.radialprogressbar_border_width
+
+--- The padding between the outline and the progressbar.
+-- @beautiful beautiful.radialprogressbar_paddings
+-- @tparam[opt=0] table|number paddings A number or a table
+-- @tparam[opt=0] number paddings.top
+-- @tparam[opt=0] number paddings.bottom
+-- @tparam[opt=0] number paddings.left
+-- @tparam[opt=0] number paddings.right
+
+local function outline_workarea(self, width, height)
+ local border_width = self._private.border_width or
+ beautiful.radialprogressbar_border_width or default_outline_width
+
+ local x, y = 0, 0
+
+ -- Make sure the border fit in the clip area
+ local offset = border_width/2
+ x, y = x + offset, y+offset
+ width, height = width-2*offset, height-2*offset
+
+ return {x=x, y=y, width=width, height=height}, offset
+end
+
+-- The child widget area
+local function content_workarea(self, width, height)
+ local padding = self._private.paddings or {}
+ local wa = outline_workarea(self, width, height)
+
+ wa.x = wa.x + (padding.left or 0)
+ wa.y = wa.y + (padding.top or 0)
+ wa.width = wa.width - (padding.left or 0) - (padding.right or 0)
+ wa.height = wa.height - (padding.top or 0) - (padding.bottom or 0)
+
+ return wa
+end
+
+-- Draw the radial outline and progress
+function radialprogressbar:after_draw_children(_, cr, width, height)
+ cr:restore()
+
+ local border_width = self._private.border_width or
+ beautiful.radialprogressbar_border_width or default_outline_width
+
+ local wa = outline_workarea(self, width, height)
+ cr:translate(wa.x, wa.y)
+
+ -- Draw the outline
+ shape.rounded_bar(cr, wa.width, wa.height)
+ cr:set_source(color(self:get_border_color() or "#0000ff"))
+ cr:set_line_width(border_width)
+ cr:stroke()
+
+ -- Draw the progress
+ cr:set_source(color(self:get_color() or "#ff00ff"))
+ shape.radial_progress(cr, wa.width, wa.height, self._percent or 0)
+ cr:set_line_width(border_width)
+ cr:stroke()
+
+end
+
+-- Set the clip
+function radialprogressbar:before_draw_children(_, cr, width, height)
+ cr:save()
+ local wa = content_workarea(self, width, height)
+ cr:translate(wa.x, wa.y)
+ shape.rounded_bar(cr, wa.width, wa.height)
+ cr:clip()
+ cr:translate(-wa.x, -wa.y)
+end
+
+-- Layout this layout
+function radialprogressbar:layout(_, width, height)
+ if self._private.widget then
+ local wa = content_workarea(self, width, height)
+
+ return { base.place_widget_at(
+ self._private.widget, wa.x, wa.y, wa.width, wa.height
+ ) }
+ end
+end
+
+-- Fit this layout into the given area
+function radialprogressbar:fit(context, width, height)
+ if self._private.widget then
+ local wa = content_workarea(self, width, height)
+ local w, h = base.fit_widget(self, context, self._private.widget, wa.width, wa.height)
+ return wa.x + w, wa.y + h
+ end
+
+ return width, height
+end
+
+--- The widget to wrap in a radial proggressbar.
+-- @property widget
+-- @tparam widget widget The widget
+
+function radialprogressbar:set_widget(widget)
+ if widget then
+ base.check_widget(widget)
+ end
+ self._private.widget = widget
+ self:emit_signal("widget::layout_changed")
+end
+
+--- Get the children elements
+-- @treturn table The children
+function radialprogressbar: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 radialprogressbar:set_children(children)
+ self._private.widget = children and children[1]
+ self:emit_signal("widget::layout_changed")
+end
+
+--- Reset this container.
+function radialprogressbar:reset()
+ self:set_widget(nil)
+end
+
+for _,v in ipairs {"left", "right", "top", "bottom"} do
+ radialprogressbar["set_"..v.."_padding"] = function(self, val)
+ self._private.paddings = self._private.paddings or {}
+ self._private.paddings[v] = val
+ self:emit_signal("widget::redraw_needed")
+ self:emit_signal("widget::layout_changed")
+ end
+end
+
+--- The padding between the outline and the progressbar.
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_container_radialprogressbar_padding.svg)
+--
+-- @property paddings
+-- @tparam[opt=0] table|number paddings A number or a table
+-- @tparam[opt=0] number paddings.top
+-- @tparam[opt=0] number paddings.bottom
+-- @tparam[opt=0] number paddings.left
+-- @tparam[opt=0] number paddings.right
+
+--- The progressbar value.
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_container_radialprogressbar_value.svg)
+--
+-- @property value
+-- @tparam number value Between min_value and max_value
+
+function radialprogressbar:set_value(val)
+ if not val then self._percent = 0; return end
+
+ if val > self._private.max_value then
+ self:set_max_value(val)
+ elseif val < self._private.min_value then
+ self:set_min_value(val)
+ end
+
+ local delta = self._private.max_value - self._private.min_value
+
+ self._percent = val/delta
+ self:emit_signal("widget::redraw_needed")
+end
+
+--- The border background color.
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_container_radialprogressbar_border_color.svg)
+--
+-- @property border_color
+
+--- The border foreground color.
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_container_radialprogressbar_color.svg)
+--
+-- @property color
+
+--- The border width.
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_container_radialprogressbar_border_width.svg)
+--
+-- @property border_width
+-- @tparam[opt=3] number border_width
+
+--- The minimum value.
+-- @property min_value
+
+--- The maximum value.
+-- @property max_value
+
+for _, prop in ipairs {"max_value", "min_value", "border_color", "color",
+ "border_width", "paddings"} do
+ radialprogressbar["set_"..prop] = function(self, value)
+ self._private[prop] = value
+ self:emit_signal("property::"..prop)
+ self:emit_signal("widget::redraw_needed")
+ end
+ radialprogressbar["get_"..prop] = function(self)
+ return self._private[prop] or beautiful["radialprogressbar_"..prop]
+ end
+end
+
+function radialprogressbar:set_paddings(val)
+ self._private.paddings = type(val) == "number" and {
+ left = val,
+ right = val,
+ top = val,
+ bottom = val,
+ } or val or {}
+ self:emit_signal("property::paddings")
+ self:emit_signal("widget::redraw_needed")
+ self:emit_signal("widget::layout_changed")
+end
+
+--- Returns a new radialprogressbar layout. A radialprogressbar layout
+-- radialprogressbars a given widget. Use `.widget` to set the widget.
+-- @param[opt] widget The widget to display.
+-- @function wibox.container.radialprogressbar
+local function new(widget)
+ local ret = base.make_widget(nil, nil, {
+ enable_properties = true,
+ })
+
+ util.table.crush(ret, radialprogressbar)
+ ret._private.max_value = 1
+ ret._private.min_value = 0
+
+ ret:set_widget(widget)
+
+ return ret
+end
+
+function radialprogressbar.mt:__call(_, ...)
+ return new(...)
+end
+
+--Imported documentation
+
+
+--- Get a widex index.
+-- @param widget The widget to look for
+-- @param[opt] recursive Also check sub-widgets
+-- @param[opt] ... Aditional widgets to add at the end of the \"path\"
+-- @return The index
+-- @return The parent layout
+-- @return The path between \"self\" and \"widget\"
+-- @function index
+
+--- Get all direct and indirect children widgets.
+-- This will scan all containers recursively to find widgets
+-- Warning: This method it prone to stack overflow id the widget, or any of its
+-- children, contain (directly or indirectly) itself.
+-- @treturn table The children
+-- @function get_all_children
+
+--- Set a declarative widget hierarchy description.
+-- See [The declarative layout system](../documentation/03-declarative-layout.md.html)
+-- @param args An array containing the widgets disposition
+-- @function setup
+
+--- Force a widget height.
+-- @property forced_height
+-- @tparam number|nil height The height (`nil` for automatic)
+
+--- Force a widget width.
+-- @property forced_width
+-- @tparam number|nil width The width (`nil` for automatic)
+
+--- The widget opacity (transparency).
+-- @property opacity
+-- @tparam[opt=1] number opacity The opacity (between 0 and 1)
+
+--- The widget visibility.
+-- @property visible
+-- @param boolean
+
+--- Set/get a widget's buttons.
+-- @param _buttons The table of buttons that should bind to the widget.
+-- @function buttons
+
+--- Emit a signal and ensure all parent widgets in the hierarchies also
+-- forward the signal. This is useful to track signals when there is a dynamic
+-- set of containers and layouts wrapping the widget.
+-- @tparam string signal_name
+-- @param ... Other arguments
+-- @function emit_signal_recursive
+
+--- When the layout (size) change.
+-- This signal is emitted when the previous results of `:layout()` and `:fit()`
+-- are no longer valid. Unless this signal is emitted, `:layout()` and `:fit()`
+-- must return the same result when called with the same arguments.
+-- @signal widget::layout_changed
+-- @see widget::redraw_needed
+
+--- When the widget content changed.
+-- This signal is emitted when the content of the widget changes. The widget will
+-- be redrawn, it is not re-layouted. Put differently, it is assumed that
+-- `:layout()` and `:fit()` would still return the same results as before.
+-- @signal widget::redraw_needed
+-- @see widget::layout_changed
+
+--- When a mouse button is pressed over the widget.
+-- @signal button::press
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When a mouse button is released over the widget.
+-- @signal button::release
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse enter a widget.
+-- @signal mouse::enter
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse leave a widget.
+-- @signal mouse::leave
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+
+--Imported documentation
+
+
+--- Disconnect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback that should be disconnected
+-- @function disconnect_signal
+
+--- Emit a signal.
+--
+-- @tparam string name The name of the signal
+-- @param ... Extra arguments for the callback functions. Each connected
+-- function receives the object as first argument and then any extra arguments
+-- that are given to emit_signal()
+-- @function emit_signal
+
+--- Connect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function connect_signal
+
+--- Connect to a signal weakly. This allows the callback function to be garbage
+-- collected and automatically disconnects the signal when that happens.
+--
+-- **Warning:**
+-- Only use this function if you really, really, really know what you
+-- are doing.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function weak_connect_signal
+
+
+return setmetatable(radialprogressbar, radialprogressbar.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/wibox/container/rotate.lua b/lib/wibox/container/rotate.lua
new file mode 100644
index 0000000..5fe4a8e
--- /dev/null
+++ b/lib/wibox/container/rotate.lua
@@ -0,0 +1,384 @@
+---------------------------------------------------------------------------
+-- A container rotating the conained widget by 90 degrees.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_container_defaults_rotate.svg)
+--
+-- @author Uli Schlachter
+-- @copyright 2010 Uli Schlachter
+-- @classmod wibox.container.rotate
+---------------------------------------------------------------------------
+
+local error = error
+local pi = math.pi
+local setmetatable = setmetatable
+local tostring = tostring
+local base = require("wibox.widget.base")
+local matrix = require("gears.matrix")
+local util = require("awful.util")
+
+local rotate = { mt = {} }
+
+local function transform(layout, width, height)
+ local dir = layout:get_direction()
+ if dir == "east" or dir == "west" then
+ return height, width
+ end
+ return width, height
+end
+
+-- Layout this layout
+function rotate:layout(_, width, height)
+ if not self._private.widget or not self._private.widget._private.visible then
+ return
+ end
+
+ local dir = self:get_direction()
+
+ local m = matrix.identity
+ if dir == "west" then
+ m = m:rotate(pi / 2)
+ m = m:translate(0, -width)
+ elseif dir == "south" then
+ m = m:rotate(pi)
+ m = m:translate(-width, -height)
+ elseif dir == "east" then
+ m = m:rotate(3 * pi / 2)
+ m = m:translate(-height, 0)
+ end
+
+ -- Since we rotated, we might have to swap width and height.
+ -- transform() does that for us.
+ return { base.place_widget_via_matrix(self._private.widget, m, transform(self, width, height)) }
+end
+
+-- Fit this layout into the given area
+function rotate:fit(context, width, height)
+ if not self._private.widget then
+ return 0, 0
+ end
+ return transform(self, base.fit_widget(self, context, self._private.widget, transform(self, width, height)))
+end
+
+--- The widget to be rotated.
+-- @property widget
+-- @tparam widget widget The widget
+
+function rotate:set_widget(widget)
+ if widget then
+ base.check_widget(widget)
+ end
+ self._private.widget = widget
+ self:emit_signal("widget::layout_changed")
+end
+
+function rotate:get_widget()
+ return self._private.widget
+end
+
+--- Get the number of children element
+-- @treturn table The children
+function rotate: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 rotate:set_children(children)
+ self:set_widget(children[1])
+end
+
+--- Reset this layout. The widget will be removed and the rotation reset.
+function rotate:reset()
+ self._private.direction = nil
+ self:set_widget(nil)
+end
+
+--Imported documentation
+
+
+--- Get a widex index.
+-- @param widget The widget to look for
+-- @param[opt] recursive Also check sub-widgets
+-- @param[opt] ... Aditional widgets to add at the end of the \"path\"
+-- @return The index
+-- @return The parent layout
+-- @return The path between \"self\" and \"widget\"
+-- @function index
+
+--- Get all direct and indirect children widgets.
+-- This will scan all containers recursively to find widgets
+-- Warning: This method it prone to stack overflow id the widget, or any of its
+-- children, contain (directly or indirectly) itself.
+-- @treturn table The children
+-- @function get_all_children
+
+--- Set a declarative widget hierarchy description.
+-- See [The declarative layout system](../documentation/03-declarative-layout.md.html)
+-- @param args An array containing the widgets disposition
+-- @function setup
+
+--- Force a widget height.
+-- @property forced_height
+-- @tparam number|nil height The height (`nil` for automatic)
+
+--- Force a widget width.
+-- @property forced_width
+-- @tparam number|nil width The width (`nil` for automatic)
+
+--- The widget opacity (transparency).
+-- @property opacity
+-- @tparam[opt=1] number opacity The opacity (between 0 and 1)
+
+--- The widget visibility.
+-- @property visible
+-- @param boolean
+
+--- Set/get a widget's buttons.
+-- @param _buttons The table of buttons that should bind to the widget.
+-- @function buttons
+
+--- Emit a signal and ensure all parent widgets in the hierarchies also
+-- forward the signal. This is useful to track signals when there is a dynamic
+-- set of containers and layouts wrapping the widget.
+-- @tparam string signal_name
+-- @param ... Other arguments
+-- @function emit_signal_recursive
+
+--- When the layout (size) change.
+-- This signal is emitted when the previous results of `:layout()` and `:fit()`
+-- are no longer valid. Unless this signal is emitted, `:layout()` and `:fit()`
+-- must return the same result when called with the same arguments.
+-- @signal widget::layout_changed
+-- @see widget::redraw_needed
+
+--- When the widget content changed.
+-- This signal is emitted when the content of the widget changes. The widget will
+-- be redrawn, it is not re-layouted. Put differently, it is assumed that
+-- `:layout()` and `:fit()` would still return the same results as before.
+-- @signal widget::redraw_needed
+-- @see widget::layout_changed
+
+--- When a mouse button is pressed over the widget.
+-- @signal button::press
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When a mouse button is released over the widget.
+-- @signal button::release
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse enter a widget.
+-- @signal mouse::enter
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse leave a widget.
+-- @signal mouse::leave
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+
+--Imported documentation
+
+
+--- Disconnect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback that should be disconnected
+-- @function disconnect_signal
+
+--- Emit a signal.
+--
+-- @tparam string name The name of the signal
+-- @param ... Extra arguments for the callback functions. Each connected
+-- function receives the object as first argument and then any extra arguments
+-- that are given to emit_signal()
+-- @function emit_signal
+
+--- Connect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function connect_signal
+
+--- Connect to a signal weakly. This allows the callback function to be garbage
+-- collected and automatically disconnects the signal when that happens.
+--
+-- **Warning:**
+-- Only use this function if you really, really, really know what you
+-- are doing.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function weak_connect_signal
+
+
+--- The direction of this rotating container.
+-- Valid values are:
+--
+-- * *north*
+-- * *east*
+-- * *south*
+-- * *north*
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_container_rotate_angle.svg)
+--
+-- @usage
+--local normal = create_arrow('Normal')
+--local north = wibox.container {
+-- create_arrow('North'),
+-- direction = 'north',
+-- widget = wibox.container.rotate
+--}
+--local south = wibox.container {
+-- create_arrow('South'),
+-- direction = 'south',
+-- widget = wibox.container.rotate
+--}
+--local east = wibox.container {
+-- create_arrow('East'),
+-- direction = 'east',
+-- widget = wibox.container.rotate
+--}
+--local west = wibox.container {
+-- create_arrow('West'),
+-- direction = 'west',
+-- widget = wibox.container.rotate
+--}
+-- @property direction
+-- @tparam string dir The direction
+
+function rotate:set_direction(dir)
+ local allowed = {
+ north = true,
+ east = true,
+ south = true,
+ west = true
+ }
+
+ if not allowed[dir] then
+ error("Invalid direction for rotate layout: " .. tostring(dir))
+ end
+
+ self._private.direction = dir
+ self:emit_signal("widget::layout_changed")
+end
+
+--- Get the direction of this rotating layout
+function rotate:get_direction()
+ return self._private.direction or "north"
+end
+
+--- Returns a new rotate container.
+-- A rotate container rotates a given widget. Use
+-- :set_widget() to set the widget and :set_direction() for the direction.
+-- The default direction is "north" which doesn't change anything.
+-- @param[opt] widget The widget to display.
+-- @param[opt] dir The direction to rotate to.
+-- @treturn table A new rotate container.
+-- @function wibox.container.rotate
+local function new(widget, dir)
+ local ret = base.make_widget(nil, nil, {enable_properties = true})
+
+ util.table.crush(ret, rotate, true)
+
+ ret:set_widget(widget)
+ ret:set_direction(dir or "north")
+
+ return ret
+end
+
+function rotate.mt:__call(...)
+ return new(...)
+end
+
+return setmetatable(rotate, rotate.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/wibox/container/scroll.lua b/lib/wibox/container/scroll.lua
new file mode 100644
index 0000000..0fb2ad5
--- /dev/null
+++ b/lib/wibox/container/scroll.lua
@@ -0,0 +1,716 @@
+---------------------------------------------------------------------------
+-- @author Uli Schlachter (based on ideas from Saleur Geoffrey)
+-- @copyright 2015 Uli Schlachter
+-- @classmod wibox.container.scroll
+---------------------------------------------------------------------------
+
+local cache = require("gears.cache")
+local timer = require("gears.timer")
+local hierarchy = require("wibox.hierarchy")
+local base = require("wibox.widget.base")
+local lgi = require("lgi")
+local GLib = lgi.GLib
+
+local scroll = {}
+local scroll_mt = { __index = scroll }
+local _need_scroll_redraw
+
+-- "Strip" a context so that we can use it for our own drawing
+local function cleanup_context(context)
+ local skip = { wibox = true, drawable = true, client = true, position = true }
+ local res = {}
+ for k, v in pairs(context) do
+ if not skip[k] then
+ res[k] = v
+ end
+ end
+ return res
+end
+
+-- Create a hierarchy (and some more stuff) for drawing the given widget. This
+-- allows "some stuff" to be re-used instead of re-created all the time.
+local hierarchy_cache = cache.new(function(context, widget, width, height)
+ context = cleanup_context(context)
+ local layouts = setmetatable({}, { __mode = "k" })
+
+ -- Create a widget hierarchy and update when needed
+ local hier
+ local function do_pending_updates(layout)
+ layouts[layout] = true
+ hier:update(context, widget, width, height, nil)
+ end
+ local function emit(signal)
+ -- Make the scroll layouts redraw
+ for w in pairs(layouts) do
+ w:emit_signal(signal)
+ end
+ end
+ local function redraw_callback()
+ emit("widget::redraw_needed")
+ end
+ local function layout_callback()
+ emit("widget::redraw_needed")
+ emit("widget::layout_changed")
+ end
+ hier = hierarchy.new(context, widget, width, height, redraw_callback, layout_callback, nil)
+
+ return hier, do_pending_updates, context
+end)
+
+--- Calculate all the information needed for scrolling.
+-- @param self The instance of the scrolling layout.
+-- @param context A widget context under which we are fit/drawn.
+-- @param width The available width
+-- @param height The available height
+-- @return A table with the following entries
+-- @field fit_width The width that should be returned from :fit
+-- @field fit_height The height that should be returned from :fit
+-- @field surface_width The width for showing the child widget
+-- @field surface_height The height for showing the child widget
+-- @field first_x The x offset for drawing the child the first time
+-- @field first_y The y offset for drawing the child the first time
+-- @field[opt] second_x The x offset for drawing the child the second time
+-- @field[opt] second_y The y offset for drawing the child the second time
+-- @field hierarchy The wibox.hierarchy instance representing "everything"
+-- @field context The widget context for drawing the hierarchy
+local function calculate_info(self, context, width, height)
+ local result = {}
+ assert(self._private.widget)
+
+ -- First, get the size of the widget (and the size of extra space)
+ local surface_width, surface_height = width, height
+ local extra_width, extra_height, extra = 0, 0, self._private.expand and self._private.extra_space or 0
+ local w, h
+ if self._private.dir == "h" then
+ w, h = base.fit_widget(self, context, self._private.widget, self._private.space_for_scrolling, height)
+ surface_width = w
+ extra_width = extra
+ else
+ w, h = base.fit_widget(self, context, self._private.widget, width, self._private.space_for_scrolling)
+ surface_height = h
+ extra_height = extra
+ end
+ result.fit_width, result.fit_height = w, h
+ if self._private.dir == "h" then
+ if self._private.max_size then
+ result.fit_width = math.min(w, self._private.max_size)
+ end
+ else
+ if self._private.max_size then
+ result.fit_height = math.min(h, self._private.max_size)
+ end
+ end
+ if w > width or h > height then
+ -- There is less space available than we need, we have to scroll
+ _need_scroll_redraw(self)
+
+ surface_width, surface_height = surface_width + extra_width, surface_height + extra_height
+
+ local x, y = 0, 0
+ local function get_scroll_offset(size, visible_size)
+ return self._private.step_function(self._private.timer:elapsed(), size, visible_size, self._private.speed, self._private.extra_space)
+ end
+ if self._private.dir == "h" then
+ x = -get_scroll_offset(surface_width - extra, width)
+ else
+ y = -get_scroll_offset(surface_height - extra, height)
+ end
+ result.first_x, result.first_y = x, y
+ -- Was the extra space already included elsewhere?
+ local extra_spacer = self._private.expand and 0 or self._private.extra_space
+ if self._private.dir == "h" then
+ x = x + surface_width + extra_spacer
+ else
+ y = y + surface_height + extra_spacer
+ end
+ result.second_x, result.second_y = x, y
+ else
+ result.first_x, result.first_y = 0, 0
+ end
+ result.surface_width, result.surface_height = surface_width, surface_height
+
+ -- Get the hierarchy and subscribe ourselves to updates
+ local hier, do_pending_updates, ctx = hierarchy_cache:get(context,
+ self._private.widget, surface_width, surface_height)
+ result.hierarchy = hier
+ result.context = ctx
+ do_pending_updates(self)
+
+ return result
+end
+
+-- Draw this scrolling layout.
+-- @param context The context in which we are drawn.
+-- @param cr The cairo context to draw to.
+-- @param width The available width.
+-- @param height The available height.
+function scroll:draw(context, cr, width, height)
+ if not self._private.widget then
+ return
+ end
+
+ local info = calculate_info(self, context, width, height)
+
+ -- Draw the first instance of the child
+ cr:save()
+ cr:translate(info.first_x, info.first_y)
+ cr:rectangle(0, 0, info.surface_width, info.surface_height)
+ cr:clip()
+ info.hierarchy:draw(info.context, cr)
+ cr:restore()
+
+ -- If there is one, draw the second instance (same code as above, minus the
+ -- clip)
+ if info.second_x and info.second_y then
+ cr:translate(info.second_x, info.second_y)
+ cr:rectangle(0, 0, info.surface_width, info.surface_height)
+ cr:clip()
+ info.hierarchy:draw(info.context, cr)
+ end
+end
+
+-- Fit the scroll layout into the given space.
+-- @param context The context in which we are fit.
+-- @param width The available width.
+-- @param height The available height.
+function scroll:fit(context, width, height)
+ if not self._private.widget then
+ return 0, 0
+ end
+ local info = calculate_info(self, context, width, height)
+ return info.fit_width, info.fit_height
+end
+
+-- Internal function used for triggering redraws for scrolling.
+-- The purpose is to start a timer for redrawing the widget for scrolling.
+-- Redrawing works by simply emitting the `widget::redraw_needed` signal.
+-- Pausing is implemented in this function: We just don't start a timer.
+-- This function must be idempotent (calling it multiple times right after
+-- another does not make a difference).
+_need_scroll_redraw = function(self)
+ if not self._private.paused and not self._private.scroll_timer then
+ self._private.scroll_timer = timer.start_new(1 / self._private.fps, function()
+ self._private.scroll_timer = nil
+ self:emit_signal("widget::redraw_needed")
+ end)
+ end
+end
+
+--- Pause the scrolling animation.
+-- @see continue
+function scroll:pause()
+ if self._private.paused then
+ return
+ end
+ self._private.paused = true
+ self._private.timer:stop()
+end
+
+--- Continue the scrolling animation.
+-- @see pause
+function scroll:continue()
+ if not self._private.paused then
+ return
+ end
+ self._private.paused = false
+ self._private.timer:continue()
+ self:emit_signal("widget::redraw_needed")
+end
+
+--- Reset the scrolling state to its initial condition.
+-- For must scroll step functions, the effect of this function should be to
+-- display the widget without any scrolling applied.
+-- This function does not undo the effect of @{pause}.
+function scroll:reset_scrolling()
+ self._private.timer:start()
+ if self._private.paused then
+ self._private.timer:stop()
+ end
+end
+
+--- Set the direction in which this widget scroll.
+-- @param dir Either "h" for horizontal scrolling or "v" for vertical scrolling
+function scroll:set_direction(dir)
+ if dir == self._private.dir then
+ return
+ end
+ if dir ~= "h" and dir ~= "v" then
+ error("Invalid direction, can only be 'h' or 'v'")
+ end
+ self._private.dir = dir
+ self:emit_signal("widget::layout_changed")
+ self:emit_signal("widget::redraw_needed")
+end
+
+--- The widget to be scrolled.
+-- @property widget
+-- @tparam widget widget The widget
+
+function scroll:set_widget(widget)
+ if widget == self._private.widget then
+ return
+ end
+ if widget then
+ base.check_widget(widget)
+ end
+ self._private.widget = widget
+ self:emit_signal("widget::layout_changed")
+ self:emit_signal("widget::redraw_needed")
+end
+
+function scroll:get_widget()
+ return self._private.widget
+end
+
+--- Get the number of children element
+-- @treturn table The children
+function scroll: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 scroll:set_children(children)
+ self:set_widget(children[1])
+end
+
+--- Specify the expand mode that is used for extra space.
+-- @tparam boolean expand If true, the widget is expanded to include the extra
+-- space. If false, the extra space is simply left empty.
+-- @see set_extra_space
+function scroll:set_expand(expand)
+ if expand == self._private.expand then
+ return
+ end
+ self._private.expand = expand
+ self:emit_signal("widget::redraw_needed")
+end
+
+--- Set the number of frames per second that this widget should draw.
+-- @tparam number fps The number of frames per second
+function scroll:set_fps(fps)
+ if fps == self._private.fps then
+ return
+ end
+ self._private.fps = fps
+ -- No signal needed: If we are scrolling, the next redraw will apply the new
+ -- FPS, else it obviously doesn't make a difference.
+end
+
+--- Set the amount of extra space that should be included in the scrolling. This
+-- extra space will likely be left empty between repetitions of the widgets.
+-- @tparam number extra_space The amount of extra space
+-- @see set_expand
+function scroll:set_extra_space(extra_space)
+ if extra_space == self._private.extra_space then
+ return
+ end
+ self._private.extra_space = extra_space
+ self:emit_signal("widget::redraw_needed")
+end
+
+--- Set the speed of the scrolling animation. The exact meaning depends on the
+-- step function that is used, but for the simplest step functions, this will be
+-- in pixels per second.
+-- @tparam number speed The speed for the animation
+function scroll:set_speed(speed)
+ if speed == self._private.speed then
+ return
+ end
+ self._private.speed = speed
+ self:emit_signal("widget::redraw_needed")
+end
+
+--- Set the maximum size of this widget in the direction set by
+-- @{set_direction}. If the child widget is smaller than this size, no scrolling
+-- is done. If the child widget is larger, then only this size will be visible
+-- and the rest is made visible via scrolling.
+-- @tparam number max_size The maximum size of this widget or nil for unlimited.
+function scroll:set_max_size(max_size)
+ if max_size == self._private.max_size then
+ return
+ end
+ self._private.max_size = max_size
+ self:emit_signal("widget::layout_changed")
+end
+
+--- Set the step function that determines the exact behaviour of the scrolling
+-- animation.
+-- The step function is called with five arguments:
+--
+-- * The time in seconds since the state of the animation
+-- * The size of the child widget
+-- * The size of the visible part of the widget
+-- * The speed of the animation. This should have a linear effect on this
+-- function's behaviour.
+-- * The extra space configured by @{set_extra_space}. This was not yet added to
+-- the size of the child widget, but should likely be added to it in most
+-- cases.
+--
+-- The step function should return a single number. This number is the offset at
+-- which the widget is drawn and should be between 0 and `size+extra_space`.
+-- @tparam function step_function A step function.
+-- @see step_functions
+function scroll:set_step_function(step_function)
+ -- Call the step functions once to see if it works
+ step_function(0, 42, 10, 10, 5)
+ if step_function == self._private.step_function then
+ return
+ end
+ self._private.step_function = step_function
+ self:emit_signal("widget::redraw_needed")
+end
+
+--- Set an upper limit for the space for scrolling.
+-- This restricts the child widget's maximal size.
+-- @tparam number space_for_scrolling The space for scrolling
+function scroll:set_space_for_scrolling(space_for_scrolling)
+ if space_for_scrolling == self._private.space_for_scrolling then
+ return
+ end
+ self._private.space_for_scrolling = space_for_scrolling
+ self:emit_signal("widget::layout_changed")
+end
+
+local function get_layout(dir, widget, fps, speed, extra_space, expand, max_size, step_function, space_for_scrolling)
+ local ret = base.make_widget(nil, nil, {enable_properties = true})
+
+ ret._private.paused = false
+ ret._private.timer = GLib.Timer()
+ ret._private.scroll_timer = nil
+
+ setmetatable(ret, scroll_mt)
+
+ ret:set_direction(dir)
+ ret:set_widget(widget)
+ ret:set_fps(fps or 20)
+ ret:set_speed(speed or 10)
+ ret:set_extra_space(extra_space or 0)
+ ret:set_expand(expand)
+ ret:set_max_size(max_size)
+ ret:set_step_function(step_function or scroll.step_functions.linear_increase)
+ ret:set_space_for_scrolling(space_for_scrolling or 2^1024)
+
+ return ret
+end
+
+--- Get a new horizontal scrolling container.
+-- @param[opt] widget The widget that should be scrolled
+-- @param[opt=20] fps The number of frames per second
+-- @param[opt=10] speed The speed of the animation
+-- @param[opt=0] extra_space The amount of extra space to include
+-- @tparam[opt=false] boolean expand Should the widget be expanded to include the
+-- extra space?
+-- @param[opt] max_size The maximum size of the child widget
+-- @param[opt=step_functions.linear_increase] step_function The step function to be used
+-- @param[opt=2^1024] space_for_scrolling The space for scrolling
+function scroll.horizontal(widget, fps, speed, extra_space, expand, max_size, step_function, space_for_scrolling)
+ return get_layout("h", widget, fps, speed, extra_space, expand, max_size, step_function, space_for_scrolling)
+end
+
+--- Get a new vertical scrolling container.
+-- @param[opt] widget The widget that should be scrolled
+-- @param[opt=20] fps The number of frames per second
+-- @param[opt=10] speed The speed of the animation
+-- @param[opt=0] extra_space The amount of extra space to include
+-- @tparam[opt=false] boolean expand Should the widget be expanded to include the
+-- extra space?
+-- @param[opt] max_size The maximum size of the child widget
+-- @param[opt=step_functions.linear_increase] step_function The step function to be used
+-- @param[opt=2^1024] space_for_scrolling The space for scrolling
+function scroll.vertical(widget, fps, speed, extra_space, expand, max_size, step_function, space_for_scrolling)
+ return get_layout("v", widget, fps, speed, extra_space, expand, max_size, step_function, space_for_scrolling)
+end
+
+--- A selection of step functions
+-- @see set_step_function
+scroll.step_functions = {}
+
+--- A step function that scrolls the widget in an increasing direction with
+-- constant speed.
+function scroll.step_functions.linear_increase(elapsed, size, _, speed, extra_space)
+ return (elapsed * speed) % (size + extra_space)
+end
+
+--- A step function that scrolls the widget in an decreasing direction with
+-- constant speed.
+function scroll.step_functions.linear_decrease(elapsed, size, _, speed, extra_space)
+ return (-elapsed * speed) % (size + extra_space)
+end
+
+--- A step function that scrolls the widget to its end and back to its
+-- beginning, then back to its end, etc. The speed is constant.
+function scroll.step_functions.linear_back_and_forth(elapsed, size, visible_size, speed)
+ local state = ((elapsed * speed) % (2 * size)) / size
+ state = state <= 1 and state or 2 - state
+ return (size - visible_size) * state
+end
+
+--- A step function that scrolls the widget to its end and back to its
+-- beginning, then back to its end, etc. The speed is null at the ends and
+-- maximal in the middle.
+function scroll.step_functions.nonlinear_back_and_forth(elapsed, size, visible_size, speed)
+ local state = ((elapsed * speed) % (2 * size)) / size
+ local negate = false
+ if state > 1 then
+ negate = true
+ state = state - 1
+ end
+ if state < 1/3 then
+ -- In the first 1/3rd of time, do a quadratic increase in speed
+ state = 2 * state * state
+ elseif state < 2/3 then
+ -- In the center, do a linear increase. That means we need:
+ -- If state is 1/3, result is 2/9 = 2 * 1/3 * 1/3
+ -- If state is 2/3, result is 7/9 = 1 - 2 * (1 - 2/3) * (1 - 2/3)
+ state = 5/3*state - 3/9
+ else
+ -- In the last 1/3rd of time, do a quadratic decrease in speed
+ state = 1 - 2 * (1 - state) * (1 - state)
+ end
+ if negate then
+ state = 1 - state
+ end
+ return (size - visible_size) * state
+end
+
+--- A step function that scrolls the widget to its end and back to its
+-- beginning, then back to its end, etc. The speed is null at the ends and
+-- maximal in the middle. At both ends the widget stands still for a moment.
+function scroll.step_functions.waiting_nonlinear_back_and_forth(elapsed, size, visible_size, speed)
+ local state = ((elapsed * speed) % (2 * size)) / size
+ local negate = false
+ if state > 1 then
+ negate = true
+ state = state - 1
+ end
+ if state < 1/5 or state > 4/5 then
+ -- One fifth of time, nothing moves
+ state = state < 1/5 and 0 or 1
+ else
+ state = (state - 1/5) * 5/3
+ if state < 1/3 then
+ -- In the first 1/3rd of time, do a quadratic increase in speed
+ state = 2 * state * state
+ elseif state < 2/3 then
+ -- In the center, do a linear increase. That means we need:
+ -- If state is 1/3, result is 2/9 = 2 * 1/3 * 1/3
+ -- If state is 2/3, result is 7/9 = 1 - 2 * (1 - 2/3) * (1 - 2/3)
+ state = 5/3*state - 3/9
+ else
+ -- In the last 1/3rd of time, do a quadratic decrease in speed
+ state = 1 - 2 * (1 - state) * (1 - state)
+ end
+ end
+ if negate then
+ state = 1 - state
+ end
+ return (size - visible_size) * state
+end
+
+--Imported documentation
+
+
+--- Get a widex index.
+-- @param widget The widget to look for
+-- @param[opt] recursive Also check sub-widgets
+-- @param[opt] ... Aditional widgets to add at the end of the \"path\"
+-- @return The index
+-- @return The parent layout
+-- @return The path between \"self\" and \"widget\"
+-- @function index
+
+--- Get all direct and indirect children widgets.
+-- This will scan all containers recursively to find widgets
+-- Warning: This method it prone to stack overflow id the widget, or any of its
+-- children, contain (directly or indirectly) itself.
+-- @treturn table The children
+-- @function get_all_children
+
+--- Set a declarative widget hierarchy description.
+-- See [The declarative layout system](../documentation/03-declarative-layout.md.html)
+-- @param args An array containing the widgets disposition
+-- @function setup
+
+--- Force a widget height.
+-- @property forced_height
+-- @tparam number|nil height The height (`nil` for automatic)
+
+--- Force a widget width.
+-- @property forced_width
+-- @tparam number|nil width The width (`nil` for automatic)
+
+--- The widget opacity (transparency).
+-- @property opacity
+-- @tparam[opt=1] number opacity The opacity (between 0 and 1)
+
+--- The widget visibility.
+-- @property visible
+-- @param boolean
+
+--- Set/get a widget's buttons.
+-- @param _buttons The table of buttons that should bind to the widget.
+-- @function buttons
+
+--- Emit a signal and ensure all parent widgets in the hierarchies also
+-- forward the signal. This is useful to track signals when there is a dynamic
+-- set of containers and layouts wrapping the widget.
+-- @tparam string signal_name
+-- @param ... Other arguments
+-- @function emit_signal_recursive
+
+--- When the layout (size) change.
+-- This signal is emitted when the previous results of `:layout()` and `:fit()`
+-- are no longer valid. Unless this signal is emitted, `:layout()` and `:fit()`
+-- must return the same result when called with the same arguments.
+-- @signal widget::layout_changed
+-- @see widget::redraw_needed
+
+--- When the widget content changed.
+-- This signal is emitted when the content of the widget changes. The widget will
+-- be redrawn, it is not re-layouted. Put differently, it is assumed that
+-- `:layout()` and `:fit()` would still return the same results as before.
+-- @signal widget::redraw_needed
+-- @see widget::layout_changed
+
+--- When a mouse button is pressed over the widget.
+-- @signal button::press
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When a mouse button is released over the widget.
+-- @signal button::release
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse enter a widget.
+-- @signal mouse::enter
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse leave a widget.
+-- @signal mouse::leave
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+
+--Imported documentation
+
+
+--- Disconnect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback that should be disconnected
+-- @function disconnect_signal
+
+--- Emit a signal.
+--
+-- @tparam string name The name of the signal
+-- @param ... Extra arguments for the callback functions. Each connected
+-- function receives the object as first argument and then any extra arguments
+-- that are given to emit_signal()
+-- @function emit_signal
+
+--- Connect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function connect_signal
+
+--- Connect to a signal weakly. This allows the callback function to be garbage
+-- collected and automatically disconnects the signal when that happens.
+--
+-- **Warning:**
+-- Only use this function if you really, really, really know what you
+-- are doing.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function weak_connect_signal
+
+
+return scroll
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/wibox/drawable.lua b/lib/wibox/drawable.lua
new file mode 100644
index 0000000..330edc3
--- /dev/null
+++ b/lib/wibox/drawable.lua
@@ -0,0 +1,489 @@
+---------------------------------------------------------------------------
+--- Handling of drawables. A drawable is something that can be drawn to.
+--
+-- @author Uli Schlachter
+-- @copyright 2012 Uli Schlachter
+-- @classmod wibox.drawable
+---------------------------------------------------------------------------
+
+local drawable = {}
+local capi = {
+ awesome = awesome,
+ root = root,
+ screen = screen
+}
+local beautiful = require("beautiful")
+local cairo = require("lgi").cairo
+local color = require("gears.color")
+local object = require("gears.object")
+local surface = require("gears.surface")
+local timer = require("gears.timer")
+local grect = require("gears.geometry").rectangle
+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 visible_drawables = {}
+
+-- Get the widget context. This should always return the same table (if
+-- possible), so that our draw and fit caches can work efficiently.
+local function get_widget_context(self)
+ local geom = self.drawable:geometry()
+
+ local s = self._forced_screen
+ if not s then
+ local sgeos = {}
+
+ for scr in capi.screen do
+ sgeos[scr] = scr.geometry
+ end
+
+ s = grect.get_by_coord(sgeos, geom.x, geom.y) or capi.screen.primary
+ end
+
+ local context = self._widget_context
+ local dpi = beautiful.xresources.get_dpi(s)
+ if (not context) or context.screen ~= s or context.dpi ~= dpi then
+ context = {
+ screen = s,
+ dpi = dpi,
+ drawable = self,
+ }
+ 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 not self.drawable.valid then return end
+ if self._forced_screen and not self._forced_screen.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
+ if not surf then return end
+ local cr = cairo.Context(surf)
+ local geom = self.drawable:geometry();
+ local x, y, width, height = geom.x, geom.y, geom.width, geom.height
+ local context = get_widget_context(self)
+
+ -- Relayout
+ if self._need_relayout or self._need_complete_repaint then
+ self._need_relayout = false
+ if self._widget_hierarchy and self.widget then
+ self._widget_hierarchy:update(context,
+ self.widget, width, height, self._dirty_area)
+ else
+ self._need_complete_repaint = true
+ if self.widget then
+ self._widget_hierarchy_callback_arg = {}
+ self._widget_hierarchy = hierarchy.new(context, self.widget, width, height,
+ self._redraw_callback, self._layout_callback, self._widget_hierarchy_callback_arg)
+ else
+ self._widget_hierarchy = nil
+ end
+ end
+
+ if self._need_complete_repaint then
+ self._need_complete_repaint = false
+ self._dirty_area:union_rectangle(cairo.RectangleInt{
+ x = 0, y = 0, width = width, height = height
+ })
+ end
+ end
+
+ -- Clip to the dirty area
+ if self._dirty_area:is_empty() then
+ return
+ end
+ for i = 0, self._dirty_area:num_rectangles() - 1 do
+ local rect = self._dirty_area:get_rectangle(i)
+ cr:rectangle(rect.x, rect.y, rect.width, rect.height)
+ end
+ self._dirty_area = cairo.Region.create()
+ cr:clip()
+
+ -- Draw the background
+ cr:save()
+
+ if not capi.awesome.composite_manager_running then
+ -- This is pseudo-transparency: We draw the wallpaper in the background
+ local wallpaper = surface.load_silently(capi.root.wallpaper(), false)
+ if wallpaper then
+ cr.operator = cairo.Operator.SOURCE
+ cr:set_source_surface(wallpaper, -x, -y)
+ cr:paint()
+ end
+ cr.operator = cairo.Operator.OVER
+ else
+ -- This is true transparency: We draw a translucent background
+ cr.operator = cairo.Operator.SOURCE
+ end
+
+ cr:set_source(self.background_color)
+ cr:paint()
+
+ cr:restore()
+
+ -- Paint the background image
+ if self.background_image then
+ cr:save()
+ if type(self.background_image) == "function" then
+ self.background_image(context, cr, width, height, unpack(self.background_image_args))
+ else
+ local pattern = cairo.Pattern.create_for_surface(self.background_image)
+ cr:set_source(pattern)
+ cr:paint()
+ end
+ cr:restore()
+ end
+
+ -- Draw the widget
+ if self._widget_hierarchy then
+ cr:set_source(self.foreground_color)
+ self._widget_hierarchy:draw(context, cr)
+ end
+
+ self.drawable:refresh()
+
+ assert(cr.status == "SUCCESS", "Cairo context entered error state: " .. cr.status)
+end
+
+local function find_widgets(_drawable, result, _hierarchy, x, y)
+ local m = _hierarchy:get_matrix_from_device()
+
+ -- Is (x,y) inside of this hierarchy or any child (aka the draw extents)
+ local x1, y1 = m:transform_point(x, y)
+ local x2, y2, w2, h2 = _hierarchy:get_draw_extents()
+ if x1 < x2 or x1 >= x2 + w2 then
+ return
+ end
+ if y1 < y2 or y1 >= y2 + h2 then
+ return
+ end
+
+ -- Is (x,y) inside of this widget?
+ local width, height = _hierarchy:get_size()
+ if x1 >= 0 and y1 >= 0 and x1 <= width and y1 <= height then
+ -- Get the extents of this widget in the device space
+ local x3, y3, w3, h3 = matrix.transform_rectangle(_hierarchy:get_matrix_to_device(),
+ 0, 0, width, height)
+ table.insert(result, {
+ x = x3, y = y3, width = w3, height = h3,
+ widget_width = width,
+ widget_height = height,
+ drawable = _drawable,
+ widget = _hierarchy:get_widget(),
+ hierarchy = _hierarchy
+ })
+ end
+ for _, child in ipairs(_hierarchy:get_children()) do
+ find_widgets(_drawable, result, child, x, y)
+ end
+end
+
+--- Find a widget by a point.
+-- The drawable must have drawn itself at least once for this to work.
+-- @param x X coordinate of the point
+-- @param y Y coordinate of the point
+-- @treturn table A table containing a description of all the widgets that
+-- contain the given point. Each entry is a table containing this drawable as
+-- its `.drawable` entry, the widget under `.widget` and the instance of
+-- `wibox.hierarchy` describing the size and position of the widget under
+-- `.hierarchy`. For convenience, `.x`, `.y`, `.width` and `.height` contain an
+-- approximation of the widget's extents on the surface. `widget_width` and
+-- `widget_height` contain the exact size of the widget in its own, local
+-- coordinate system (which may e.g. be rotated and scaled).
+function drawable:find_widgets(x, y)
+ local result = {}
+ if self._widget_hierarchy then
+ find_widgets(self, result, self._widget_hierarchy, x, y)
+ end
+ return result
+end
+
+
+--- Set the widget that the drawable displays
+function drawable:set_widget(widget)
+ self.widget = widget
+
+ -- Make sure the widget gets drawn
+ self._need_relayout = true
+ self.draw()
+end
+
+--- Set the background of the drawable
+-- @param c The background to use. This must either be a cairo pattern object,
+-- nil or a string that gears.color() understands.
+-- @see gears.color
+function drawable:set_bg(c)
+ c = c or "#000000"
+ local t = type(c)
+
+ if t == "string" or t == "table" then
+ c = color(c)
+ end
+
+ -- If the background is completely opaque, we don't need to redraw when
+ -- the drawable is moved
+ -- XXX: This isn't needed when awesome.composite_manager_running is true,
+ -- but a compositing manager could stop/start and we'd have to properly
+ -- handle this. So for now we choose the lazy approach.
+ local redraw_on_move = not color.create_opaque_pattern(c)
+ if self._redraw_on_move ~= redraw_on_move then
+ self._redraw_on_move = redraw_on_move
+ if redraw_on_move then
+ self.drawable:connect_signal("property::x", self._do_complete_repaint)
+ self.drawable:connect_signal("property::y", self._do_complete_repaint)
+ else
+ self.drawable:disconnect_signal("property::x", self._do_complete_repaint)
+ self.drawable:disconnect_signal("property::y", self._do_complete_repaint)
+ end
+ end
+
+ self.background_color = c
+ self._do_complete_repaint()
+end
+
+--- Set the background image of the drawable
+-- If `image` is a function, it will be called with `(context, cr, width, height)`
+-- as arguments. Any other arguments passed to this method will be appended.
+-- @param image A background image or a function
+function drawable:set_bgimage(image, ...)
+ if type(image) ~= "function" then
+ image = surface(image)
+ end
+
+ self.background_image = image
+ self.background_image_args = {...}
+
+ self._do_complete_repaint()
+end
+
+--- Set the foreground of the drawable
+-- @param c The foreground to use. This must either be a cairo pattern object,
+-- nil or a string that gears.color() understands.
+-- @see gears.color
+function drawable:set_fg(c)
+ c = c or "#FFFFFF"
+ if type(c) == "string" or type(c) == "table" then
+ c = color(c)
+ end
+ self.foreground_color = c
+ self._do_complete_repaint()
+end
+
+function drawable:_force_screen(s)
+ self._forced_screen = s
+end
+
+function drawable:_inform_visible(visible)
+ self._visible = visible
+ if visible then
+ visible_drawables[self] = true
+ -- The wallpaper or widgets might have changed
+ self:_do_complete_repaint()
+ else
+ visible_drawables[self] = nil
+ end
+end
+
+local function emit_difference(name, list, skip)
+ local function in_table(table, val)
+ for _, v in pairs(table) do
+ if v.widget == val.widget then
+ return true
+ end
+ end
+ return false
+ end
+
+ for _, v in pairs(list) do
+ if not in_table(skip, v) then
+ v.widget:emit_signal(name,v)
+ end
+ end
+end
+
+local function handle_leave(_drawable)
+ emit_difference("mouse::leave", _drawable._widgets_under_mouse, {})
+ _drawable._widgets_under_mouse = {}
+end
+
+local function handle_motion(_drawable, x, y)
+ if x < 0 or y < 0 or x > _drawable.drawable:geometry().width or y > _drawable.drawable:geometry().height then
+ return handle_leave(_drawable)
+ end
+
+ -- Build a plain list of all widgets on that point
+ local widgets_list = _drawable:find_widgets(x, y)
+
+ -- First, "leave" all widgets that were left
+ emit_difference("mouse::leave", _drawable._widgets_under_mouse, widgets_list)
+ -- Then enter some widgets
+ emit_difference("mouse::enter", widgets_list, _drawable._widgets_under_mouse)
+
+ _drawable._widgets_under_mouse = widgets_list
+end
+
+local function setup_signals(_drawable)
+ local d = _drawable.drawable
+
+ local function clone_signal(name)
+ -- When "name" is emitted on wibox.drawin, also emit it on wibox
+ d:connect_signal(name, function(_, ...)
+ _drawable:emit_signal(name, ...)
+ end)
+ end
+ clone_signal("button::press")
+ clone_signal("button::release")
+ clone_signal("mouse::enter")
+ clone_signal("mouse::leave")
+ clone_signal("mouse::move")
+ clone_signal("property::surface")
+ clone_signal("property::width")
+ clone_signal("property::height")
+ clone_signal("property::x")
+ clone_signal("property::y")
+end
+
+function drawable.new(d, widget_context_skeleton, drawable_name)
+ local ret = object()
+ ret.drawable = d
+ ret._widget_context_skeleton = widget_context_skeleton
+ ret._need_complete_repaint = true
+ ret._need_relayout = true
+ ret._dirty_area = cairo.Region.create()
+ setup_signals(ret)
+
+ for k, v in pairs(drawable) do
+ if type(v) == "function" then
+ ret[k] = v
+ end
+ end
+
+ -- Only redraw a drawable once, even when we get told to do so multiple times.
+ ret._redraw_pending = false
+ ret._do_redraw = function()
+ ret._redraw_pending = false
+ do_redraw(ret)
+ end
+
+ -- Connect our signal when we need a redraw
+ ret.draw = function()
+ if not ret._redraw_pending then
+ timer.delayed_call(ret._do_redraw)
+ ret._redraw_pending = true
+ end
+ end
+ ret._do_complete_repaint = function()
+ ret._need_complete_repaint = true
+ ret:draw()
+ end
+
+ -- 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
+
+ -- Set the default background
+ ret:set_bg(beautiful.bg_normal)
+ ret:set_fg(beautiful.fg_normal)
+
+ -- Initialize internals
+ ret._widgets_under_mouse = {}
+
+ local function button_signal(name)
+ d:connect_signal(name, function(_, x, y, button, modifiers)
+ local widgets = ret:find_widgets(x, y)
+ for _, v in pairs(widgets) do
+ -- Calculate x/y inside of the widget
+ local lx, ly = v.hierarchy:get_matrix_from_device():transform_point(x, y)
+ v.widget:emit_signal(name, lx, ly, button, modifiers,v)
+ end
+ end)
+ end
+ button_signal("button::press")
+ button_signal("button::release")
+
+ d:connect_signal("mouse::move", function(_, x, y) handle_motion(ret, x, y) end)
+ d:connect_signal("mouse::leave", function() handle_leave(ret) end)
+
+ -- Set up our callbacks for repaints
+ ret._redraw_callback = function(hierar, arg)
+ -- Avoid crashes when a drawable was partly finalized and dirty_area is broken.
+ if not ret._visible then
+ return
+ end
+ if ret._widget_hierarchy_callback_arg ~= arg then
+ return
+ end
+ local m = hierar:get_matrix_to_device()
+ local x, y, width, height = matrix.transform_rectangle(m, hierar:get_draw_extents())
+ local x1, y1 = math.floor(x), math.floor(y)
+ local x2, y2 = math.ceil(x + width), math.ceil(y + height)
+ ret._dirty_area:union_rectangle(cairo.RectangleInt{
+ x = x1, y = y1, width = x2 - x1, height = y2 - y1
+ })
+ ret:draw()
+ end
+ ret._layout_callback = function(_, arg)
+ if ret._widget_hierarchy_callback_arg ~= arg then
+ return
+ end
+ ret._need_relayout = true
+ -- When not visible, we will be redrawn when we become visible. In the
+ -- mean-time, the layout does not matter much.
+ if ret._visible then
+ ret:draw()
+ end
+ end
+
+ -- Add __tostring method to metatable.
+ ret.drawable_name = drawable_name or object.modulename(3)
+ local mt = {}
+ local orig_string = tostring(ret)
+ mt.__tostring = function()
+ return string.format("%s (%s)", ret.drawable_name, orig_string)
+ end
+ ret = setmetatable(ret, mt)
+
+ -- Make sure the drawable is drawn at least once
+ ret._do_complete_repaint()
+
+ return ret
+end
+
+-- Redraw all drawables when the wallpaper changes
+capi.awesome.connect_signal("wallpaper_changed", function()
+ for d in pairs(visible_drawables) do
+ d:_do_complete_repaint()
+ end
+end)
+
+-- Give drawables a chance to react to screen changes
+local function draw_all()
+ for d in pairs(visible_drawables) do
+ d:draw()
+ 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
new file mode 100644
index 0000000..6bf2167
--- /dev/null
+++ b/lib/wibox/hierarchy.lua
@@ -0,0 +1,333 @@
+---------------------------------------------------------------------------
+-- Management of widget hierarchies. Each widget hierarchy object has a widget
+-- for which it saves e.g. size and transformation in its parent. Also, each
+-- widget has a number of children.
+--
+-- @author Uli Schlachter
+-- @copyright 2015 Uli Schlachter
+-- @module wibox.hierarchy
+---------------------------------------------------------------------------
+
+local matrix = require("gears.matrix")
+local protected_call = require("gears.protected_call")
+local cairo = require("lgi").cairo
+local base = require("wibox.widget.base")
+local no_parent = base.no_parent_I_know_what_I_am_doing
+
+local hierarchy = {}
+
+local function hierarchy_new(redraw_callback, layout_callback, callback_arg)
+ local result = {
+ _matrix = matrix.identity,
+ _matrix_to_device = matrix.identity,
+ _need_update = true,
+ _widget = nil,
+ _context = nil,
+ _redraw_callback = redraw_callback,
+ _layout_callback = layout_callback,
+ _callback_arg = callback_arg,
+ _size = {
+ width = nil,
+ height = nil
+ },
+ _draw_extents = {
+ x = 0,
+ y = 0,
+ width = 0,
+ height = 0
+ },
+ _parent = nil,
+ _children = {}
+ }
+
+ function result._redraw()
+ redraw_callback(result, callback_arg)
+ end
+ function result._layout()
+ local h = result
+ while h do
+ h._need_update = true
+ h = h._parent
+ end
+ layout_callback(result, callback_arg)
+ end
+ function result._emit_recursive(widget, name, ...)
+ local cur = result
+ assert(widget == cur._widget)
+ while cur do
+ if cur._widget then
+ cur._widget:emit_signal(name, ...)
+ end
+ cur = cur._parent
+ end
+ end
+
+ for k, f in pairs(hierarchy) do
+ if type(f) == "function" then
+ result[k] = f
+ end
+ end
+ return result
+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
+ -- Nothing changed
+ return
+ end
+
+ self._need_update = false
+
+ local old_x, old_y, old_width, old_height
+ local old_widget = self._widget
+ if self._size.width and self._size.height then
+ local x, y, w, h = matrix.transform_rectangle(self._matrix_to_device, 0, 0, self._size.width, self._size.height)
+ old_x, old_y = math.floor(x), math.floor(y)
+ old_width, old_height = math.ceil(x + w) - old_x, math.ceil(y + h) - old_y
+ else
+ old_x, old_y, old_width, old_height = 0, 0, 0, 0
+ end
+
+ -- Disconnect old signals
+ if old_widget and old_widget ~= widget then
+ self._widget:disconnect_signal("widget::redraw_needed", self._redraw)
+ self._widget:disconnect_signal("widget::layout_changed", self._layout)
+ self._widget:disconnect_signal("widget::emit_recursive", self._emit_recursive)
+ end
+
+ -- 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
+ self._matrix_to_device = matrix_to_device
+
+ -- Connect signals
+ if old_widget ~= widget then
+ widget:weak_connect_signal("widget::redraw_needed", self._redraw)
+ widget:weak_connect_signal("widget::layout_changed", self._layout)
+ widget:weak_connect_signal("widget::emit_recursive", self._emit_recursive)
+ end
+
+ -- Update children
+ local old_children = self._children
+ local layout_result = base.layout_widget(no_parent, context, widget, width, height)
+ self._children = {}
+ for _, w in ipairs(layout_result or {}) do
+ local r = table.remove(old_children, 1)
+ if not r then
+ r = hierarchy_new(self._redraw_callback, self._layout_callback, self._callback_arg)
+ r._parent = self
+ end
+ hierarchy_update(r, context, w._widget, w._width, w._height, region, w._matrix, w._matrix * matrix_to_device)
+ table.insert(self._children, r)
+ end
+
+ -- Calculate the draw extents
+ local x1, y1, x2, y2 = 0, 0, width, height
+ for _, h in ipairs(self._children) do
+ local px, py, pwidth, pheight = matrix.transform_rectangle(h._matrix, h:get_draw_extents())
+ x1 = math.min(x1, px)
+ y1 = math.min(y1, py)
+ x2 = math.max(x2, px + pwidth)
+ y2 = math.max(y2, py + pheight)
+ end
+ self._draw_extents = {
+ x = x1, y = y1,
+ width = x2 - x1,
+ height = y2 - y1
+ }
+
+ -- Check which part needs to be redrawn
+
+ -- Are there any children which were removed? Their area needs a redraw.
+ for _, child in ipairs(old_children) do
+ local x, y, w, h = matrix.transform_rectangle(child._matrix_to_device, child:get_draw_extents())
+ region:union_rectangle(cairo.RectangleInt{
+ x = x, y = y, width = w, height = h
+ })
+ child._parent = nil
+ end
+
+ -- Did we change and need to be redrawn?
+ local x, y, w, h = matrix.transform_rectangle(self._matrix_to_device, 0, 0, self._size.width, self._size.height)
+ local new_x, new_y = math.floor(x), math.floor(y)
+ local new_width, new_height = math.ceil(x + w) - new_x, math.ceil(y + h) - new_y
+ if new_x ~= old_x or new_y ~= old_y or new_width ~= old_width or new_height ~= old_height or
+ widget ~= old_widget then
+ region:union_rectangle(cairo.RectangleInt{
+ x = old_x, y = old_y, width = old_width, height = old_height
+ })
+ region:union_rectangle(cairo.RectangleInt{
+ x = new_x, y = new_y, width = new_width, height = new_height
+ })
+ end
+end
+
+--- Create a new widget hierarchy that has no parent.
+-- @param context The context in which we are laid out.
+-- @param widget The widget that is at the base of the hierarchy.
+-- @param width The available width for this hierarchy.
+-- @param height The available height for this hierarchy.
+-- @param redraw_callback Callback that is called with the corresponding widget
+-- hierarchy on widget::redraw_needed on some widget.
+-- @param layout_callback Callback that is called with the corresponding widget
+-- hierarchy on widget::layout_changed on some widget.
+-- @param callback_arg A second argument that is given to the above callbacks.
+-- @return A new widget hierarchy
+function hierarchy.new(context, widget, width, height, redraw_callback, layout_callback, callback_arg)
+ local result = hierarchy_new(redraw_callback, layout_callback, callback_arg)
+ result:update(context, widget, width, height)
+ return result
+end
+
+--- Update a widget hierarchy with some new state.
+-- @param context The context in which we are laid out.
+-- @param widget The widget that is at the base of the hierarchy.
+-- @param width The available width for this hierarchy.
+-- @param height The available height for this hierarchy.
+-- @param[opt] region A region to use for accumulating changed parts
+-- @return A cairo region describing the changed parts (either the `region`
+-- argument or a new, internally created region).
+function hierarchy:update(context, widget, width, height, region)
+ region = region or cairo.Region.create()
+ hierarchy_update(self, context, widget, width, height, region, self._matrix, self._matrix_to_device)
+ return region
+end
+
+--- Get the widget that this hierarchy manages.
+function hierarchy:get_widget()
+ return self._widget
+end
+
+--- Get a matrix that transforms to the parent's coordinate space from this
+-- hierarchy's coordinate system.
+-- @return A matrix describing the transformation.
+function hierarchy:get_matrix_to_parent()
+ return self._matrix
+end
+
+--- Get a matrix that transforms to the base of this hierarchy's coordinate
+-- system (aka the coordinate system of the device that this
+-- hierarchy is applied upon) from this hierarchy's coordinate system.
+-- @return A matrix describing the transformation.
+function hierarchy:get_matrix_to_device()
+ return self._matrix_to_device
+end
+
+--- Get a matrix that transforms from the parent's coordinate space into this
+-- hierarchy's coordinate system.
+-- @return A matrix describing the transformation.
+function hierarchy:get_matrix_from_parent()
+ local m = self:get_matrix_to_parent()
+ return m:invert()
+end
+
+--- Get a matrix that transforms from the base of this hierarchy's coordinate
+-- system (aka the coordinate system of the device that this
+-- hierarchy is applied upon) into this hierarchy's coordinate system.
+-- @return A matrix describing the transformation.
+function hierarchy:get_matrix_from_device()
+ local m = self:get_matrix_to_device()
+ return m:invert()
+end
+
+--- Get the extents that this hierarchy possibly draws to (in the current coordinate space).
+-- This includes the size of this element plus the size of all children
+-- (after applying the corresponding transformation).
+-- @return x, y, width, height
+function hierarchy:get_draw_extents()
+ local ext = self._draw_extents
+ return ext.x, ext.y, ext.width, ext.height
+end
+
+--- Get the size that this hierarchy logically covers (in the current coordinate space).
+-- @return width, height
+function hierarchy:get_size()
+ local ext = self._size
+ return ext.width, ext.height
+end
+
+--- Get a list of all children.
+-- @return List of all children hierarchies.
+function hierarchy:get_children()
+ return self._children
+end
+
+--- Does the given cairo context have an empty clip (aka "no drawing possible")?
+local function empty_clip(cr)
+ local _, _, width, height = cr:clip_extents()
+ return width == 0 or height == 0
+end
+
+--- Draw a hierarchy to some cairo context.
+-- This function draws the widgets in this widget hierarchy to the given cairo
+-- context. The context's clip is used to skip parts that aren't visible.
+-- @param context The context in which widgets are drawn.
+-- @param cr The cairo context that is used for drawing.
+function hierarchy:draw(context, cr)
+ local widget = self:get_widget()
+ if not widget._private.visible then
+ return
+ end
+
+ cr:save()
+ cr:transform(self:get_matrix_to_parent():to_cairo_matrix())
+
+ -- Clip to the draw extents
+ cr:rectangle(self:get_draw_extents())
+ cr:clip()
+
+ -- Draw if needed
+ if not empty_clip(cr) then
+ local opacity = widget:get_opacity()
+ local function call(func, extra_arg1, extra_arg2)
+ if not func then return end
+ if not extra_arg2 then
+ protected_call(func, widget, context, cr, self:get_size())
+ else
+ protected_call(func, widget, context, extra_arg1, extra_arg2, cr, self:get_size())
+ end
+ end
+
+ -- Prepare opacity handling
+ if opacity ~= 1 then
+ cr:push_group()
+ end
+
+ -- Draw the widget
+ cr:save()
+ cr:rectangle(0, 0, self:get_size())
+ cr:clip()
+ call(widget.draw)
+ cr:restore()
+
+ -- Draw its children (We already clipped to the draw extents above)
+ call(widget.before_draw_children)
+ for i, wi in ipairs(self:get_children()) do
+ call(widget.before_draw_child, i, wi:get_widget())
+ wi:draw(context, cr)
+ call(widget.after_draw_child, i, wi:get_widget())
+ end
+ call(widget.after_draw_children)
+
+ -- Apply opacity
+ if opacity ~= 1 then
+ cr:pop_group_to_source()
+ cr.operator = cairo.Operator.OVER
+ cr:paint_with_alpha(opacity)
+ end
+ end
+
+ cr:restore()
+end
+
+return hierarchy
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/wibox/init.lua b/lib/wibox/init.lua
new file mode 100644
index 0000000..3fc89ce
--- /dev/null
+++ b/lib/wibox/init.lua
@@ -0,0 +1,479 @@
+---------------------------------------------------------------------------
+-- @author Uli Schlachter
+-- @copyright 2010 Uli Schlachter
+-- @classmod wibox
+---------------------------------------------------------------------------
+
+local capi = {
+ drawin = drawin,
+ root = root,
+ awesome = awesome,
+ screen = screen
+}
+local setmetatable = setmetatable
+local pairs = pairs
+local type = type
+local object = require("gears.object")
+local grect = require("gears.geometry").rectangle
+local beautiful = require("beautiful")
+local base = require("wibox.widget.base")
+
+--- This provides widget box windows. Every wibox can also be used as if it were
+-- a drawin. All drawin functions and properties are also available on wiboxes!
+-- wibox
+local wibox = { mt = {}, object = {} }
+wibox.layout = require("wibox.layout")
+wibox.container = require("wibox.container")
+wibox.widget = require("wibox.widget")
+wibox.drawable = require("wibox.drawable")
+wibox.hierarchy = require("wibox.hierarchy")
+
+local force_forward = {
+ shape_bounding = true,
+ shape_clip = true,
+}
+
+--Imported documentation
+
+--- Border width.
+--
+-- **Signal:**
+--
+-- * *property::border_width*
+--
+-- @property border_width
+-- @param integer
+
+--- Border color.
+--
+-- Please note that this property only support string based 24 bit or 32 bit
+-- colors:
+--
+-- Red Blue
+-- _| _|
+-- #FF00FF
+-- T‾
+-- Green
+--
+--
+-- Red Blue
+-- _| _|
+-- #FF00FF00
+-- T‾ ‾T
+-- Green Alpha
+--
+-- **Signal:**
+--
+-- * *property::border_color*
+--
+-- @property border_color
+-- @param string
+
+--- On top of other windows.
+--
+-- **Signal:**
+--
+-- * *property::ontop*
+--
+-- @property ontop
+-- @param boolean
+
+--- The mouse cursor.
+--
+-- **Signal:**
+--
+-- * *property::cursor*
+--
+-- @property cursor
+-- @param string
+-- @see mouse
+
+--- Visibility.
+--
+-- **Signal:**
+--
+-- * *property::visible*
+--
+-- @property visible
+-- @param boolean
+
+--- The opacity of the wibox, between 0 and 1.
+--
+-- **Signal:**
+--
+-- * *property::opacity*
+--
+-- @property opacity
+-- @tparam number opacity (between 0 and 1)
+
+--- The window type (desktop, normal, dock, ...).
+--
+-- **Signal:**
+--
+-- * *property::type*
+--
+-- @property type
+-- @param string
+-- @see client.type
+
+--- The x coordinates.
+--
+-- **Signal:**
+--
+-- * *property::x*
+--
+-- @property x
+-- @param integer
+
+--- The y coordinates.
+--
+-- **Signal:**
+--
+-- * *property::y*
+--
+-- @property y
+-- @param integer
+
+--- The width of the wibox.
+--
+-- **Signal:**
+--
+-- * *property::width*
+--
+-- @property width
+-- @param width
+
+--- The height of the wibox.
+--
+-- **Signal:**
+--
+-- * *property::height*
+--
+-- @property height
+-- @param height
+
+--- The wibox screen.
+--
+-- @property screen
+-- @param screen
+
+--- The wibox's `drawable`.
+--
+-- **Signal:**
+--
+-- * *property::drawable*
+--
+-- @property drawable
+-- @tparam drawable drawable
+
+--- The widget that the `wibox` displays.
+-- @property widget
+-- @param widget
+
+--- The X window id.
+--
+-- **Signal:**
+--
+-- * *property::window*
+--
+-- @property window
+-- @param string
+-- @see client.window
+
+--- The wibox's bounding shape as a (native) cairo surface.
+--
+-- **Signal:**
+--
+-- * *property::shape_bounding*
+--
+-- @property shape_bounding
+-- @param surface._native
+
+--- The wibox's clip shape as a (native) cairo surface.
+--
+-- **Signal:**
+--
+-- * *property::shape_clip*
+--
+-- @property shape_clip
+-- @param surface._native
+
+--- Get or set mouse buttons bindings to a wibox.
+--
+-- @param buttons_table A table of buttons objects, or nothing.
+-- @function buttons
+
+--- Get or set wibox geometry. That's the same as accessing or setting the x,
+-- y, width or height properties of a wibox.
+--
+-- @param A table with coordinates to modify.
+-- @return A table with wibox coordinates and geometry.
+-- @function geometry
+
+--- Get or set wibox struts.
+--
+-- @param strut A table with new strut, or nothing
+-- @return The wibox strut in a table.
+-- @function struts
+-- @see client.struts
+
+--- The default background color.
+-- @beautiful beautiful.bg_normal
+-- @see bg
+
+--- The default foreground (text) color.
+-- @beautiful beautiful.fg_normal
+-- @see fg
+
+--- Set a declarative widget hierarchy description.
+-- See [The declarative layout system](../documentation/03-declarative-layout.md.html)
+-- @param args An array containing the widgets disposition
+-- @name setup
+-- @class function
+
+--- The background of the wibox.
+-- @param c The background to use. This must either be a cairo pattern object,
+-- nil or a string that gears.color() understands.
+-- @property bg
+-- @see gears.color
+
+--- The background image of the drawable.
+-- If `image` is a function, it will be called with `(context, cr, width, height)`
+-- as arguments. Any other arguments passed to this method will be appended.
+-- @param image A background image or a function
+-- @property bgimage
+-- @see gears.surface
+
+--- The foreground (text) of the wibox.
+-- @param c The foreground to use. This must either be a cairo pattern object,
+-- nil or a string that gears.color() understands.
+-- @property fg
+-- @see gears.color
+
+--- Find a widget by a point.
+-- The wibox must have drawn itself at least once for this to work.
+-- @tparam number x X coordinate of the point
+-- @tparam number y Y coordinate of the point
+-- @treturn table A sorted table of widgets positions. The first element is the biggest
+-- container while the last is the topmost widget. The table contains *x*, *y*,
+-- *width*, *height* and *widget*.
+-- @name find_widgets
+-- @class function
+
+
+function wibox:set_widget(widget)
+ self._drawable:set_widget(widget)
+end
+
+function wibox:get_widget()
+ return self._drawable.widget
+end
+
+wibox.setup = base.widget.setup
+
+function wibox:set_bg(c)
+ self._drawable:set_bg(c)
+end
+
+function wibox:set_bgimage(image, ...)
+ self._drawable:set_bgimage(image, ...)
+end
+
+function wibox:set_fg(c)
+ self._drawable:set_fg(c)
+end
+
+function wibox:find_widgets(x, y)
+ return self._drawable:find_widgets(x, y)
+end
+
+function wibox:get_screen()
+ if self.screen_assigned and self.screen_assigned.valid then
+ return self.screen_assigned
+ else
+ self.screen_assigned = nil
+ end
+ local sgeos = {}
+
+ for s in capi.screen do
+ sgeos[s] = s.geometry
+ end
+
+ return grect.get_closest_by_coord(sgeos, self.x, self.y)
+end
+
+function wibox:set_screen(s)
+ s = capi.screen[s or 1]
+ if s ~= self:get_screen() then
+ self.x = s.geometry.x
+ self.y = s.geometry.y
+ end
+
+ -- Remember this screen so things work correctly if screens overlap and
+ -- (x,y) is not enough to figure out the correct screen.
+ self.screen_assigned = s
+ self._drawable:_force_screen(s)
+end
+
+for _, k in pairs{ "buttons", "struts", "geometry", "get_xproperty", "set_xproperty" } do
+ wibox[k] = function(self, ...)
+ return self.drawin[k](self.drawin, ...)
+ end
+end
+
+local function setup_signals(_wibox)
+ local obj
+ local function clone_signal(name)
+ -- When "name" is emitted on wibox.drawin, also emit it on wibox
+ obj:connect_signal(name, function(_, ...)
+ _wibox:emit_signal(name, ...)
+ end)
+ end
+
+ obj = _wibox.drawin
+ clone_signal("property::border_color")
+ clone_signal("property::border_width")
+ clone_signal("property::buttons")
+ clone_signal("property::cursor")
+ clone_signal("property::height")
+ clone_signal("property::ontop")
+ clone_signal("property::opacity")
+ clone_signal("property::struts")
+ clone_signal("property::visible")
+ clone_signal("property::width")
+ clone_signal("property::x")
+ clone_signal("property::y")
+ clone_signal("property::geometry")
+ clone_signal("property::shape_bounding")
+ clone_signal("property::shape_clip")
+
+ obj = _wibox._drawable
+ clone_signal("button::press")
+ clone_signal("button::release")
+ clone_signal("mouse::enter")
+ clone_signal("mouse::leave")
+ clone_signal("mouse::move")
+ clone_signal("property::surface")
+end
+
+--- Create a wibox.
+-- @tparam[opt=nil] table args
+-- @tparam integer args.border_width Border width.
+-- @tparam string args.border_color Border color.
+-- @tparam boolean args.ontop On top of other windows.
+-- @tparam string args.cursor The mouse cursor.
+-- @tparam boolean args.visible Visibility.
+-- @tparam number args.opacity The opacity of the wibox, between 0 and 1.
+-- @tparam string args.type The window type (desktop, normal, dock, …).
+-- @tparam integer args.x The x coordinates.
+-- @tparam integer args.y The y coordinates.
+-- @tparam integer args.width The width of the wibox.
+-- @tparam integer args.height The height of the wibox.
+-- @tparam screen args.screen The wibox screen.
+-- @tparam wibox.widget args.widget The widget that the wibox displays.
+-- @param args.shape_bounding The wibox’s bounding shape as a (native) cairo surface.
+-- @param args.shape_clip The wibox’s clip shape as a (native) cairo surface.
+-- @tparam color args.bg The background of the wibox.
+-- @tparam surface args.bgimage The background image of the drawable.
+-- @tparam color args.fg The foreground (text) of the wibox.
+-- @treturn wibox The new wibox
+-- @function .wibox
+
+local function new(args)
+ args = args or {}
+ local ret = object()
+ local w = capi.drawin(args)
+
+ function w.get_wibox()
+ return ret
+ end
+
+ ret.drawin = w
+ ret._drawable = wibox.drawable(w.drawable, { wibox = ret },
+ "wibox drawable (" .. object.modulename(3) .. ")")
+
+ ret._drawable:_inform_visible(w.visible)
+ w:connect_signal("property::visible", function()
+ ret._drawable:_inform_visible(w.visible)
+ end)
+
+ for k, v in pairs(wibox) do
+ if type(v) == "function" then
+ ret[k] = v
+ end
+ end
+
+ setup_signals(ret)
+ ret.draw = ret._drawable.draw
+
+ -- Set the default background
+ ret:set_bg(args.bg or beautiful.bg_normal)
+ ret:set_fg(args.fg or beautiful.fg_normal)
+
+ -- Add __tostring method to metatable.
+ local mt = {}
+ local orig_string = tostring(ret)
+ mt.__tostring = function()
+ return string.format("wibox: %s (%s)",
+ tostring(ret._drawable), orig_string)
+ end
+ ret = setmetatable(ret, mt)
+
+ -- Make sure the wibox is drawn at least once
+ ret.draw()
+
+ -- If a value is not found, look in the drawin
+ setmetatable(ret, {
+ __index = function(self, k)
+ if rawget(self, "get_"..k) then
+ return self["get_"..k](self)
+ else
+ return w[k]
+ end
+ end,
+ __newindex = function(self, k,v)
+ if rawget(self, "set_"..k) then
+ self["set_"..k](self, v)
+ elseif w[k] ~= nil or force_forward[k] then
+ w[k] = v
+ else
+ rawset(self, k, v)
+ end
+ end
+ })
+
+ -- Set other wibox specific arguments
+ if args.bgimage then
+ ret:set_bgimage( args.bgimage )
+ end
+
+ if args.widget then
+ ret:set_widget ( args.widget )
+ end
+
+ if args.screen then
+ ret:set_screen ( args.screen )
+ end
+
+ return ret
+end
+
+--- Redraw a wibox. You should never have to call this explicitely because it is
+-- automatically called when needed.
+-- @param wibox
+-- @function draw
+
+function wibox.mt:__call(...)
+ return new(...)
+end
+
+-- Extend the luaobject
+object.properties(capi.drawin, {
+ getter_class = wibox.object,
+ setter_class = wibox.object,
+ auto_emit = true,
+})
+
+return setmetatable(wibox, wibox.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/wibox/layout/align.lua b/lib/wibox/layout/align.lua
new file mode 100644
index 0000000..92a5582
--- /dev/null
+++ b/lib/wibox/layout/align.lua
@@ -0,0 +1,526 @@
+---------------------------------------------------------------------------
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_layout_defaults_align.svg)
+--
+-- @usage
+--wibox.widget {
+-- generic_widget( 'first' ),
+-- generic_widget( 'second' ),
+-- generic_widget( 'third' ),
+-- layout = wibox.layout.align.horizontal
+--}
+-- @author Uli Schlachter
+-- @copyright 2010 Uli Schlachter
+-- @classmod wibox.layout.align
+---------------------------------------------------------------------------
+
+local table = table
+local pairs = pairs
+local type = type
+local floor = math.floor
+local util = require("awful.util")
+local base = require("wibox.widget.base")
+
+local align = {}
+
+-- Calculate the layout of an align layout.
+-- @param context The context in which we are drawn.
+-- @param width The available width.
+-- @param height The available height.
+function align:layout(context, width, height)
+ local result = {}
+
+ -- Draw will have to deal with all three align modes and should work in a
+ -- way that makes sense if one or two of the widgets are missing (if they
+ -- are all missing, it won't draw anything.) It should also handle the case
+ -- where the fit something that isn't set to expand (for instance the
+ -- outside widgets when the expand mode is "inside" or any of the widgets
+ -- when the expand mode is "none" wants to take up more space than is
+ -- allowed.
+ local size_first = 0
+ -- start with all the space given by the parent, subtract as we go along
+ local size_remains = self._private.dir == "y" and height or width
+ -- This is only set & used if expand ~= "inside" and we have second width.
+ -- It contains the size allocated to the second widget.
+ local size_second
+
+ -- we will prioritize the middle widget unless the expand mode is "inside"
+ -- if it is, we prioritize the first widget by not doing this block also,
+ -- if the second widget doesn't exist, we will prioritise the first one
+ -- instead
+ if self._private.expand ~= "inside" and self._private.second then
+ local w, h = base.fit_widget(self, context, self._private.second, width, height)
+ size_second = self._private.dir == "y" and h or w
+ -- if all the space is taken, skip the rest, and draw just the middle
+ -- widget
+ if size_second >= size_remains then
+ return { base.place_widget_at(self._private.second, 0, 0, width, height) }
+ else
+ -- the middle widget is sized first, the outside widgets are given
+ -- the remaining space if available we will draw later
+ size_remains = floor((size_remains - size_second) / 2)
+ end
+ end
+ if self._private.first then
+ local w, h, _ = width, height, nil
+ -- we use the fit function for the "inside" and "none" modes, but
+ -- ignore it for the "outside" mode, which will force it to expand
+ -- into the remaining space
+ if self._private.expand ~= "outside" then
+ if self._private.dir == "y" then
+ _, h = base.fit_widget(self, context, self._private.first, width, size_remains)
+ size_first = h
+ -- for "inside", the third widget will get a chance to use the
+ -- remaining space, then the middle widget. For "none" we give
+ -- the third widget the remaining space if there was no second
+ -- widget to take up any space (as the first if block is skipped
+ -- if this is the case)
+ if self._private.expand == "inside" or not self._private.second then
+ size_remains = size_remains - h
+ end
+ else
+ w, _ = base.fit_widget(self, context, self._private.first, size_remains, height)
+ size_first = w
+ if self._private.expand == "inside" or not self._private.second then
+ size_remains = size_remains - w
+ end
+ end
+ else
+ if self._private.dir == "y" then
+ h = size_remains
+ else
+ w = size_remains
+ end
+ end
+ table.insert(result, base.place_widget_at(self._private.first, 0, 0, w, h))
+ end
+ -- size_remains will be <= 0 if first used all the space
+ if self._private.third and size_remains > 0 then
+ local w, h, _ = width, height, nil
+ if self._private.expand ~= "outside" then
+ if self._private.dir == "y" then
+ _, h = base.fit_widget(self, context, self._private.third, width, size_remains)
+ -- give the middle widget the rest of the space for "inside" mode
+ if self._private.expand == "inside" then
+ size_remains = size_remains - h
+ end
+ else
+ w, _ = base.fit_widget(self, context, self._private.third, size_remains, height)
+ if self._private.expand == "inside" then
+ size_remains = size_remains - w
+ end
+ end
+ else
+ if self._private.dir == "y" then
+ h = size_remains
+ else
+ w = size_remains
+ end
+ end
+ local x, y = width - w, height - h
+ table.insert(result, base.place_widget_at(self._private.third, x, y, w, h))
+ end
+ -- here we either draw the second widget in the space set aside for it
+ -- in the beginning, or in the remaining space, if it is "inside"
+ if self._private.second and size_remains > 0 then
+ local x, y, w, h = 0, 0, width, height
+ if self._private.expand == "inside" then
+ if self._private.dir == "y" then
+ h = size_remains
+ x, y = 0, size_first
+ else
+ w = size_remains
+ x, y = size_first, 0
+ end
+ else
+ local _
+ if self._private.dir == "y" then
+ _, h = base.fit_widget(self, context, self._private.second, width, size_second)
+ y = floor( (height - h)/2 )
+ else
+ w, _ = base.fit_widget(self, context, self._private.second, size_second, height)
+ x = floor( (width -w)/2 )
+ end
+ end
+ table.insert(result, base.place_widget_at(self._private.second, x, y, w, h))
+ end
+ return result
+end
+
+--- Set the layout's first widget.
+-- This is the widget that is at the left/top
+-- @property first
+
+function align:set_first(widget)
+ if self._private.first == widget then
+ return
+ end
+ self._private.first = widget
+ self:emit_signal("widget::layout_changed")
+end
+
+--- Set the layout's second widget. This is the centered one.
+-- @property second
+
+function align:set_second(widget)
+ if self._private.second == widget then
+ return
+ end
+ self._private.second = widget
+ self:emit_signal("widget::layout_changed")
+end
+
+--- Set the layout's third widget.
+-- This is the widget that is at the right/bottom
+-- @property third
+
+function align:set_third(widget)
+ if self._private.third == widget then
+ return
+ end
+ self._private.third = widget
+ self:emit_signal("widget::layout_changed")
+end
+
+for _, prop in ipairs {"first", "second", "third", "expand" } do
+ align["get_"..prop] = function(self)
+ return self._private[prop]
+ end
+end
+
+--- All direct children of this layout.
+-- This can be used to replace all 3 widgets at once.
+-- @treturn table a list of all widgets
+-- @property children
+
+function align:get_children()
+ return util.from_sparse {self._private.first, self._private.second, self._private.third}
+end
+
+function align:set_children(children)
+ self:set_first(children[1])
+ self:set_second(children[2])
+ self:set_third(children[3])
+end
+
+-- Fit the align layout into the given space. The align layout will
+-- ask for the sum of the sizes of its sub-widgets in its direction
+-- and the largest sized sub widget in the other direction.
+-- @param context The context in which we are fit.
+-- @param orig_width The available width.
+-- @param orig_height The available height.
+function align:fit(context, orig_width, orig_height)
+ local used_in_dir = 0
+ local used_in_other = 0
+
+ for _, v in pairs{self._private.first, self._private.second, self._private.third} do
+ local w, h = base.fit_widget(self, context, v, orig_width, orig_height)
+
+ local max = self._private.dir == "y" and w or h
+ if max > used_in_other then
+ used_in_other = max
+ end
+
+ used_in_dir = used_in_dir + (self._private.dir == "y" and h or w)
+ end
+
+ if self._private.dir == "y" then
+ return used_in_other, used_in_dir
+ end
+ return used_in_dir, used_in_other
+end
+
+--- Set the expand mode which determines how sub widgets expand to take up
+-- unused space.
+--
+-- @tparam[opt=inside] string mode How to use unused space.
+--
+-- * "inside" - Default option. Size of outside widgets is determined using
+-- their fit function. Second, middle, or center widget expands to fill
+-- remaining space.
+-- * "outside" - Center widget is sized using its fit function and placed in
+-- the center of the allowed space. Outside widgets expand (or contract) to
+-- fill remaining space on their side.
+-- * "none" - All widgets are sized using their fit function, drawn to only the
+-- returned space, or remaining space, whichever is smaller. Center widget
+-- gets priority.
+-- @property expand
+
+function align:set_expand(mode)
+ if mode == "none" or mode == "outside" then
+ self._private.expand = mode
+ else
+ self._private.expand = "inside"
+ end
+ self:emit_signal("widget::layout_changed")
+end
+
+function align:reset()
+ for _, v in pairs({ "first", "second", "third" }) do
+ self[v] = nil
+ end
+ self:emit_signal("widget::layout_changed")
+end
+
+local function get_layout(dir, first, second, third)
+ local ret = base.make_widget(nil, nil, {enable_properties = true})
+ ret._private.dir = dir
+
+ for k, v in pairs(align) do
+ if type(v) == "function" then
+ rawset(ret, k, v)
+ end
+ end
+
+ ret:set_expand("inside")
+ ret:set_first(first)
+ ret:set_second(second)
+ ret:set_third(third)
+
+ -- An align layout allow set_children to have empty entries
+ ret.allow_empty_widget = true
+
+ return ret
+end
+
+--- Returns a new horizontal align layout. An align layout can display up to
+-- three widgets. The widget set via :set_left() is left-aligned. :set_right()
+-- sets a widget which will be right-aligned. The remaining space between those
+-- two will be given to the widget set via :set_middle().
+-- @tparam[opt] widget left Widget to be put to the left.
+-- @tparam[opt] widget middle Widget to be put to the middle.
+-- @tparam[opt] widget right Widget to be put to the right.
+function align.horizontal(left, middle, right)
+ local ret = get_layout("x", left, middle, right)
+
+ rawset(ret, "set_left" , ret.set_first )
+ rawset(ret, "set_middle", ret.set_second )
+ rawset(ret, "set_right" , ret.set_third )
+
+ return ret
+end
+
+--- Returns a new vertical align layout. An align layout can display up to
+-- three widgets. The widget set via :set_top() is top-aligned. :set_bottom()
+-- sets a widget which will be bottom-aligned. The remaining space between those
+-- two will be given to the widget set via :set_middle().
+-- @tparam[opt] widget top Widget to be put to the top.
+-- @tparam[opt] widget middle Widget to be put to the middle.
+-- @tparam[opt] widget bottom Widget to be put to the right.
+function align.vertical(top, middle, bottom)
+ local ret = get_layout("y", top, middle, bottom)
+
+ rawset(ret, "set_top" , ret.set_first )
+ rawset(ret, "set_middle", ret.set_second )
+ rawset(ret, "set_bottom", ret.set_third )
+
+ return ret
+end
+
+--Imported documentation
+
+
+--- Get a widex index.
+-- @param widget The widget to look for
+-- @param[opt] recursive Also check sub-widgets
+-- @param[opt] ... Aditional widgets to add at the end of the \"path\"
+-- @return The index
+-- @return The parent layout
+-- @return The path between \"self\" and \"widget\"
+-- @function index
+
+--- Get all direct and indirect children widgets.
+-- This will scan all containers recursively to find widgets
+-- Warning: This method it prone to stack overflow id the widget, or any of its
+-- children, contain (directly or indirectly) itself.
+-- @treturn table The children
+-- @function get_all_children
+
+--- Set a declarative widget hierarchy description.
+-- See [The declarative layout system](../documentation/03-declarative-layout.md.html)
+-- @param args An array containing the widgets disposition
+-- @function setup
+
+--- Force a widget height.
+-- @property forced_height
+-- @tparam number|nil height The height (`nil` for automatic)
+
+--- Force a widget width.
+-- @property forced_width
+-- @tparam number|nil width The width (`nil` for automatic)
+
+--- The widget opacity (transparency).
+-- @property opacity
+-- @tparam[opt=1] number opacity The opacity (between 0 and 1)
+
+--- The widget visibility.
+-- @property visible
+-- @param boolean
+
+--- Set/get a widget's buttons.
+-- @param _buttons The table of buttons that should bind to the widget.
+-- @function buttons
+
+--- Emit a signal and ensure all parent widgets in the hierarchies also
+-- forward the signal. This is useful to track signals when there is a dynamic
+-- set of containers and layouts wrapping the widget.
+-- @tparam string signal_name
+-- @param ... Other arguments
+-- @function emit_signal_recursive
+
+--- When the layout (size) change.
+-- This signal is emitted when the previous results of `:layout()` and `:fit()`
+-- are no longer valid. Unless this signal is emitted, `:layout()` and `:fit()`
+-- must return the same result when called with the same arguments.
+-- @signal widget::layout_changed
+-- @see widget::redraw_needed
+
+--- When the widget content changed.
+-- This signal is emitted when the content of the widget changes. The widget will
+-- be redrawn, it is not re-layouted. Put differently, it is assumed that
+-- `:layout()` and `:fit()` would still return the same results as before.
+-- @signal widget::redraw_needed
+-- @see widget::layout_changed
+
+--- When a mouse button is pressed over the widget.
+-- @signal button::press
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When a mouse button is released over the widget.
+-- @signal button::release
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse enter a widget.
+-- @signal mouse::enter
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse leave a widget.
+-- @signal mouse::leave
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+
+--Imported documentation
+
+
+--- Disconnect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback that should be disconnected
+-- @function disconnect_signal
+
+--- Emit a signal.
+--
+-- @tparam string name The name of the signal
+-- @param ... Extra arguments for the callback functions. Each connected
+-- function receives the object as first argument and then any extra arguments
+-- that are given to emit_signal()
+-- @function emit_signal
+
+--- Connect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function connect_signal
+
+--- Connect to a signal weakly. This allows the callback function to be garbage
+-- collected and automatically disconnects the signal when that happens.
+--
+-- **Warning:**
+-- Only use this function if you really, really, really know what you
+-- are doing.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function weak_connect_signal
+
+
+return align
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/wibox/layout/constraint.lua b/lib/wibox/layout/constraint.lua
new file mode 100644
index 0000000..5333b38
--- /dev/null
+++ b/lib/wibox/layout/constraint.lua
@@ -0,0 +1,17 @@
+---------------------------------------------------------------------------
+-- This class has been moved to `wibox.container.`
+--
+-- @author Lukáš Hrázký
+-- @copyright 2012 Lukáš Hrázký
+-- @classmod wibox.layout.constraint
+---------------------------------------------------------------------------
+
+local util = require("awful.util")
+
+return util.deprecate_class(
+ require("wibox.container.constraint"),
+ "wibox.layout.constraint",
+ "wibox.container.constraint"
+)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/wibox/layout/fixed.lua b/lib/wibox/layout/fixed.lua
new file mode 100644
index 0000000..7258438
--- /dev/null
+++ b/lib/wibox/layout/fixed.lua
@@ -0,0 +1,585 @@
+---------------------------------------------------------------------------
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_layout_defaults_fixed.svg)
+--
+-- @usage
+--wibox.widget {
+-- generic_widget( 'first' ),
+-- generic_widget( 'second' ),
+-- generic_widget( 'third' ),
+-- layout = wibox.layout.fixed.horizontal
+--}
+-- @author Uli Schlachter
+-- @copyright 2010 Uli Schlachter
+-- @classmod wibox.layout.fixed
+---------------------------------------------------------------------------
+
+local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
+local base = require("wibox.widget.base")
+local table = table
+local pairs = pairs
+local util = require("awful.util")
+
+local fixed = {}
+
+--Imported documentation
+
+--- Set a widget at a specific index, replace the current one.
+-- **Signal:** widget::replaced The argument is the new widget and the old one
+-- and the index.
+-- @tparam number index A widget or a widget index
+-- @param widget2 The widget to take the place of the first one
+-- @treturn boolean If the operation is successful
+-- @name set
+-- @class function
+
+--- Replace the first instance of `widget` in the layout with `widget2`.
+-- **Signal:** widget::replaced The argument is the new widget and the old one
+-- and the index.
+-- @param widget The widget to replace
+-- @param widget2 The widget to replace `widget` with
+-- @tparam[opt=false] boolean recursive Digg in all compatible layouts to find the widget.
+-- @treturn boolean If the operation is successful
+-- @name replace_widget
+-- @class function
+
+--- Swap 2 widgets in a layout.
+-- **Signal:** widget::swapped The arguments are both widgets and both (new) indexes.
+-- @tparam number index1 The first widget index
+-- @tparam number index2 The second widget index
+-- @treturn boolean If the operation is successful
+-- @name swap
+-- @class function
+
+--- Swap 2 widgets in a layout.
+-- If widget1 is present multiple time, only the first instance is swapped
+-- **Signal:** widget::swapped The arguments are both widgets and both (new) indexes.
+-- if the layouts not the same, then only `widget::replaced` will be emitted.
+-- @param widget1 The first widget
+-- @param widget2 The second widget
+-- @tparam[opt=false] boolean recursive Digg in all compatible layouts to find the widget.
+-- @treturn boolean If the operation is successful
+-- @name swap_widgets
+-- @class function
+
+--- Get all direct children of this layout.
+-- @param layout The layout you are modifying.
+-- @property children
+
+--- Reset a ratio layout. This removes all widgets from the layout.
+-- **Signal:** widget::reset
+-- @param layout The layout you are modifying.
+-- @name reset
+-- @class function
+
+
+-- Layout a fixed layout. Each widget gets just the space it asks for.
+-- @param context The context in which we are drawn.
+-- @param width The available width.
+-- @param height The available height.
+function fixed:layout(context, width, height)
+ local result = {}
+ local pos,spacing = 0, self._private.spacing
+
+ for k, v in pairs(self._private.widgets) do
+ local x, y, w, h, _
+ if self._private.dir == "y" then
+ x, y = 0, pos
+ w, h = width, height - pos
+ if k ~= #self._private.widgets or not self._private.fill_space then
+ _, h = base.fit_widget(self, context, v, w, h);
+ end
+ pos = pos + h + spacing
+ else
+ x, y = pos, 0
+ w, h = width - pos, height
+ if k ~= #self._private.widgets or not self._private.fill_space then
+ w, _ = base.fit_widget(self, context, v, w, h);
+ end
+ pos = pos + w + spacing
+ end
+
+ if (self._private.dir == "y" and pos-spacing > height) or
+ (self._private.dir ~= "y" and pos-spacing > width) then
+ break
+ end
+ table.insert(result, base.place_widget_at(v, x, y, w, h))
+ end
+ return result
+end
+
+--- Add some widgets to the given fixed layout
+-- @param ... Widgets that should be added (must at least be one)
+function fixed:add(...)
+ -- No table.pack in Lua 5.1 :-(
+ local args = { n=select('#', ...), ... }
+ assert(args.n > 0, "need at least one widget to add")
+ for i=1, args.n do
+ base.check_widget(args[i])
+ table.insert(self._private.widgets, args[i])
+ end
+ self:emit_signal("widget::layout_changed")
+end
+
+
+--- Remove a widget from the layout
+-- @tparam number index The widget index to remove
+-- @treturn boolean index If the operation is successful
+function fixed:remove(index)
+ if not index or index < 1 or index > #self._private.widgets then return false end
+
+ table.remove(self._private.widgets, index)
+
+ self:emit_signal("widget::layout_changed")
+
+ return true
+end
+
+--- Remove one or more widgets from the layout
+-- The last parameter can be a boolean, forcing a recursive seach of the
+-- widget(s) to remove.
+-- @param widget ... Widgets that should be removed (must at least be one)
+-- @treturn boolean If the operation is successful
+function fixed:remove_widgets(...)
+ local args = { ... }
+
+ local recursive = type(args[#args]) == "boolean" and args[#args]
+
+ local ret = true
+ for k, rem_widget in ipairs(args) do
+ if recursive and k == #args then break end
+
+ local idx, l = self:index(rem_widget, recursive)
+
+ if idx and l and l.remove then
+ l:remove(idx, false)
+ else
+ ret = false
+ end
+
+ end
+
+ return #args > (recursive and 1 or 0) and ret
+end
+
+function fixed:get_children()
+ return self._private.widgets
+end
+
+function fixed:set_children(children)
+ self:reset()
+ if #children > 0 then
+ self:add(unpack(children))
+ end
+end
+
+--- Replace the first instance of `widget` in the layout with `widget2`
+-- @param widget The widget to replace
+-- @param widget2 The widget to replace `widget` with
+-- @tparam[opt=false] boolean recursive Digg in all compatible layouts to find the widget.
+-- @treturn boolean If the operation is successful
+function fixed:replace_widget(widget, widget2, recursive)
+ local idx, l = self:index(widget, recursive)
+
+ if idx and l then
+ l:set(idx, widget2)
+ return true
+ end
+
+ return false
+end
+
+function fixed:swap(index1, index2)
+ if not index1 or not index2 or index1 > #self._private.widgets
+ or index2 > #self._private.widgets then
+ return false
+ end
+
+ local widget1, widget2 = self._private.widgets[index1], self._private.widgets[index2]
+
+ self:set(index1, widget2)
+ self:set(index2, widget1)
+
+ self:emit_signal("widget::swapped", widget1, widget2, index2, index1)
+
+ return true
+end
+
+function fixed:swap_widgets(widget1, widget2, recursive)
+ base.check_widget(widget1)
+ base.check_widget(widget2)
+
+ local idx1, l1 = self:index(widget1, recursive)
+ local idx2, l2 = self:index(widget2, recursive)
+
+ if idx1 and l1 and idx2 and l2 and (l1.set or l1.set_widget) and (l2.set or l2.set_widget) then
+ if l1.set then
+ l1:set(idx1, widget2)
+ if l1 == self then
+ self:emit_signal("widget::swapped", widget1, widget2, idx2, idx1)
+ end
+ elseif l1.set_widget then
+ l1:set_widget(widget2)
+ end
+ if l2.set then
+ l2:set(idx2, widget1)
+ if l2 == self then
+ self:emit_signal("widget::swapped", widget1, widget2, idx2, idx1)
+ end
+ elseif l2.set_widget then
+ l2:set_widget(widget1)
+ end
+
+ return true
+ end
+
+ return false
+end
+
+function fixed:set(index, widget2)
+ if (not widget2) or (not self._private.widgets[index]) then return false end
+
+ base.check_widget(widget2)
+
+ local w = self._private.widgets[index]
+
+ self._private.widgets[index] = widget2
+
+ self:emit_signal("widget::layout_changed")
+ self:emit_signal("widget::replaced", widget2, w, index)
+
+ return true
+end
+
+--- Insert a new widget in the layout at position `index`
+-- **Signal:** widget::inserted The arguments are the widget and the index
+-- @tparam number index The position
+-- @param widget The widget
+-- @treturn boolean If the operation is successful
+function fixed:insert(index, widget)
+ if not index or index < 1 or index > #self._private.widgets + 1 then return false end
+
+ base.check_widget(widget)
+ table.insert(self._private.widgets, index, widget)
+ self:emit_signal("widget::layout_changed")
+ self:emit_signal("widget::inserted", widget, #self._private.widgets)
+
+ return true
+end
+
+-- Fit the fixed layout into the given space
+-- @param context The context in which we are fit.
+-- @param orig_width The available width.
+-- @param orig_height The available height.
+function fixed:fit(context, orig_width, orig_height)
+ local width, height = orig_width, orig_height
+ local used_in_dir, used_max = 0, 0
+
+ for _, v in pairs(self._private.widgets) do
+ local w, h = base.fit_widget(self, context, v, width, height)
+ local in_dir, max
+ if self._private.dir == "y" then
+ max, in_dir = w, h
+ height = height - in_dir
+ else
+ in_dir, max = w, h
+ width = width - in_dir
+ end
+ if max > used_max then
+ used_max = max
+ end
+ used_in_dir = used_in_dir + in_dir
+
+ if width <= 0 or height <= 0 then
+ if self._private.dir == "y" then
+ used_in_dir = orig_height
+ else
+ used_in_dir = orig_width
+ end
+ break
+ end
+ end
+
+ local spacing = self._private.spacing * (#self._private.widgets-1)
+
+ if self._private.dir == "y" then
+ return used_max, used_in_dir + spacing
+ end
+ return used_in_dir + spacing, used_max
+end
+
+function fixed:reset()
+ self._private.widgets = {}
+ self:emit_signal("widget::layout_changed")
+ self:emit_signal("widget::reseted")
+end
+
+--- Set the layout's fill_space property. If this property is true, the last
+-- widget will get all the space that is left. If this is false, the last widget
+-- won't be handled specially and there can be space left unused.
+-- @property fill_space
+
+function fixed:fill_space(val)
+ if self._private.fill_space ~= val then
+ self._private.fill_space = not not val
+ self:emit_signal("widget::layout_changed")
+ end
+end
+
+local function get_layout(dir, widget1, ...)
+ local ret = base.make_widget(nil, nil, {enable_properties = true})
+
+ util.table.crush(ret, fixed, true)
+
+ ret._private.dir = dir
+ ret._private.widgets = {}
+ ret:set_spacing(0)
+ ret:fill_space(false)
+
+ if widget1 then
+ ret:add(widget1, ...)
+ end
+
+ return ret
+end
+
+--- Returns a new horizontal fixed layout. Each widget will get as much space as it
+-- asks for and each widget will be drawn next to its neighboring widget.
+-- Widgets can be added via :add() or as arguments to this function.
+-- @tparam widget ... Widgets that should be added to the layout.
+-- @function wibox.layout.fixed.horizontal
+function fixed.horizontal(...)
+ return get_layout("x", ...)
+end
+
+--- Returns a new vertical fixed layout. Each widget will get as much space as it
+-- asks for and each widget will be drawn next to its neighboring widget.
+-- Widgets can be added via :add() or as arguments to this function.
+-- @tparam widget ... Widgets that should be added to the layout.
+-- @function wibox.layout.fixed.vertical
+function fixed.vertical(...)
+ return get_layout("y", ...)
+end
+
+--- Add spacing between each layout widgets
+-- @property spacing
+-- @tparam number spacing Spacing between widgets.
+
+function fixed:set_spacing(spacing)
+ if self._private.spacing ~= spacing then
+ self._private.spacing = spacing
+ self:emit_signal("widget::layout_changed")
+ end
+end
+
+function fixed:get_spacing()
+ return self._private.spacing or 0
+end
+
+--Imported documentation
+
+
+--- Get a widex index.
+-- @param widget The widget to look for
+-- @param[opt] recursive Also check sub-widgets
+-- @param[opt] ... Aditional widgets to add at the end of the \"path\"
+-- @return The index
+-- @return The parent layout
+-- @return The path between \"self\" and \"widget\"
+-- @function index
+
+--- Get all direct and indirect children widgets.
+-- This will scan all containers recursively to find widgets
+-- Warning: This method it prone to stack overflow id the widget, or any of its
+-- children, contain (directly or indirectly) itself.
+-- @treturn table The children
+-- @function get_all_children
+
+--- Set a declarative widget hierarchy description.
+-- See [The declarative layout system](../documentation/03-declarative-layout.md.html)
+-- @param args An array containing the widgets disposition
+-- @function setup
+
+--- Force a widget height.
+-- @property forced_height
+-- @tparam number|nil height The height (`nil` for automatic)
+
+--- Force a widget width.
+-- @property forced_width
+-- @tparam number|nil width The width (`nil` for automatic)
+
+--- The widget opacity (transparency).
+-- @property opacity
+-- @tparam[opt=1] number opacity The opacity (between 0 and 1)
+
+--- The widget visibility.
+-- @property visible
+-- @param boolean
+
+--- Set/get a widget's buttons.
+-- @param _buttons The table of buttons that should bind to the widget.
+-- @function buttons
+
+--- Emit a signal and ensure all parent widgets in the hierarchies also
+-- forward the signal. This is useful to track signals when there is a dynamic
+-- set of containers and layouts wrapping the widget.
+-- @tparam string signal_name
+-- @param ... Other arguments
+-- @function emit_signal_recursive
+
+--- When the layout (size) change.
+-- This signal is emitted when the previous results of `:layout()` and `:fit()`
+-- are no longer valid. Unless this signal is emitted, `:layout()` and `:fit()`
+-- must return the same result when called with the same arguments.
+-- @signal widget::layout_changed
+-- @see widget::redraw_needed
+
+--- When the widget content changed.
+-- This signal is emitted when the content of the widget changes. The widget will
+-- be redrawn, it is not re-layouted. Put differently, it is assumed that
+-- `:layout()` and `:fit()` would still return the same results as before.
+-- @signal widget::redraw_needed
+-- @see widget::layout_changed
+
+--- When a mouse button is pressed over the widget.
+-- @signal button::press
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When a mouse button is released over the widget.
+-- @signal button::release
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse enter a widget.
+-- @signal mouse::enter
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse leave a widget.
+-- @signal mouse::leave
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+
+--Imported documentation
+
+
+--- Disconnect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback that should be disconnected
+-- @function disconnect_signal
+
+--- Emit a signal.
+--
+-- @tparam string name The name of the signal
+-- @param ... Extra arguments for the callback functions. Each connected
+-- function receives the object as first argument and then any extra arguments
+-- that are given to emit_signal()
+-- @function emit_signal
+
+--- Connect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function connect_signal
+
+--- Connect to a signal weakly. This allows the callback function to be garbage
+-- collected and automatically disconnects the signal when that happens.
+--
+-- **Warning:**
+-- Only use this function if you really, really, really know what you
+-- are doing.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function weak_connect_signal
+
+
+return fixed
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/wibox/layout/flex.lua b/lib/wibox/layout/flex.lua
new file mode 100644
index 0000000..1326f31
--- /dev/null
+++ b/lib/wibox/layout/flex.lua
@@ -0,0 +1,429 @@
+---------------------------------------------------------------------------
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_layout_defaults_flex.svg)
+--
+-- @usage
+--wibox.widget {
+-- generic_widget( 'first' ),
+-- generic_widget( 'second' ),
+-- generic_widget( 'third' ),
+-- layout = wibox.layout.flex.horizontal
+--}
+-- @author Uli Schlachter
+-- @copyright 2010 Uli Schlachter
+-- @classmod wibox.layout.flex
+---------------------------------------------------------------------------
+
+local base = require("wibox.widget.base")
+local fixed = require("wibox.layout.fixed")
+local table = table
+local pairs = pairs
+local floor = math.floor
+local util = require("awful.util")
+
+local flex = {}
+
+--Imported documentation
+
+--- Set a widget at a specific index, replace the current one.
+-- **Signal:** widget::replaced The argument is the new widget and the old one
+-- and the index.
+-- @tparam number index A widget or a widget index
+-- @param widget2 The widget to take the place of the first one
+-- @treturn boolean If the operation is successful
+-- @name set
+-- @class function
+
+--- Replace the first instance of `widget` in the layout with `widget2`.
+-- **Signal:** widget::replaced The argument is the new widget and the old one
+-- and the index.
+-- @param widget The widget to replace
+-- @param widget2 The widget to replace `widget` with
+-- @tparam[opt=false] boolean recursive Digg in all compatible layouts to find the widget.
+-- @treturn boolean If the operation is successful
+-- @name replace_widget
+-- @class function
+
+--- Swap 2 widgets in a layout.
+-- **Signal:** widget::swapped The arguments are both widgets and both (new) indexes.
+-- @tparam number index1 The first widget index
+-- @tparam number index2 The second widget index
+-- @treturn boolean If the operation is successful
+-- @name swap
+-- @class function
+
+--- Swap 2 widgets in a layout.
+-- If widget1 is present multiple time, only the first instance is swapped
+-- **Signal:** widget::swapped The arguments are both widgets and both (new) indexes.
+-- if the layouts not the same, then only `widget::replaced` will be emitted.
+-- @param widget1 The first widget
+-- @param widget2 The second widget
+-- @tparam[opt=false] boolean recursive Digg in all compatible layouts to find the widget.
+-- @treturn boolean If the operation is successful
+-- @name swap_widgets
+-- @class function
+
+--- Get all direct children of this layout.
+-- @param layout The layout you are modifying.
+-- @property children
+
+--- Reset a ratio layout. This removes all widgets from the layout.
+-- **Signal:** widget::reset
+-- @param layout The layout you are modifying.
+-- @name reset
+-- @class function
+
+
+--- Replace the layout children
+-- @tparam table children A table composed of valid widgets
+-- @name set_children
+-- @class function
+
+--- Add some widgets to the given fixed layout
+-- @param layout The layout you are modifying.
+-- @tparam widget ... Widgets that should be added (must at least be one)
+-- @name add
+-- @class function
+
+--- Remove a widget from the layout
+-- @tparam index The widget index to remove
+-- @treturn boolean index If the operation is successful
+-- @name remove
+-- @class function
+
+--- Remove one or more widgets from the layout
+-- The last parameter can be a boolean, forcing a recursive seach of the
+-- widget(s) to remove.
+-- @param widget ... Widgets that should be removed (must at least be one)
+-- @treturn boolean If the operation is successful
+-- @name remove_widgets
+-- @class function
+
+--- Insert a new widget in the layout at position `index`
+-- @tparam number index The position
+-- @param widget The widget
+-- @treturn boolean If the operation is successful
+-- @name insert
+-- @class function
+
+function flex:layout(_, width, height)
+ local result = {}
+ local pos,spacing = 0, self._private.spacing
+ local num = #self._private.widgets
+ local total_spacing = (spacing*(num-1))
+
+ local space_per_item
+ if self._private.dir == "y" then
+ space_per_item = height / num - total_spacing/num
+ else
+ space_per_item = width / num - total_spacing/num
+ end
+
+ if self._private.max_widget_size then
+ space_per_item = math.min(space_per_item, self._private.max_widget_size)
+ end
+
+ for _, v in pairs(self._private.widgets) do
+ local x, y, w, h
+ if self._private.dir == "y" then
+ x, y = 0, util.round(pos)
+ w, h = width, floor(space_per_item)
+ else
+ x, y = util.round(pos), 0
+ w, h = floor(space_per_item), height
+ end
+
+ table.insert(result, base.place_widget_at(v, x, y, w, h))
+
+ pos = pos + space_per_item + spacing
+
+ if (self._private.dir == "y" and pos-spacing >= height) or
+ (self._private.dir ~= "y" and pos-spacing >= width) then
+ break
+ end
+ end
+
+ return result
+end
+
+-- Fit the flex layout into the given space.
+-- @param context The context in which we are fit.
+-- @param orig_width The available width.
+-- @param orig_height The available height.
+function flex:fit(context, orig_width, orig_height)
+ local used_in_dir = 0
+ local used_in_other = 0
+
+ -- Figure out the maximum size we can give out to sub-widgets
+ local sub_height = self._private.dir == "x" and orig_height or orig_height / #self._private.widgets
+ local sub_width = self._private.dir == "y" and orig_width or orig_width / #self._private.widgets
+
+ for _, v in pairs(self._private.widgets) do
+ local w, h = base.fit_widget(self, context, v, sub_width, sub_height)
+
+ local max = self._private.dir == "y" and w or h
+ if max > used_in_other then
+ used_in_other = max
+ end
+
+ used_in_dir = used_in_dir + (self._private.dir == "y" and h or w)
+ end
+
+ if self._private.max_widget_size then
+ used_in_dir = math.min(used_in_dir,
+ #self._private.widgets * self._private.max_widget_size)
+ end
+
+ local spacing = self._private.spacing * (#self._private.widgets-1)
+
+ if self._private.dir == "y" then
+ return used_in_other, used_in_dir + spacing
+ end
+ return used_in_dir + spacing, used_in_other
+end
+
+--- Set the maximum size the widgets in this layout will take.
+--That is, maximum width for horizontal and maximum height for vertical.
+-- @property max_widget_size
+-- @param number
+
+function flex:set_max_widget_size(val)
+ if self._private.max_widget_size ~= val then
+ self._private.max_widget_size = val
+ self:emit_signal("widget::layout_changed")
+ end
+end
+
+local function get_layout(dir, widget1, ...)
+ local ret = fixed[dir](widget1, ...)
+
+ util.table.crush(ret, flex, true)
+
+ ret._private.fill_space = nil
+
+ return ret
+end
+
+--- Returns a new horizontal flex layout. A flex layout shares the available space
+-- equally among all widgets. Widgets can be added via :add(widget).
+-- @tparam widget ... Widgets that should be added to the layout.
+-- @function wibox.layout.flex.horizontal
+function flex.horizontal(...)
+ return get_layout("horizontal", ...)
+end
+
+--- Returns a new vertical flex layout. A flex layout shares the available space
+-- equally among all widgets. Widgets can be added via :add(widget).
+-- @tparam widget ... Widgets that should be added to the layout.
+-- @function wibox.layout.flex.vertical
+function flex.vertical(...)
+ return get_layout("vertical", ...)
+end
+
+--Imported documentation
+
+
+--- Get a widex index.
+-- @param widget The widget to look for
+-- @param[opt] recursive Also check sub-widgets
+-- @param[opt] ... Aditional widgets to add at the end of the \"path\"
+-- @return The index
+-- @return The parent layout
+-- @return The path between \"self\" and \"widget\"
+-- @function index
+
+--- Get all direct and indirect children widgets.
+-- This will scan all containers recursively to find widgets
+-- Warning: This method it prone to stack overflow id the widget, or any of its
+-- children, contain (directly or indirectly) itself.
+-- @treturn table The children
+-- @function get_all_children
+
+--- Set a declarative widget hierarchy description.
+-- See [The declarative layout system](../documentation/03-declarative-layout.md.html)
+-- @param args An array containing the widgets disposition
+-- @function setup
+
+--- Force a widget height.
+-- @property forced_height
+-- @tparam number|nil height The height (`nil` for automatic)
+
+--- Force a widget width.
+-- @property forced_width
+-- @tparam number|nil width The width (`nil` for automatic)
+
+--- The widget opacity (transparency).
+-- @property opacity
+-- @tparam[opt=1] number opacity The opacity (between 0 and 1)
+
+--- The widget visibility.
+-- @property visible
+-- @param boolean
+
+--- Set/get a widget's buttons.
+-- @param _buttons The table of buttons that should bind to the widget.
+-- @function buttons
+
+--- Emit a signal and ensure all parent widgets in the hierarchies also
+-- forward the signal. This is useful to track signals when there is a dynamic
+-- set of containers and layouts wrapping the widget.
+-- @tparam string signal_name
+-- @param ... Other arguments
+-- @function emit_signal_recursive
+
+--- When the layout (size) change.
+-- This signal is emitted when the previous results of `:layout()` and `:fit()`
+-- are no longer valid. Unless this signal is emitted, `:layout()` and `:fit()`
+-- must return the same result when called with the same arguments.
+-- @signal widget::layout_changed
+-- @see widget::redraw_needed
+
+--- When the widget content changed.
+-- This signal is emitted when the content of the widget changes. The widget will
+-- be redrawn, it is not re-layouted. Put differently, it is assumed that
+-- `:layout()` and `:fit()` would still return the same results as before.
+-- @signal widget::redraw_needed
+-- @see widget::layout_changed
+
+--- When a mouse button is pressed over the widget.
+-- @signal button::press
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When a mouse button is released over the widget.
+-- @signal button::release
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse enter a widget.
+-- @signal mouse::enter
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse leave a widget.
+-- @signal mouse::leave
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+
+--Imported documentation
+
+
+--- Disconnect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback that should be disconnected
+-- @function disconnect_signal
+
+--- Emit a signal.
+--
+-- @tparam string name The name of the signal
+-- @param ... Extra arguments for the callback functions. Each connected
+-- function receives the object as first argument and then any extra arguments
+-- that are given to emit_signal()
+-- @function emit_signal
+
+--- Connect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function connect_signal
+
+--- Connect to a signal weakly. This allows the callback function to be garbage
+-- collected and automatically disconnects the signal when that happens.
+--
+-- **Warning:**
+-- Only use this function if you really, really, really know what you
+-- are doing.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function weak_connect_signal
+
+
+return flex
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/wibox/layout/init.lua b/lib/wibox/layout/init.lua
new file mode 100644
index 0000000..4a9b006
--- /dev/null
+++ b/lib/wibox/layout/init.lua
@@ -0,0 +1,23 @@
+---------------------------------------------------------------------------
+--- Collection of layouts that can be used in widget boxes
+--
+-- @author Uli Schlachter
+-- @copyright 2010 Uli Schlachter
+-- @classmod wibox.layout
+---------------------------------------------------------------------------
+local base = require("wibox.widget.base")
+
+return setmetatable({
+ fixed = require("wibox.layout.fixed");
+ align = require("wibox.layout.align");
+ flex = require("wibox.layout.flex");
+ rotate = require("wibox.layout.rotate");
+ margin = require("wibox.layout.margin");
+ mirror = require("wibox.layout.mirror");
+ constraint = require("wibox.layout.constraint");
+ scroll = require("wibox.layout.scroll");
+ ratio = require("wibox.layout.ratio");
+ stack = require("wibox.layout.stack");
+}, {__call = function(_, args) return base.make_widget_declarative(args) end})
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/wibox/layout/margin.lua b/lib/wibox/layout/margin.lua
new file mode 100644
index 0000000..8d2eba2
--- /dev/null
+++ b/lib/wibox/layout/margin.lua
@@ -0,0 +1,17 @@
+---------------------------------------------------------------------------
+-- This class has been moved to `wibox.container.margin`
+--
+-- @author Uli Schlachter
+-- @copyright 2010 Uli Schlachter
+-- @classmod wibox.layout.margin
+---------------------------------------------------------------------------
+
+local util = require("awful.util")
+
+return util.deprecate_class(
+ require("wibox.container.margin"),
+ "wibox.layout.margin",
+ "wibox.container.margin"
+)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/wibox/layout/mirror.lua b/lib/wibox/layout/mirror.lua
new file mode 100644
index 0000000..16be754
--- /dev/null
+++ b/lib/wibox/layout/mirror.lua
@@ -0,0 +1,17 @@
+---------------------------------------------------------------------------
+-- This class has been moved to `wibox.container.mirror`
+--
+-- @author dodo
+-- @copyright 2012 dodo
+-- @classmod wibox.layout.mirror
+---------------------------------------------------------------------------
+
+local util = require("awful.util")
+
+return util.deprecate_class(
+ require("wibox.container.mirror"),
+ "wibox.layout.mirror",
+ "wibox.container.mirror"
+)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/wibox/layout/ratio.lua b/lib/wibox/layout/ratio.lua
new file mode 100644
index 0000000..bcae443
--- /dev/null
+++ b/lib/wibox/layout/ratio.lua
@@ -0,0 +1,583 @@
+---------------------------------------------------------------------------
+--- A layout filling all the available space. Each widget is assigned a
+-- ratio (percentage) of the total space. Multiple methods are available to
+-- ajust this ratio.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_layout_defaults_ratio.svg)
+--
+-- @usage
+--local w = wibox.widget {
+-- generic_widget( 'first' ),
+-- generic_widget( 'second' ),
+-- generic_widget( 'third' ),
+-- layout = wibox.layout.ratio.horizontal
+--}
+--w:ajust_ratio(2, 0.44, 0.33, 0.22)
+-- @author Emmanuel Lepage Vallee
+-- @copyright 2016 Emmanuel Lepage Vallee
+-- @classmod wibox.layout.ratio
+---------------------------------------------------------------------------
+
+local base = require("wibox.widget.base" )
+local flex = require("wibox.layout.flex" )
+local table = table
+local pairs = pairs
+local floor = math.floor
+local util = require("awful.util")
+
+local ratio = {}
+
+--Imported documentation
+
+--- Set a widget at a specific index, replace the current one.
+-- **Signal:** widget::replaced The argument is the new widget and the old one
+-- and the index.
+-- @tparam number index A widget or a widget index
+-- @param widget2 The widget to take the place of the first one
+-- @treturn boolean If the operation is successful
+-- @name set
+-- @class function
+
+--- Replace the first instance of `widget` in the layout with `widget2`.
+-- **Signal:** widget::replaced The argument is the new widget and the old one
+-- and the index.
+-- @param widget The widget to replace
+-- @param widget2 The widget to replace `widget` with
+-- @tparam[opt=false] boolean recursive Digg in all compatible layouts to find the widget.
+-- @treturn boolean If the operation is successful
+-- @name replace_widget
+-- @class function
+
+--- Swap 2 widgets in a layout.
+-- **Signal:** widget::swapped The arguments are both widgets and both (new) indexes.
+-- @tparam number index1 The first widget index
+-- @tparam number index2 The second widget index
+-- @treturn boolean If the operation is successful
+-- @name swap
+-- @class function
+
+--- Swap 2 widgets in a layout.
+-- If widget1 is present multiple time, only the first instance is swapped
+-- **Signal:** widget::swapped The arguments are both widgets and both (new) indexes.
+-- if the layouts not the same, then only `widget::replaced` will be emitted.
+-- @param widget1 The first widget
+-- @param widget2 The second widget
+-- @tparam[opt=false] boolean recursive Digg in all compatible layouts to find the widget.
+-- @treturn boolean If the operation is successful
+-- @name swap_widgets
+-- @class function
+
+--- Get all direct children of this layout.
+-- @param layout The layout you are modifying.
+-- @property children
+
+--- Reset a ratio layout. This removes all widgets from the layout.
+-- **Signal:** widget::reset
+-- @param layout The layout you are modifying.
+-- @name reset
+-- @class function
+
+
+-- Compute the sum of all ratio (ideally, it should be 1)
+local function gen_sum(self, i_s, i_e)
+ local sum, new_w = 0,0
+
+ for i = i_s or 1, i_e or #self._private.widgets do
+ if self._private.ratios[i] then
+ sum = sum + self._private.ratios[i]
+ else
+ new_w = new_w + 1
+ end
+ end
+
+ return sum, new_w
+end
+
+-- The ratios are expressed as percentages. For this to work, the sum of all
+-- ratio must be 1. This function attempt to ajust them. Space can be taken
+-- from or added to a ratio when widgets are being added or removed. If a
+-- specific ratio must be enforced for a widget, it has to be done with the
+-- `ajust_ratio` method after each insertion or deletion
+local function normalize(self)
+ local count = #self._private.widgets
+ if count == 0 then return end
+
+ -- Instead of adding "if" everywhere, just handle this common case
+ if count == 1 then
+ self._private.ratios = { 1 }
+ return
+ end
+
+ local sum, new_w = gen_sum(self)
+ local old_count = #self._private.widgets - new_w
+
+ local to_add = (sum == 0) and 1 or (sum / old_count)
+
+ -- Make sure all widgets have a ratio
+ for i=1, #self._private.widgets do
+ if not self._private.ratios[i] then
+ self._private.ratios[i] = to_add
+ end
+ end
+
+ sum = sum + to_add*new_w
+
+ local delta, new_sum = (1 - sum) / count,0
+
+ -- Increase or decrease each ratio so it the sum become 1
+ for i=1, #self._private.widgets do
+ self._private.ratios[i] = self._private.ratios[i] + delta
+ new_sum = new_sum + self._private.ratios[i]
+ end
+
+ -- Floating points is not an exact science, but it should still be close
+ -- to 1.00.
+ assert(new_sum > 0.99 and new_sum < 1.01)
+end
+
+function ratio:layout(_, width, height)
+ local result = {}
+ local pos,spacing = 0, self._private.spacing
+
+ for k, v in ipairs(self._private.widgets) do
+ local space
+ local x, y, w, h
+
+ if self._private.dir == "y" then
+ space = height * self._private.ratios[k]
+ x, y = 0, util.round(pos)
+ w, h = width, floor(space)
+ else
+ space = width * self._private.ratios[k]
+ x, y = util.round(pos), 0
+ w, h = floor(space), height
+ end
+
+ table.insert(result, base.place_widget_at(v, x, y, w, h))
+
+ pos = pos + space + spacing
+
+ -- Make sure all widgets fit in the layout, if they aren't, something
+ -- went wrong
+ if (self._private.dir == "y" and util.round(pos) >= height) or
+ (self._private.dir ~= "y" and util.round(pos) >= width) then
+ break
+ end
+ end
+
+ return result
+end
+
+--- Increase the ratio of "widget"
+-- If the increment produce an invalid ratio (not between 0 and 1), the method
+-- do nothing.
+-- @tparam number index The widget index to change
+-- @tparam number increment An floating point value between -1 and 1 where the
+-- end result is within 0 and 1
+function ratio:inc_ratio(index, increment)
+ if #self._private.widgets == 1 or (not index) or (not self._private.ratios[index])
+ or increment < -1 or increment > 1 then
+ return
+ end
+
+ assert(self._private.ratios[index])
+
+ self:set_ratio(index, self._private.ratios[index] + increment)
+end
+
+--- Increment the ratio of the first instance of `widget`
+-- If the increment produce an invalid ratio (not between 0 and 1), the method
+-- do nothing.
+-- @param widget The widget to ajust
+-- @tparam number increment An floating point value between -1 and 1 where the
+-- end result is within 0 and 1
+function ratio:inc_widget_ratio(widget, increment)
+ if not widget or not increment then return end
+
+ local index = self:index(widget)
+
+ self:inc_ratio(index, increment)
+end
+
+--- Set the ratio of the widget at position `index`
+-- @tparam number index The index of the widget to change
+-- @tparam number percent An floating point value between 0 and 1
+function ratio:set_ratio(index, percent)
+ if not percent or #self._private.widgets == 1 or not index or not self._private.widgets[index]
+ or percent < 0 or percent > 1 then
+ return
+ end
+
+ local old = self._private.ratios[index]
+
+ -- Remove what has to be cleared from all widget
+ local delta = ( (percent-old) / (#self._private.widgets-1) )
+
+ for k in pairs(self._private.widgets) do
+ self._private.ratios[k] = self._private.ratios[k] - delta
+ end
+
+ -- Set the new ratio
+ self._private.ratios[index] = percent
+
+ -- As some widgets may now have a slightly negative ratio, normalize again
+ normalize(self)
+
+ self:emit_signal("widget::layout_changed")
+end
+
+--- Get the ratio at `index`.
+-- @tparam number index The widget index to query
+-- @treturn number The index (between 0 and 1)
+function ratio:get_ratio(index)
+ if not index then return end
+ return self._private.ratios[index]
+end
+
+--- Set the ratio of `widget` to `percent`.
+-- @tparam widget widget The widget to ajust.
+-- @tparam number percent A floating point value between 0 and 1.
+function ratio:set_widget_ratio(widget, percent)
+ local index = self:index(widget)
+
+ self:set_ratio(index, percent)
+end
+
+--- Update all widgets to match a set of a ratio.
+-- The sum of before, itself and after must be 1 or nothing will be done
+-- @tparam number index The index of the widget to change
+-- @tparam number before The sum of the ratio before the widget
+-- @tparam number itself The ratio for "widget"
+-- @tparam number after The sum of the ratio after the widget
+function ratio:ajust_ratio(index, before, itself, after)
+ if not self._private.widgets[index] or not before or not itself or not after then
+ return
+ end
+
+ local sum = before + itself + after
+
+ -- As documented, it is the caller job to come up with valid numbers
+ if math.min(before, itself, after) < 0 then return end
+ if sum > 1.01 or sum < -0.99 then return end
+
+ -- Compute the before and after offset to be applied to each widgets
+ local before_count, after_count = index-1, #self._private.widgets - index
+
+ local b, a = gen_sum(self, 1, index-1), gen_sum(self, index+1)
+
+ local db, da = (before - b)/before_count, (after - a)/after_count
+
+ -- Apply the new ratio
+ self._private.ratios[index] = itself
+
+ -- Equality split the delta among widgets before and after
+ for i = 1, index -1 do
+ self._private.ratios[i] = self._private.ratios[i] + db
+ end
+ for i = index+1, #self._private.widgets do
+ self._private.ratios[i] = self._private.ratios[i] + da
+ end
+
+ -- Remove potential negative ratio
+ normalize(self)
+
+ self:emit_signal("widget::layout_changed")
+end
+
+--- Update all widgets to match a set of a ratio
+-- @param widget The widget to ajust
+-- @tparam number before The sum of the ratio before the widget
+-- @tparam number itself The ratio for "widget"
+-- @tparam number after The sum of the ratio after the widget
+function ratio:ajust_widget_ratio(widget, before, itself, after)
+ local index = self:index(widget)
+ self:ajust_ratio(index, before, itself, after)
+end
+
+--- Add some widgets to the given fixed layout
+-- **Signal:** widget::added The argument are the widgets
+-- @tparam widget ... Widgets that should be added (must at least be one)
+function ratio:add(...)
+ -- No table.pack in Lua 5.1 :-(
+ local args = { n=select('#', ...), ... }
+ assert(args.n > 0, "need at least one widget to add")
+ for i=1, args.n do
+ base.check_widget(args[i])
+ table.insert(self._private.widgets, args[i])
+ end
+
+ normalize(self)
+ self:emit_signal("widget::layout_changed")
+ self:emit_signal("widget::added", ...)
+end
+
+--- Remove a widget from the layout
+-- **Signal:** widget::removed The arguments are the widget and the index
+-- @tparam number index The widget index to remove
+-- @treturn boolean index If the operation is successful
+function ratio:remove(index)
+ if not index or not self._private.widgets[index] then return false end
+
+ local w = self._private.widgets[index]
+
+ table.remove(self._private.ratios, index)
+ table.remove(self._private.widgets, index)
+
+ normalize(self)
+
+ self:emit_signal("widget::layout_changed")
+ self:emit_signal("widget::removed", w, index)
+
+ return true
+end
+
+--- Insert a new widget in the layout at position `index`
+-- **Signal:** widget::inserted The arguments are the widget and the index
+-- @tparam number index The position
+-- @param widget The widget
+function ratio:insert(index, widget)
+ if not index or index < 1 or index > #self._private.widgets + 1 then return false end
+
+ base.check_widget(widget)
+
+ table.insert(self._private.widgets, index, widget)
+
+ normalize(self)
+
+ self:emit_signal("widget::layout_changed")
+ self:emit_signal("widget::inserted", widget, #self._private.widgets)
+end
+
+local function get_layout(dir, widget1, ...)
+ local ret = flex[dir](widget1, ...)
+
+ util.table.crush(ret, ratio, true)
+
+ ret._private.fill_space = nil
+
+ ret._private.ratios = {}
+
+ return ret
+end
+
+--- Returns a new horizontal ratio layout. A ratio layout shares the available space
+-- equally among all widgets. Widgets can be added via :add(widget).
+-- @tparam widget ... Widgets that should be added to the layout.
+function ratio.horizontal(...)
+ return get_layout("horizontal", ...)
+end
+
+--- Returns a new vertical ratio layout. A ratio layout shares the available space
+-- equally among all widgets. Widgets can be added via :add(widget).
+-- @tparam widget ... Widgets that should be added to the layout.
+function ratio.vertical(...)
+ return get_layout("vertical", ...)
+end
+
+--Imported documentation
+
+
+--- Get a widex index.
+-- @param widget The widget to look for
+-- @param[opt] recursive Also check sub-widgets
+-- @param[opt] ... Aditional widgets to add at the end of the \"path\"
+-- @return The index
+-- @return The parent layout
+-- @return The path between \"self\" and \"widget\"
+-- @function index
+
+--- Get all direct and indirect children widgets.
+-- This will scan all containers recursively to find widgets
+-- Warning: This method it prone to stack overflow id the widget, or any of its
+-- children, contain (directly or indirectly) itself.
+-- @treturn table The children
+-- @function get_all_children
+
+--- Set a declarative widget hierarchy description.
+-- See [The declarative layout system](../documentation/03-declarative-layout.md.html)
+-- @param args An array containing the widgets disposition
+-- @function setup
+
+--- Force a widget height.
+-- @property forced_height
+-- @tparam number|nil height The height (`nil` for automatic)
+
+--- Force a widget width.
+-- @property forced_width
+-- @tparam number|nil width The width (`nil` for automatic)
+
+--- The widget opacity (transparency).
+-- @property opacity
+-- @tparam[opt=1] number opacity The opacity (between 0 and 1)
+
+--- The widget visibility.
+-- @property visible
+-- @param boolean
+
+--- Set/get a widget's buttons.
+-- @param _buttons The table of buttons that should bind to the widget.
+-- @function buttons
+
+--- Emit a signal and ensure all parent widgets in the hierarchies also
+-- forward the signal. This is useful to track signals when there is a dynamic
+-- set of containers and layouts wrapping the widget.
+-- @tparam string signal_name
+-- @param ... Other arguments
+-- @function emit_signal_recursive
+
+--- When the layout (size) change.
+-- This signal is emitted when the previous results of `:layout()` and `:fit()`
+-- are no longer valid. Unless this signal is emitted, `:layout()` and `:fit()`
+-- must return the same result when called with the same arguments.
+-- @signal widget::layout_changed
+-- @see widget::redraw_needed
+
+--- When the widget content changed.
+-- This signal is emitted when the content of the widget changes. The widget will
+-- be redrawn, it is not re-layouted. Put differently, it is assumed that
+-- `:layout()` and `:fit()` would still return the same results as before.
+-- @signal widget::redraw_needed
+-- @see widget::layout_changed
+
+--- When a mouse button is pressed over the widget.
+-- @signal button::press
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When a mouse button is released over the widget.
+-- @signal button::release
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse enter a widget.
+-- @signal mouse::enter
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse leave a widget.
+-- @signal mouse::leave
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+
+--Imported documentation
+
+
+--- Disconnect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback that should be disconnected
+-- @function disconnect_signal
+
+--- Emit a signal.
+--
+-- @tparam string name The name of the signal
+-- @param ... Extra arguments for the callback functions. Each connected
+-- function receives the object as first argument and then any extra arguments
+-- that are given to emit_signal()
+-- @function emit_signal
+
+--- Connect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function connect_signal
+
+--- Connect to a signal weakly. This allows the callback function to be garbage
+-- collected and automatically disconnects the signal when that happens.
+--
+-- **Warning:**
+-- Only use this function if you really, really, really know what you
+-- are doing.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function weak_connect_signal
+
+
+return ratio
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/wibox/layout/rotate.lua b/lib/wibox/layout/rotate.lua
new file mode 100644
index 0000000..a6b5d7a
--- /dev/null
+++ b/lib/wibox/layout/rotate.lua
@@ -0,0 +1,17 @@
+---------------------------------------------------------------------------
+-- This class has been moved to `wibox.container.rotate`
+--
+-- @author Uli Schlachter
+-- @copyright 2010 Uli Schlachter
+-- @classmod wibox.layout.rotate
+---------------------------------------------------------------------------
+
+local util = require("awful.util")
+
+return util.deprecate_class(
+ require("wibox.container.rotate"),
+ "wibox.layout.rotate",
+ "wibox.container.rotate"
+)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/wibox/layout/scroll.lua b/lib/wibox/layout/scroll.lua
new file mode 100644
index 0000000..e0be79f
--- /dev/null
+++ b/lib/wibox/layout/scroll.lua
@@ -0,0 +1,16 @@
+---------------------------------------------------------------------------
+-- This class has been moved to `wibox.container.scroll`
+--
+-- @author Uli Schlachter (based on ideas from Saleur Geoffrey)
+-- @copyright 2015 Uli Schlachter
+-- @classmod wibox.layout.scroll
+---------------------------------------------------------------------------
+local util = require("awful.util")
+
+return util.deprecate_class(
+ require("wibox.container.scroll"),
+ "wibox.layout.scroll",
+ "wibox.container.scroll"
+)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/wibox/layout/stack.lua b/lib/wibox/layout/stack.lua
new file mode 100644
index 0000000..bd28703
--- /dev/null
+++ b/lib/wibox/layout/stack.lua
@@ -0,0 +1,402 @@
+---------------------------------------------------------------------------
+-- A stacked layout.
+--
+-- This layout display widgets on top of each other. It can be used to overlay
+-- a `wibox.widget.textbox` on top of a `awful.widget.progressbar` or manage
+-- "pages" where only one is visible at any given moment.
+--
+-- The indices are going from 1 (the bottom of the stack) up to the top of
+-- the stack. The order can be changed either using `:swap` or `:raise`.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_layout_defaults_stack.svg)
+--
+-- @usage
+--wibox.widget {
+-- generic_widget( 'first' ),
+-- generic_widget( 'second' ),
+-- generic_widget( 'third' ),
+-- layout = wibox.layout.stack
+--}
+-- @author Emmanuel Lepage Vallee
+-- @copyright 2016 Emmanuel Lepage Vallee
+-- @classmod wibox.layout.stack
+---------------------------------------------------------------------------
+
+local base = require("wibox.widget.base" )
+local fixed = require("wibox.layout.fixed")
+local table = table
+local pairs = pairs
+local util = require("awful.util")
+
+local stack = {mt={}}
+
+--Imported documentation
+
+--- Set a widget at a specific index, replace the current one.
+-- **Signal:** widget::replaced The argument is the new widget and the old one
+-- and the index.
+-- @tparam number index A widget or a widget index
+-- @param widget2 The widget to take the place of the first one
+-- @treturn boolean If the operation is successful
+-- @name set
+-- @class function
+
+--- Replace the first instance of `widget` in the layout with `widget2`.
+-- **Signal:** widget::replaced The argument is the new widget and the old one
+-- and the index.
+-- @param widget The widget to replace
+-- @param widget2 The widget to replace `widget` with
+-- @tparam[opt=false] boolean recursive Digg in all compatible layouts to find the widget.
+-- @treturn boolean If the operation is successful
+-- @name replace_widget
+-- @class function
+
+--- Swap 2 widgets in a layout.
+-- **Signal:** widget::swapped The arguments are both widgets and both (new) indexes.
+-- @tparam number index1 The first widget index
+-- @tparam number index2 The second widget index
+-- @treturn boolean If the operation is successful
+-- @name swap
+-- @class function
+
+--- Swap 2 widgets in a layout.
+-- If widget1 is present multiple time, only the first instance is swapped
+-- **Signal:** widget::swapped The arguments are both widgets and both (new) indexes.
+-- if the layouts not the same, then only `widget::replaced` will be emitted.
+-- @param widget1 The first widget
+-- @param widget2 The second widget
+-- @tparam[opt=false] boolean recursive Digg in all compatible layouts to find the widget.
+-- @treturn boolean If the operation is successful
+-- @name swap_widgets
+-- @class function
+
+--- Get all direct children of this layout.
+-- @param layout The layout you are modifying.
+-- @property children
+
+--- Reset a ratio layout. This removes all widgets from the layout.
+-- **Signal:** widget::reset
+-- @param layout The layout you are modifying.
+-- @name reset
+-- @class function
+
+
+--- Add some widgets to the given stack layout
+-- @param layout The layout you are modifying.
+-- @tparam widget ... Widgets that should be added (must at least be one)
+-- @name add
+-- @class function
+
+--- Remove a widget from the layout
+-- @tparam index The widget index to remove
+-- @treturn boolean index If the operation is successful
+-- @name remove
+-- @class function
+
+--- Insert a new widget in the layout at position `index`
+-- @tparam number index The position
+-- @param widget The widget
+-- @treturn boolean If the operation is successful
+-- @name insert
+-- @class function
+
+--- Remove one or more widgets from the layout
+-- The last parameter can be a boolean, forcing a recursive seach of the
+-- widget(s) to remove.
+-- @param widget ... Widgets that should be removed (must at least be one)
+-- @treturn boolean If the operation is successful
+-- @name remove_widgets
+-- @class function
+
+--- Add spacing between each layout widgets
+-- @property spacing
+-- @tparam number spacing Spacing between widgets.
+
+function stack:layout(_, width, height)
+ local result = {}
+ local spacing = self._private.spacing
+
+ for _, v in pairs(self._private.widgets) do
+ table.insert(result, base.place_widget_at(v, spacing, spacing, width - 2*spacing, height - 2*spacing))
+ if self._private.top_only then break end
+ end
+
+ return result
+end
+
+function stack:fit(context, orig_width, orig_height)
+ local max_w, max_h = 0,0
+ local spacing = self._private.spacing
+
+ for _, v in pairs(self._private.widgets) do
+ local w, h = base.fit_widget(self, context, v, orig_width, orig_height)
+ max_w, max_h = math.max(max_w, w+2*spacing), math.max(max_h, h+2*spacing)
+ end
+
+ return math.min(max_w, orig_width), math.min(max_h, orig_height)
+end
+
+--- If only the first stack widget is drawn
+-- @property top_only
+
+function stack:get_top_only()
+ return self._private.top_only
+end
+
+function stack:set_top_only(top_only)
+ self._private.top_only = top_only
+end
+
+--- Raise a widget at `index` to the top of the stack
+-- @tparam number index the widget index to raise
+function stack:raise(index)
+ if (not index) or self._private.widgets[index] then return end
+
+ local w = self._private.widgets[index]
+ table.remove(self._private.widgets, index)
+ table.insert(self._private.widgets, w)
+
+ self:emit_signal("widget::layout_changed")
+end
+
+--- Raise the first instance of `widget`
+-- @param widget The widget to raise
+-- @tparam[opt=false] boolean recursive Also look deeper in the hierarchy to
+-- find the widget
+function stack:raise_widget(widget, recursive)
+ local idx, layout = self:index(widget, recursive)
+
+ if not idx or not layout then return end
+
+ -- Bubble up in the stack until the right index is found
+ while layout and layout ~= self do
+ idx, layout = self:index(layout, recursive)
+ end
+
+ if layout == self and idx ~= 1 then
+ self:raise(idx)
+ end
+end
+
+--- Create a new stack layout.
+-- @function wibox.layout.stack
+-- @treturn widget A new stack layout
+
+local function new(...)
+ local ret = fixed.horizontal(...)
+
+ util.table.crush(ret, stack, true)
+
+ return ret
+end
+
+function stack.mt:__call(_, ...)
+ return new(...)
+end
+
+--Imported documentation
+
+
+--- Get a widex index.
+-- @param widget The widget to look for
+-- @param[opt] recursive Also check sub-widgets
+-- @param[opt] ... Aditional widgets to add at the end of the \"path\"
+-- @return The index
+-- @return The parent layout
+-- @return The path between \"self\" and \"widget\"
+-- @function index
+
+--- Get all direct and indirect children widgets.
+-- This will scan all containers recursively to find widgets
+-- Warning: This method it prone to stack overflow id the widget, or any of its
+-- children, contain (directly or indirectly) itself.
+-- @treturn table The children
+-- @function get_all_children
+
+--- Set a declarative widget hierarchy description.
+-- See [The declarative layout system](../documentation/03-declarative-layout.md.html)
+-- @param args An array containing the widgets disposition
+-- @function setup
+
+--- Force a widget height.
+-- @property forced_height
+-- @tparam number|nil height The height (`nil` for automatic)
+
+--- Force a widget width.
+-- @property forced_width
+-- @tparam number|nil width The width (`nil` for automatic)
+
+--- The widget opacity (transparency).
+-- @property opacity
+-- @tparam[opt=1] number opacity The opacity (between 0 and 1)
+
+--- The widget visibility.
+-- @property visible
+-- @param boolean
+
+--- Set/get a widget's buttons.
+-- @param _buttons The table of buttons that should bind to the widget.
+-- @function buttons
+
+--- Emit a signal and ensure all parent widgets in the hierarchies also
+-- forward the signal. This is useful to track signals when there is a dynamic
+-- set of containers and layouts wrapping the widget.
+-- @tparam string signal_name
+-- @param ... Other arguments
+-- @function emit_signal_recursive
+
+--- When the layout (size) change.
+-- This signal is emitted when the previous results of `:layout()` and `:fit()`
+-- are no longer valid. Unless this signal is emitted, `:layout()` and `:fit()`
+-- must return the same result when called with the same arguments.
+-- @signal widget::layout_changed
+-- @see widget::redraw_needed
+
+--- When the widget content changed.
+-- This signal is emitted when the content of the widget changes. The widget will
+-- be redrawn, it is not re-layouted. Put differently, it is assumed that
+-- `:layout()` and `:fit()` would still return the same results as before.
+-- @signal widget::redraw_needed
+-- @see widget::layout_changed
+
+--- When a mouse button is pressed over the widget.
+-- @signal button::press
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When a mouse button is released over the widget.
+-- @signal button::release
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse enter a widget.
+-- @signal mouse::enter
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse leave a widget.
+-- @signal mouse::leave
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+
+--Imported documentation
+
+
+--- Disconnect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback that should be disconnected
+-- @function disconnect_signal
+
+--- Emit a signal.
+--
+-- @tparam string name The name of the signal
+-- @param ... Extra arguments for the callback functions. Each connected
+-- function receives the object as first argument and then any extra arguments
+-- that are given to emit_signal()
+-- @function emit_signal
+
+--- Connect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function connect_signal
+
+--- Connect to a signal weakly. This allows the callback function to be garbage
+-- collected and automatically disconnects the signal when that happens.
+--
+-- **Warning:**
+-- Only use this function if you really, really, really know what you
+-- are doing.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function weak_connect_signal
+
+
+return setmetatable(stack, stack.mt)
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/wibox/widget/background.lua b/lib/wibox/widget/background.lua
new file mode 100644
index 0000000..3c35a0b
--- /dev/null
+++ b/lib/wibox/widget/background.lua
@@ -0,0 +1,16 @@
+---------------------------------------------------------------------------
+-- This class has been moved to `wibox.container.background`
+--
+-- @author Uli Schlachter
+-- @copyright 2010 Uli Schlachter
+-- @classmod wibox.widget.background
+---------------------------------------------------------------------------
+local util = require("awful.util")
+
+return util.deprecate_class(
+ require("wibox.container.background"),
+ "wibox.widget.background",
+ "wibox.container.background"
+)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/wibox/widget/base.lua b/lib/wibox/widget/base.lua
new file mode 100644
index 0000000..dd80ec7
--- /dev/null
+++ b/lib/wibox/widget/base.lua
@@ -0,0 +1,694 @@
+---------------------------------------------------------------------------
+-- @author Uli Schlachter
+-- @copyright 2010 Uli Schlachter
+-- @classmod wibox.widget.base
+---------------------------------------------------------------------------
+
+local object = require("gears.object")
+local cache = require("gears.cache")
+local matrix = require("gears.matrix")
+local protected_call = require("gears.protected_call")
+local util = require("awful.util")
+local setmetatable = setmetatable
+local pairs = pairs
+local type = type
+local table = table
+
+local base = {}
+
+-- {{{ Functions on widgets
+
+--- Functions available on all widgets.
+base.widget = {}
+
+--- Set/get a widget's buttons.
+-- @tab _buttons The table of buttons that is bound to the widget.
+-- @function buttons
+function base.widget:buttons(_buttons)
+ if _buttons then
+ self._private.widget_buttons = _buttons
+ end
+ return self._private.widget_buttons
+end
+
+--- Set a widget's visibility.
+-- @tparam boolean b Whether the widget is visible.
+-- @function set_visible
+function base.widget:set_visible(b)
+ if b ~= self._private.visible then
+ self._private.visible = b
+ self:emit_signal("widget::layout_changed")
+ -- In case something ignored fit and drew the widget anyway.
+ self:emit_signal("widget::redraw_needed")
+ end
+end
+
+--- Is the widget visible?
+-- @treturn boolean
+-- @function get_visible
+function base.widget:get_visible()
+ return self._private.visible or false
+end
+
+--- Set a widget's opacity.
+-- @tparam number o The opacity to use (a number from 0 (transparent) to 1
+-- (opaque)).
+-- @function set_opacity
+function base.widget:set_opacity(o)
+ if o ~= self._private.opacity then
+ self._private.opacity = o
+ self:emit_signal("widget::redraw")
+ end
+end
+
+--- Get the widget's opacity.
+-- @treturn number The opacity (between 0 (transparent) and 1 (opaque)).
+-- @function get_opacity
+function base.widget:get_opacity()
+ return self._private.opacity
+end
+
+--- Set the widget's forced width.
+-- @tparam[opt] number width With `nil` the default mechanism of calling the
+-- `:fit` method is used.
+-- @see fit_widget
+-- @function set_forced_width
+function base.widget:set_forced_width(width)
+ if width ~= self._private.forced_width then
+ self._private.forced_width = width
+ self:emit_signal("widget::layout_changed")
+ end
+end
+
+--- Get the widget's forced width.
+--
+-- Note that widget instances can be used in different places simultaneously,
+-- and therefore can have multiple dimensions.
+-- If there is no forced width/height, then the only way to get the widget's
+-- actual size is during a `mouse::enter`, `mouse::leave` or button event.
+-- @treturn[opt] number The forced width (nil if automatic).
+-- @see fit_widget
+-- @function get_forced_width
+function base.widget:get_forced_width()
+ return self._private.forced_width
+end
+
+--- Set the widget's forced height.
+-- @tparam[opt] number height With `nil` the default mechanism of calling the
+-- `:fit` method is used.
+-- @see fit_widget
+-- @function set_height
+function base.widget:set_forced_height(height)
+ if height ~= self._private.forced_height then
+ self._private.forced_height = height
+ self:emit_signal("widget::layout_changed")
+ end
+end
+
+--- Get the widget's forced height.
+--
+-- Note that widget instances can be used in different places simultaneously,
+-- and therefore can have multiple dimensions.
+-- If there is no forced width/height, then the only way to get the widget's
+-- actual size is during a `mouse::enter`, `mouse::leave` or button event.
+-- @treturn[opt] number The forced height (nil if automatic).
+-- @function get_forced_height
+function base.widget:get_forced_height()
+ return self._private.forced_height
+end
+
+--- Get the widget's direct children widgets.
+--
+-- This method should be re-implemented by the relevant widgets.
+-- @treturn table The children
+-- @function get_children
+function base.widget:get_children()
+ return {}
+end
+
+--- Replace the layout children.
+--
+-- The default implementation does nothing, this must be re-implemented by
+-- all layout and container widgets.
+-- @tab children A table composed of valid widgets.
+-- @function set_children
+function base.widget:set_children(children) -- luacheck: no unused
+ -- Nothing on purpose
+end
+
+-- It could have been merged into `get_all_children`, but it's not necessary.
+local function digg_children(ret, tlw)
+ for _, w in ipairs(tlw:get_children()) do
+ table.insert(ret, w)
+ digg_children(ret, w)
+ end
+end
+
+--- Get all direct and indirect children widgets.
+--
+-- This will scan all containers recursively to find widgets.
+--
+-- *Warning*: This method it prone to stack overflow if the widget, or any of
+-- its children, contains (directly or indirectly) itself.
+-- @treturn table The children
+-- @function get_all_children
+function base.widget:get_all_children()
+ local ret = {}
+ digg_children(ret, self)
+ return ret
+end
+
+--- Emit a signal and ensure all parent widgets in the hierarchies also
+-- forward the signal. This is useful to track signals when there is a dynamic
+-- set of containers and layouts wrapping the widget.
+--
+-- Note that this function has two flaws:
+--
+-- 1. The signal is only forwarded once the widget tree has been built. This
+-- happens after all currently scheduled functions have been executed.
+-- Therefore, it will not start to work right away.
+-- 2. In case the widget is present multiple times in a single widget tree,
+-- this function will also forward the signal multiple time (one per upward
+-- tree path).
+--
+-- @tparam string signal_name
+-- @param ... Other arguments
+-- @function emit_signal_recursive
+function base.widget:emit_signal_recursive(signal_name, ...)
+ -- This is a convenience wrapper, the real implementation is in the
+ -- hierarchy.
+
+ self:emit_signal("widget::emit_recursive", signal_name, ...)
+end
+
+--- Get the index of a widget.
+-- @tparam widget widget The widget to look for.
+-- @tparam[opt] boolean recursive Also check sub-widgets?
+-- @tparam[opt] widget ... Additional widgets to add at the end of the "path"
+-- @treturn number The index.
+-- @treturn widget The parent widget.
+-- @treturn table The path between "self" and "widget".
+-- @function index
+function base.widget:index(widget, recursive, ...)
+ local widgets = self:get_children()
+ for idx, w in ipairs(widgets) do
+ if w == widget then
+ return idx, self, {...}
+ elseif recursive then
+ local child_idx, l, path = w:index(widget, true, self, ...)
+ if child_idx and l then
+ return child_idx, l, path
+ end
+ end
+ end
+ return nil, self, {}
+end
+-- }}}
+
+-- {{{ Caches
+
+-- Indexes are widgets, allow them to be garbage-collected.
+local widget_dependencies = setmetatable({}, { __mode = "kv" })
+
+-- Get the cache of the given kind for this widget. This returns a gears.cache
+-- that calls the callback of kind `kind` on the widget.
+local function get_cache(widget, kind)
+ if not widget._private.widget_caches[kind] then
+ widget._private.widget_caches[kind] = cache.new(function(...)
+ return protected_call(widget[kind], widget, ...)
+ end)
+ end
+ return widget._private.widget_caches[kind]
+end
+
+-- Special value to skip the dependency recording that is normally done by
+-- base.fit_widget() and base.layout_widget(). The caller must ensure that no
+-- caches depend on the result of the call and/or must handle the children's
+-- widget::layout_changed signal correctly when using this.
+base.no_parent_I_know_what_I_am_doing = {}
+
+-- Record a dependency from parent to child: The layout of `parent` depends on
+-- the layout of `child`.
+local function record_dependency(parent, child)
+ if parent == base.no_parent_I_know_what_I_am_doing then
+ return
+ end
+
+ base.check_widget(parent)
+ base.check_widget(child)
+
+ local deps = widget_dependencies[child] or {}
+ deps[parent] = true
+ widget_dependencies[child] = deps
+end
+
+-- Clear the caches for `widget` and all widgets that depend on it.
+local clear_caches
+function clear_caches(widget)
+ local deps = widget_dependencies[widget] or {}
+ widget_dependencies[widget] = {}
+ widget._private.widget_caches = {}
+ for w in pairs(deps) do
+ clear_caches(w)
+ end
+end
+
+-- }}}
+
+--- Figure out the geometry in the device coordinate space.
+--
+-- This gives only tight bounds if no rotations by non-multiples of 90° are
+-- used.
+-- @function wibox.widget.base.rect_to_device_geometry
+function base.rect_to_device_geometry(cr, x, y, width, height)
+ return matrix.transform_rectangle(cr.matrix, x, y, width, height)
+end
+
+--- Fit a widget for the given available width and height.
+--
+-- This calls the widget's `:fit` callback and caches the result for later use.
+-- Never call `:fit` directly, but always through this function!
+-- @tparam widget parent The parent widget which requests this information.
+-- @tab context The context in which we are fit.
+-- @tparam widget widget The widget to fit (this uses
+-- `widget:fit(context, width, height)`).
+-- @tparam number width The available width for the widget.
+-- @tparam number height The available height for the widget.
+-- @treturn number The width that the widget wants to use.
+-- @treturn number The height that the widget wants to use.
+-- @function wibox.widget.base.fit_widget
+function base.fit_widget(parent, context, widget, width, height)
+ record_dependency(parent, widget)
+
+ if not widget._private.visible then
+ return 0, 0
+ end
+
+ -- Sanitize the input. This also filters out e.g. NaN.
+ width = math.max(0, width)
+ height = math.max(0, height)
+
+ local w, h = 0, 0
+ if widget.fit then
+ w, h = get_cache(widget, "fit"):get(context, width, height)
+ else
+ -- If it has no fit method, calculate based on the size of children
+ local children = base.layout_widget(parent, context, widget, width, height)
+ for _, info in ipairs(children or {}) do
+ local x, y, w2, h2 = matrix.transform_rectangle(info._matrix,
+ 0, 0, info._width, info._height)
+ w, h = math.max(w, x + w2), math.max(h, y + h2)
+ end
+ end
+
+ -- Apply forced size and handle nil's
+ w = widget._private.forced_width or w or 0
+ h = widget._private.forced_height or h or 0
+
+ -- Also sanitize the output.
+ w = math.max(0, math.min(w, width))
+ h = math.max(0, math.min(h, height))
+ return w, h
+end
+
+--- Lay out a widget for the given available width and height.
+--
+-- This calls the widget's `:layout` callback and caches the result for later
+-- use. Never call `:layout` directly, but always through this function!
+-- However, normally there shouldn't be any reason why you need to use this
+-- function.
+-- @tparam widget parent The parent widget which requests this information.
+-- @tab context The context in which we are laid out.
+-- @tparam widget widget The widget to layout (this uses
+-- `widget:layout(context, width, height)`).
+-- @tparam number width The available width for the widget.
+-- @tparam number height The available height for the widget.
+-- @treturn[opt] table The result from the widget's `:layout` callback.
+-- @function wibox.widget.base.layout_widget
+function base.layout_widget(parent, context, widget, width, height)
+ record_dependency(parent, widget)
+
+ if not widget._private.visible then
+ return
+ end
+
+ -- Sanitize the input. This also filters out e.g. NaN.
+ width = math.max(0, width)
+ height = math.max(0, height)
+
+ if widget.layout then
+ return get_cache(widget, "layout"):get(context, width, height)
+ end
+end
+
+--- Handle a button event on a widget.
+--
+-- This is used internally and should not be called directly.
+-- @function wibox.widget.base.handle_button
+function base.handle_button(event, widget, x, y, button, modifiers, geometry)
+ x = x or y -- luacheck: no unused
+ local function is_any(mod)
+ return #mod == 1 and mod[1] == "Any"
+ end
+
+ local function tables_equal(a, b)
+ if #a ~= #b then
+ return false
+ end
+ for k, v in pairs(b) do
+ if a[k] ~= v then
+ return false
+ end
+ end
+ return true
+ end
+
+ -- Find all matching button objects.
+ local matches = {}
+ for _, v in pairs(widget._private.widget_buttons) do
+ local match = true
+ -- Is it the right button?
+ if v.button ~= 0 and v.button ~= button then match = false end
+ -- Are the correct modifiers pressed?
+ if (not is_any(v.modifiers)) and (not tables_equal(v.modifiers, modifiers)) then match = false end
+ if match then
+ table.insert(matches, v)
+ end
+ end
+
+ -- Emit the signals.
+ for _, v in pairs(matches) do
+ v:emit_signal(event,geometry)
+ end
+end
+
+--- Create widget placement information. This should be used in a widget's
+-- `:layout()` callback.
+-- @tparam widget widget The widget that should be placed.
+-- @param mat A matrix transforming from the parent widget's coordinate
+-- system. For example, use matrix.create_translate(1, 2) to draw a
+-- widget at position (1, 2) relative to the parent widget.
+-- @tparam number width The width of the widget in its own coordinate system.
+-- That is, after applying the transformation matrix.
+-- @tparam number height The height of the widget in its own coordinate system.
+-- That is, after applying the transformation matrix.
+-- @treturn table An opaque object that can be returned from `:layout()`.
+-- @function wibox.widget.base.place_widget_via_matrix
+function base.place_widget_via_matrix(widget, mat, width, height)
+ return {
+ _widget = widget,
+ _width = width,
+ _height = height,
+ _matrix = mat
+ }
+end
+
+--- Create widget placement information. This should be used for a widget's
+-- `:layout()` callback.
+-- @tparam widget widget The widget that should be placed.
+-- @tparam number x The x coordinate for the widget.
+-- @tparam number y The y coordinate for the widget.
+-- @tparam number width The width of the widget in its own coordinate system.
+-- That is, after applying the transformation matrix.
+-- @tparam number height The height of the widget in its own coordinate system.
+-- That is, after applying the transformation matrix.
+-- @treturn table An opaque object that can be returned from `:layout()`.
+-- @function wibox.widget.base.place_widget_at
+function base.place_widget_at(widget, x, y, width, height)
+ return base.place_widget_via_matrix(widget, matrix.create_translate(x, y), width, height)
+end
+
+-- Read the table, separate attributes from widgets.
+local function parse_table(t, leave_empty)
+ local max = 0
+ local attributes, widgets = {}, {}
+ for k,v in pairs(t) do
+ if type(k) == "number" then
+ if v then
+ -- Since `ipairs` doesn't always work on sparse tables, update
+ -- the maximum.
+ if k > max then
+ max = k
+ end
+
+ widgets[k] = v
+ end
+ else
+ attributes[k] = v
+ end
+ end
+
+ -- Pack the sparse table, if the container doesn't support sparse tables.
+ if not leave_empty then
+ widgets = util.table.from_sparse(widgets)
+ max = #widgets
+ end
+
+ return max, attributes, widgets
+end
+
+-- Recursively build a container from a declarative table.
+local function drill(ids, content)
+ if not content then return end
+
+ -- Alias `widget` to `layout` as they are handled the same way.
+ content.layout = content.layout or content.widget
+
+ -- Make sure the layout is not indexed on a function.
+ local layout = type(content.layout) == "function" and content.layout() or content.layout
+
+ -- Create layouts based on metatable's __call.
+ local l = layout.is_widget and layout or layout()
+
+ -- Get the number of children widgets (including nil widgets).
+ local max, attributes, widgets = parse_table(content, l.allow_empty_widget)
+
+ -- Get the optional identifier to create a virtual widget tree to place
+ -- in an "access table" to be able to retrieve the widget.
+ local id = attributes.id
+
+ -- Clear the internal attributes.
+ attributes.id, attributes.layout, attributes.widget = nil, nil, nil
+
+ -- Set layout attributes.
+ -- This has to be done before the widgets are added because it might affect
+ -- the output.
+ for attr, val in pairs(attributes) do
+ if l["set_"..attr] then
+ l["set_"..attr](l, val)
+ elseif type(l[attr]) == "function" then
+ l[attr](l, val)
+ else
+ l[attr] = val
+ end
+ end
+
+ -- Add all widgets.
+ for k = 1, max do
+ -- ipairs cannot be used on sparse tables.
+ local v, id2, e = widgets[k], id, nil
+ if v then
+ -- It is another declarative container, parse it.
+ if not v.is_widget then
+ e, id2 = drill(ids, v)
+ widgets[k] = e
+ end
+ base.check_widget(widgets[k])
+
+ -- Place the widget in the access table.
+ if id2 then
+ l [id2] = e
+ ids[id2] = ids[id2] or {}
+ table.insert(ids[id2], e)
+ end
+ end
+ end
+ -- Replace all children (if any) with the new ones.
+ l:set_children(widgets)
+ return l, id
+end
+
+-- Only available when the declarative system is used.
+local function get_children_by_id(self, name)
+ if rawget(self, "_private") then
+ return self._private.by_id[name] or {}
+ else
+ return rawget(self, "_by_id")[name] or {}
+ end
+end
+
+--- Set a declarative widget hierarchy description.
+--
+-- See [The declarative layout system](../documentation/03-declarative-layout.md.html).
+-- @tab args A table containing the widget's disposition.
+-- @function setup
+function base.widget:setup(args)
+ local f,ids = self.set_widget or self.add or self.set_first,{}
+ local w, id = drill(ids, args)
+ f(self,w)
+ if id then
+ -- Avoid being dropped by wibox metatable -> drawin
+ rawset(self, id, w)
+ ids[id] = ids[id] or {}
+ table.insert(ids[id], 1, w)
+ end
+
+ if rawget(self, "_private") then
+ self._private.by_id = ids
+ else
+ rawset(self, "_by_id", ids)
+ end
+
+ rawset(self, "get_children_by_id", get_children_by_id)
+end
+
+--- Create a widget from a declarative description.
+--
+-- See [The declarative layout system](../documentation/03-declarative-layout.md.html).
+-- @tab args A table containing the widgets disposition.
+-- @function wibox.widget.base.make_widget_declarative
+function base.make_widget_declarative(args)
+ local ids = {}
+
+ if (not args.layout) and (not args.widget) then
+ args.widget = base.make_widget(nil, args.id)
+ end
+
+ local w, id = drill(ids, args)
+
+ local mt = getmetatable(w) or {}
+ local orig_string = tostring(w)
+
+ -- Add the main id (if any)
+ if id then
+ ids[id] = ids[id] or {}
+ table.insert(ids[id], 1, w)
+ end
+
+ if rawget(w, "_private") then
+ w._private.by_id = ids
+ else
+ rawset(w, "_by_id", ids)
+ end
+
+ rawset(w, "get_children_by_id", get_children_by_id)
+
+ mt.__tostring = function()
+ return string.format("%s (%s)", id or w.widget_name or "N/A", orig_string)
+ end
+
+ return setmetatable(w, mt)
+end
+
+--- Create an empty widget skeleton.
+--
+-- See [Creating new widgets](../documentation/04-new-widget.md.html).
+-- @tparam[opt] widget proxy If this is set, the returned widget will be a
+-- proxy for this widget. It will be equivalent to this widget.
+-- This means it looks the same on the screen.
+-- @tparam[opt] string widget_name Name of the widget. If not set, it will be
+-- set automatically via @{gears.object.modulename}.
+-- @tparam[opt={}] table args Widget settings
+-- @tparam[opt=false] boolean args.enable_properties Enable automatic getter
+-- and setter methods.
+-- @tparam[opt=nil] table args.class The widget class
+-- @see fit_widget
+-- @function wibox.widget.base.make_widget
+function base.make_widget(proxy, widget_name, args)
+ args = args or {}
+ local ret = object {
+ enable_properties = args.enable_properties,
+ class = args.class,
+ }
+
+ -- Backwards compatibility.
+ -- TODO: Remove this
+ ret:connect_signal("widget::updated", function()
+ ret:emit_signal("widget::layout_changed")
+ ret:emit_signal("widget::redraw_needed")
+ end)
+
+ -- Create a table used to store the widgets internal data.
+ rawset(ret, "_private", {})
+
+ -- No buttons yet.
+ ret._private.widget_buttons = {}
+
+ -- Widget is visible.
+ ret._private.visible = true
+
+ -- Widget is fully opaque.
+ ret._private.opacity = 1
+
+ -- Differentiate tables from widgets.
+ rawset(ret, "is_widget", true)
+
+ -- Size is not restricted/forced.
+ ret._private.forced_width = nil
+ ret._private.forced_height = nil
+
+ -- Make buttons work.
+ ret:connect_signal("button::press", function(...)
+ return base.handle_button("press", ...)
+ end)
+ ret:connect_signal("button::release", function(...)
+ return base.handle_button("release", ...)
+ end)
+
+ if proxy then
+ rawset(ret, "fit", function(_, context, width, height)
+ return base.fit_widget(ret, context, proxy, width, height)
+ end)
+ rawset(ret, "layout", function(_, _, width, height)
+ return { base.place_widget_at(proxy, 0, 0, width, height) }
+ end)
+ proxy:connect_signal("widget::layout_changed", function()
+ ret:emit_signal("widget::layout_changed")
+ end)
+ proxy:connect_signal("widget::redraw_needed", function()
+ ret:emit_signal("widget::redraw_needed")
+ end)
+ end
+
+ -- Set up caches.
+ clear_caches(ret)
+ ret:connect_signal("widget::layout_changed", function()
+ clear_caches(ret)
+ end)
+
+ -- Add functions.
+ for k, v in pairs(base.widget) do
+ rawset(ret, k, v)
+ end
+
+ -- Add __tostring method to metatable.
+ rawset(ret, "widget_name", widget_name or object.modulename(3))
+ local mt = getmetatable(ret) or {}
+ local orig_string = tostring(ret)
+ mt.__tostring = function()
+ return string.format("%s (%s)", ret.widget_name, orig_string)
+ end
+ return setmetatable(ret, mt)
+end
+
+--- Generate an empty widget which takes no space and displays nothing.
+-- @function wibox.widget.base.empty_widget
+function base.empty_widget()
+ return base.make_widget()
+end
+
+--- Do some sanity checking on a widget.
+--
+-- This function raises an error if the widget is not valid.
+-- @function wibox.widget.base.check_widget
+function base.check_widget(widget)
+ assert(type(widget) == "table", "Type should be table, but is " .. tostring(type(widget)))
+ assert(widget.is_widget, "Argument is not a widget!")
+ for _, func in pairs({ "connect_signal", "disconnect_signal" }) do
+ assert(type(widget[func]) == "function", func .. " is not a function")
+ end
+end
+
+return base
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/wibox/widget/checkbox.lua b/lib/wibox/widget/checkbox.lua
new file mode 100644
index 0000000..57bdaa3
--- /dev/null
+++ b/lib/wibox/widget/checkbox.lua
@@ -0,0 +1,530 @@
+---------------------------------------------------------------------------
+-- A boolean display widget.
+--
+-- If necessary, themes can implement custom shape:
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_widget_checkbox_custom.svg)
+--
+--
+-- wibox.widget {
+-- checked = true,
+-- color = beautiful.bg_normal,
+-- paddings = 2,
+-- check_shape = function(cr, width, height)
+-- local rs = math.min(width, height)
+-- cr:move_to( 0 , 0 )
+-- cr:line_to( rs , 0 )
+-- cr:move_to( 0 , 0 )
+-- cr:line_to( 0 , rs )
+-- cr:move_to( 0 , rs )
+-- cr:line_to( rs , rs )
+-- cr:move_to( rs , 0 )
+-- cr:line_to( rs , rs )
+-- cr:move_to( 0 , 0 )
+-- cr:line_to( rs , rs )
+-- cr:move_to( 0 , rs )
+-- cr:line_to( rs , 0 )
+-- end,
+-- check_border_color = '#ff0000',
+-- check_color = '#00000000',
+-- check_border_width = 1,
+-- widget = wibox.widget.checkbox
+-- }
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_widget_defaults_checkbox.svg)
+--
+-- @usage
+--wibox.widget {
+-- checked = true,
+-- color = beautiful.bg_normal,
+-- paddings = 2,
+-- shape = gears.shape.circle,
+-- widget = wibox.widget.checkbox
+--}
+-- @author Emmanuel Lepage Valle
+-- @copyright 2010 Emmanuel Lepage Vallee
+-- @classmod wibox.widget.checkbox
+---------------------------------------------------------------------------
+
+local color = require( "gears.color" )
+local base = require( "wibox.widget.base" )
+local beautiful = require( "beautiful" )
+local shape = require( "gears.shape" )
+local util = require( "awful.util" )
+
+local checkbox = {}
+
+--- The outer (unchecked area) border width.
+-- @beautiful beautiful.checkbox_border_width
+
+--- The outer (unchecked area) background color, pattern or gradient.
+-- @beautiful beautiful.checkbox_bg
+
+--- The outer (unchecked area) border color.
+-- @beautiful beautiful.checkbox_border_color
+
+--- The checked part border color.
+-- @beautiful beautiful.checkbox_check_border_color
+
+--- The checked part border width.
+-- @beautiful beautiful.checkbox_check_border_width
+
+--- The checked part filling color.
+-- @beautiful beautiful.checkbox_check_color
+
+--- The outer (unchecked area) shape.
+-- @beautiful beautiful.checkbox_shape
+-- @see gears.shape
+
+--- The checked part shape.
+-- If none is set, then the `shape` property will be used.
+-- @beautiful beautiful.checkbox_check_shape
+-- @see gears.shape
+
+--- The padding between the outline and the progressbar.
+-- @beautiful beautiful.checkbox_paddings
+-- @tparam[opt=0] table|number paddings A number or a table
+-- @tparam[opt=0] number paddings.top
+-- @tparam[opt=0] number paddings.bottom
+-- @tparam[opt=0] number paddings.left
+-- @tparam[opt=0] number paddings.right
+
+--- The checkbox color.
+-- This will be used for the unchecked part border color and the checked part
+-- filling color. Note that `check_color` and `border_color` have priority
+-- over this property.
+-- @beautiful beautiful.checkbox_color
+
+--- The outer (unchecked area) border width.
+-- @property border_width
+
+--- The outer (unchecked area) background color, pattern or gradient.
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_widget_checkbox_bg.svg)
+--
+-- @usage
+--wibox.widget {
+-- checked = true,
+-- color = beautiful.bg_normal,
+-- bg = '#ff00ff',
+-- border_width = 3,
+-- paddings = 4,
+-- border_color = '#0000ff',
+-- check_color = '#ff0000',
+-- check_border_color = '#ffff00',
+-- check_border_width = 1,
+-- widget = wibox.widget.checkbox
+--}
+-- @property bg
+
+--- The outer (unchecked area) border color.
+-- @property border_color
+
+--- The checked part border color.
+-- @property check_border_color
+
+--- The checked part border width.
+-- @property check_border_width
+
+--- The checked part filling color.
+-- @property check_color
+
+--- The outer (unchecked area) shape.
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_widget_checkbox_shape.svg)
+--
+-- @usage
+--for _, s in ipairs {'rectangle', 'circle', 'losange', 'octogon'} do
+-- wibox.widget {
+-- checked = true,
+-- color = beautiful.bg_normal,
+-- paddings = 2,
+-- shape = gears.shape[s],
+-- widget = wibox.widget.checkbox
+-- }
+--end
+-- @property shape
+-- @see gears.shape
+
+--- The checked part shape.
+-- If none is set, then the `shape` property will be used.
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_widget_checkbox_check_shape.svg)
+--
+-- @usage
+--for _, s in ipairs {'rectangle', 'circle', 'losange', 'octogon'} do
+-- wibox.widget {
+-- checked = true,
+-- color = beautiful.bg_normal,
+-- paddings = 2,
+-- check_shape = gears.shape[s],
+-- widget = wibox.widget.checkbox
+-- }
+--end
+-- @property check_shape
+-- @see gears.shape
+
+--- The padding between the outline and the progressbar.
+-- @property paddings
+-- @tparam[opt=0] table|number paddings A number or a table
+-- @tparam[opt=0] number paddings.top
+-- @tparam[opt=0] number paddings.bottom
+-- @tparam[opt=0] number paddings.left
+-- @tparam[opt=0] number paddings.right
+
+--- The checkbox color.
+-- This will be used for the unchecked part border color and the checked part
+-- filling color. Note that `check_color` and `border_color` have priority
+-- over this property.
+-- @property color
+
+local function outline_workarea(self, width, height)
+ local offset = (self._private.border_width or
+ beautiful.checkbox_border_width or 1)/2
+
+ return {
+ x = offset,
+ y = offset,
+ width = width-2*offset,
+ height = height-2*offset
+ }
+end
+
+-- The child widget area
+local function content_workarea(self, width, height)
+ local padding = self._private.paddings or {}
+ local offset = self:get_check_border_width() or 0
+ local wa = outline_workarea(self, width, height)
+
+ wa.x = offset + wa.x + (padding.left or 1)
+ wa.y = offset + wa.y + (padding.top or 1)
+ wa.width = wa.width - (padding.left or 1) - (padding.right or 1) - 2*offset
+ wa.height = wa.height - (padding.top or 1) - (padding.bottom or 1) - 2*offset
+
+ return wa
+end
+
+local function draw(self, _, cr, width, height)
+ local size = math.min(width, height)
+
+ local background_shape = self:get_shape() or shape.rectangle
+ local border_width = self:get_border_width() or 1
+
+ local main_color = self:get_color()
+ local bg = self:get_bg()
+ local border_color = self:get_border_color()
+
+ -- If no color is set, it will fallback to the default one
+ if border_color or main_color then
+ cr:set_source(color(border_color or main_color))
+ end
+
+ local wa = outline_workarea(self, size, size)
+ cr:translate(wa.x, wa.y)
+ background_shape(cr, wa.width, wa.height)
+ cr:set_line_width(border_width)
+
+ if bg then
+ cr:save()
+ cr:set_source(color(bg))
+ cr:fill_preserve()
+ cr:restore()
+ end
+
+ cr:stroke()
+
+ cr:translate(-wa.x, -wa.y)
+
+ -- Draw the checked part
+ if self._private.checked then
+ local col = self:get_check_color() or main_color
+ border_color = self:get_check_border_color()
+ border_width = self:get_check_border_width() or 0
+ local check_shape = self:get_check_shape() or background_shape
+
+ wa = content_workarea(self, size, size)
+ cr:translate(wa.x, wa.y)
+
+ check_shape(cr, wa.width, wa.height)
+
+ if col then
+ cr:set_source(color(col))
+ end
+
+ if border_width > 0 then
+ cr:fill_preserve()
+ cr:set_line_width(border_width)
+ cr:set_source(color(border_color))
+ cr:stroke()
+ else
+ cr:fill()
+ end
+ end
+end
+
+local function fit(_, _, w, h)
+ local size = math.min(w, h)
+ return size, size
+end
+
+--- If the checkbox is checked.
+-- @property checked
+-- @param boolean
+
+for _, prop in ipairs {"border_width", "bg", "border_color", "check_border_color",
+ "check_border_width", "check_color", "shape", "check_shape", "paddings",
+ "checked", "color" } do
+ checkbox["set_"..prop] = function(self, value)
+ self._private[prop] = value
+ self:emit_signal("property::"..prop)
+ self:emit_signal("widget::redraw_needed")
+ end
+ checkbox["get_"..prop] = function(self)
+ return self._private[prop] or beautiful["checkbox_"..prop]
+ end
+end
+
+--- The checkbox color.
+-- @property color
+
+function checkbox:set_paddings(val)
+ self._private.paddings = type(val) == "number" and {
+ left = val,
+ right = val,
+ top = val,
+ bottom = val,
+ } or val or {}
+ self:emit_signal("property::paddings")
+ self:emit_signal("widget::redraw_needed")
+end
+
+local function new(checked, args)
+ checked, args = checked or false, args or {}
+
+ local ret = base.make_widget(nil, nil, {
+ enable_properties = true,
+ })
+
+ util.table.crush(ret, checkbox)
+
+ ret._private.checked = checked
+ ret._private.color = args.color and color(args.color) or nil
+
+ rawset(ret, "fit" , fit )
+ rawset(ret, "draw", draw)
+
+ return ret
+end
+
+--Imported documentation
+
+
+--- Get a widex index.
+-- @param widget The widget to look for
+-- @param[opt] recursive Also check sub-widgets
+-- @param[opt] ... Aditional widgets to add at the end of the \"path\"
+-- @return The index
+-- @return The parent layout
+-- @return The path between \"self\" and \"widget\"
+-- @function index
+
+--- Get all direct and indirect children widgets.
+-- This will scan all containers recursively to find widgets
+-- Warning: This method it prone to stack overflow id the widget, or any of its
+-- children, contain (directly or indirectly) itself.
+-- @treturn table The children
+-- @function get_all_children
+
+--- Set a declarative widget hierarchy description.
+-- See [The declarative layout system](../documentation/03-declarative-layout.md.html)
+-- @param args An array containing the widgets disposition
+-- @function setup
+
+--- Force a widget height.
+-- @property forced_height
+-- @tparam number|nil height The height (`nil` for automatic)
+
+--- Force a widget width.
+-- @property forced_width
+-- @tparam number|nil width The width (`nil` for automatic)
+
+--- The widget opacity (transparency).
+-- @property opacity
+-- @tparam[opt=1] number opacity The opacity (between 0 and 1)
+
+--- The widget visibility.
+-- @property visible
+-- @param boolean
+
+--- Set/get a widget's buttons.
+-- @param _buttons The table of buttons that should bind to the widget.
+-- @function buttons
+
+--- Emit a signal and ensure all parent widgets in the hierarchies also
+-- forward the signal. This is useful to track signals when there is a dynamic
+-- set of containers and layouts wrapping the widget.
+-- @tparam string signal_name
+-- @param ... Other arguments
+-- @function emit_signal_recursive
+
+--- When the layout (size) change.
+-- This signal is emitted when the previous results of `:layout()` and `:fit()`
+-- are no longer valid. Unless this signal is emitted, `:layout()` and `:fit()`
+-- must return the same result when called with the same arguments.
+-- @signal widget::layout_changed
+-- @see widget::redraw_needed
+
+--- When the widget content changed.
+-- This signal is emitted when the content of the widget changes. The widget will
+-- be redrawn, it is not re-layouted. Put differently, it is assumed that
+-- `:layout()` and `:fit()` would still return the same results as before.
+-- @signal widget::redraw_needed
+-- @see widget::layout_changed
+
+--- When a mouse button is pressed over the widget.
+-- @signal button::press
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When a mouse button is released over the widget.
+-- @signal button::release
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse enter a widget.
+-- @signal mouse::enter
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse leave a widget.
+-- @signal mouse::leave
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+
+--Imported documentation
+
+
+--- Disconnect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback that should be disconnected
+-- @function disconnect_signal
+
+--- Emit a signal.
+--
+-- @tparam string name The name of the signal
+-- @param ... Extra arguments for the callback functions. Each connected
+-- function receives the object as first argument and then any extra arguments
+-- that are given to emit_signal()
+-- @function emit_signal
+
+--- Connect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function connect_signal
+
+--- Connect to a signal weakly. This allows the callback function to be garbage
+-- collected and automatically disconnects the signal when that happens.
+--
+-- **Warning:**
+-- Only use this function if you really, really, really know what you
+-- are doing.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function weak_connect_signal
+
+
+return setmetatable({}, { __call = function(_, ...) return new(...) end})
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/wibox/widget/graph.lua b/lib/wibox/widget/graph.lua
new file mode 100644
index 0000000..c03c726
--- /dev/null
+++ b/lib/wibox/widget/graph.lua
@@ -0,0 +1,575 @@
+---------------------------------------------------------------------------
+--- A graph widget.
+--
+-- The graph goes from left to right. To change this to right to left, use
+-- a `wibox.container.mirror` widget. This can also be used to have data
+-- shown from top to bottom.
+--
+-- To add text on top of the graph, use a `wibox.layout.stack` and a
+-- `wibox.container.align` widgets.
+--
+-- To display the graph vertically, use a `wibox.container.rotate` widget.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_widget_defaults_graph.svg)
+--
+-- @usage
+--wibox.widget {
+-- max_value = 29,
+-- widget = wibox.widget.graph
+--}
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2009 Julien Danjou
+-- @classmod wibox.widget.graph
+---------------------------------------------------------------------------
+
+local setmetatable = setmetatable
+local ipairs = ipairs
+local math = math
+local table = table
+local type = type
+local color = require("gears.color")
+local base = require("wibox.widget.base")
+local beautiful = require("beautiful")
+
+local graph = { mt = {} }
+
+--- Set the graph border color.
+-- If the value is nil, no border will be drawn.
+--
+-- @property border_color
+-- @tparam gears.color border_color The border color to set.
+-- @see gears.color
+
+--- Set the graph foreground color.
+--
+-- @property color
+-- @tparam color color The graph color.
+-- @see gears.color
+
+--- Set the graph background color.
+--
+-- @property background_color
+-- @tparam gears.color background_color The graph background color.
+-- @see gears.color
+
+--- Set the maximum value the graph should handle.
+-- If "scale" is also set, the graph never scales up below this value, but it
+-- automatically scales down to make all data fit.
+--
+-- @property max_value
+-- @param number
+
+--- The minimum value.
+-- Note that the min_value is not supported when used along with the stack
+-- property.
+-- @property min_value
+-- @param number
+
+--- Set the graph to automatically scale its values. Default is false.
+--
+-- @property scale
+-- @param boolean
+
+--- Set the width or the individual steps.
+--
+-- Note that it isn't supported when used along with stacked graphs.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_widget_graph_step.svg)
+--
+-- @usage
+--wibox.widget {
+-- max_value = 29,
+-- step_width = 3,
+-- step_spacing = 1,
+-- step_shape = function(cr, width, height)
+-- gears.shape.rounded_rect(cr, width, height, 2)
+-- end,
+-- widget = wibox.widget.graph
+--}
+--
+-- @property step_width
+-- @param[opt=1] number
+
+--- Set the spacing between the steps.
+--
+-- Note that it isn't supported when used along with stacked graphs.
+--
+-- @property step_spacing
+-- @param[opt=0] number
+
+--- The step shape.
+-- @property step_shape
+-- @param[opt=rectangle] shape
+-- @see gears.shape
+
+--- Set the graph to draw stacks. Default is false.
+--
+-- @property stack
+-- @param boolean
+
+--- Set the graph stacking colors. Order matters.
+--
+-- @property stack_colors
+-- @param stack_colors A table with stacking colors.
+
+--- The graph background color.
+-- @beautiful beautiful.graph_bg
+
+--- The graph foreground color.
+-- @beautiful beautiful.graph_fg
+
+--- The graph border color.
+-- @beautiful beautiful.graph_border_color
+
+local properties = { "width", "height", "border_color", "stack",
+ "stack_colors", "color", "background_color",
+ "max_value", "scale", "min_value", "step_shape",
+ "step_spacing", "step_width" }
+
+function graph.draw(_graph, _, cr, width, height)
+ local max_value = _graph._private.max_value
+ local min_value = _graph._private.min_value or (
+ _graph._private.scale and math.huge or 0)
+ local values = _graph._private.values
+
+ local step_shape = _graph._private.step_shape
+ local step_spacing = _graph._private.step_spacing or 0
+ local step_width = _graph._private.step_width or 1
+
+ cr:set_line_width(1)
+
+ -- Draw the background first
+ cr:set_source(color(_graph._private.background_color or beautiful.graph_bg or "#000000aa"))
+ cr:paint()
+
+ -- Account for the border width
+ cr:save()
+ if _graph._private.border_color then
+ cr:translate(1, 1)
+ width, height = width - 2, height - 2
+ end
+
+ -- Draw a stacked graph
+ if _graph._private.stack then
+
+ if _graph._private.scale then
+ for _, v in ipairs(values) do
+ for _, sv in ipairs(v) do
+ if sv > max_value then
+ max_value = sv
+ end
+ if min_value > sv then
+ min_value = sv
+ end
+ end
+ end
+ end
+
+ for i = 0, width do
+ local rel_i = 0
+ local rel_x = i + 0.5
+
+ if _graph._private.stack_colors then
+ for idx, col in ipairs(_graph._private.stack_colors) do
+ local stack_values = values[idx]
+ if stack_values and i < #stack_values then
+ local value = stack_values[#stack_values - i] + rel_i
+ cr:move_to(rel_x, height * (1 - (rel_i / max_value)))
+ cr:line_to(rel_x, height * (1 - (value / max_value)))
+ cr:set_source(color(col or beautiful.graph_fg or "#ff0000"))
+ cr:stroke()
+ rel_i = value
+ end
+ end
+ end
+ end
+ else
+ if _graph._private.scale then
+ for _, v in ipairs(values) do
+ if v > max_value then
+ max_value = v
+ end
+ if min_value > v then
+ min_value = v
+ end
+ end
+ end
+
+ -- Draw the background on no value
+ if #values ~= 0 then
+ -- Draw reverse
+ for i = 0, #values - 1 do
+ local value = values[#values - i]
+ if value >= 0 then
+ local x = i*step_width + ((i-1)*step_spacing) + 0.5
+ value = (value - min_value) / max_value
+ cr:move_to(x, height * (1 - value))
+
+ if step_shape then
+ cr:translate(step_width + (i>1 and step_spacing or 0), height * (1 - value))
+ step_shape(cr, step_width, height)
+ cr:translate(0, -(height * (1 - value)))
+ elseif step_width > 1 then
+ cr:rectangle(x, height * (1 - value), step_width, height)
+ else
+ cr:line_to(x, height)
+ end
+ end
+ end
+ cr:set_source(color(_graph._private.color or beautiful.graph_fg or "#ff0000"))
+
+ if step_shape or step_width > 1 then
+ cr:fill()
+ else
+ cr:stroke()
+ end
+ end
+
+ end
+
+ -- Undo the cr:translate() for the border and step shapes
+ cr:restore()
+
+ -- Draw the border last so that it overlaps already drawn values
+ if _graph._private.border_color then
+ -- We decremented these by two above
+ width, height = width + 2, height + 2
+
+ -- Draw the border
+ cr:rectangle(0.5, 0.5, width - 1, height - 1)
+ cr:set_source(color(_graph._private.border_color or beautiful.graph_border_color or "#ffffff"))
+ cr:stroke()
+ end
+end
+
+function graph.fit(_graph)
+ return _graph._private.width, _graph._private.height
+end
+
+--- Add a value to the graph
+--
+-- @param value The value to be added to the graph
+-- @param group The stack color group index.
+function graph:add_value(value, group)
+ value = value or 0
+ local values = self._private.values
+ local max_value = self._private.max_value
+ value = math.max(0, value)
+ if not self._private.scale then
+ value = math.min(max_value, value)
+ end
+
+ if self._private.stack and group then
+ if not self._private.values[group]
+ or type(self._private.values[group]) ~= "table"
+ then
+ self._private.values[group] = {}
+ end
+ values = self._private.values[group]
+ end
+ table.insert(values, value)
+
+ local border_width = 0
+ if self._private.border_color then border_width = 2 end
+
+ -- Ensure we never have more data than we can draw
+ while #values > self._private.width - border_width do
+ table.remove(values, 1)
+ end
+
+ self:emit_signal("widget::redraw_needed")
+ return self
+end
+
+--- Clear the graph.
+function graph:clear()
+ self._private.values = {}
+ self:emit_signal("widget::redraw_needed")
+ return self
+end
+
+--- Set the graph height.
+-- @param height The height to set.
+function graph:set_height(height)
+ if height >= 5 then
+ self._private.height = height
+ self:emit_signal("widget::layout_changed")
+ end
+ return self
+end
+
+--- Set the graph width.
+-- @param width The width to set.
+function graph:set_width(width)
+ if width >= 5 then
+ self._private.width = width
+ self:emit_signal("widget::layout_changed")
+ end
+ return self
+end
+
+-- Build properties function
+for _, prop in ipairs(properties) do
+ if not graph["set_" .. prop] then
+ graph["set_" .. prop] = function(_graph, value)
+ if _graph._private[prop] ~= value then
+ _graph._private[prop] = value
+ _graph:emit_signal("widget::redraw_needed")
+ end
+ return _graph
+ end
+ end
+ if not graph["get_" .. prop] then
+ graph["get_" .. prop] = function(_graph)
+ return _graph._private[prop]
+ end
+ end
+end
+
+--- Create a graph widget.
+-- @param args Standard widget() arguments. You should add width and height
+-- key to set graph geometry.
+-- @return A new graph widget.
+-- @function wibox.widget.graph
+function graph.new(args)
+ args = args or {}
+
+ local width = args.width or 100
+ local height = args.height or 20
+
+ if width < 5 or height < 5 then return end
+
+ local _graph = base.make_widget(nil, nil, {enable_properties = true})
+
+ _graph._private.width = width
+ _graph._private.height = height
+ _graph._private.values = {}
+ _graph._private.max_value = 1
+
+ -- Set methods
+ _graph.add_value = graph["add_value"]
+ _graph.clear = graph["clear"]
+ _graph.draw = graph.draw
+ _graph.fit = graph.fit
+
+ for _, prop in ipairs(properties) do
+ _graph["set_" .. prop] = graph["set_" .. prop]
+ _graph["get_" .. prop] = graph["get_" .. prop]
+ end
+
+ return _graph
+end
+
+function graph.mt:__call(...)
+ return graph.new(...)
+end
+
+--Imported documentation
+
+
+--- Get a widex index.
+-- @param widget The widget to look for
+-- @param[opt] recursive Also check sub-widgets
+-- @param[opt] ... Aditional widgets to add at the end of the \"path\"
+-- @return The index
+-- @return The parent layout
+-- @return The path between \"self\" and \"widget\"
+-- @function index
+
+--- Get all direct and indirect children widgets.
+-- This will scan all containers recursively to find widgets
+-- Warning: This method it prone to stack overflow id the widget, or any of its
+-- children, contain (directly or indirectly) itself.
+-- @treturn table The children
+-- @function get_all_children
+
+--- Set a declarative widget hierarchy description.
+-- See [The declarative layout system](../documentation/03-declarative-layout.md.html)
+-- @param args An array containing the widgets disposition
+-- @function setup
+
+--- Force a widget height.
+-- @property forced_height
+-- @tparam number|nil height The height (`nil` for automatic)
+
+--- Force a widget width.
+-- @property forced_width
+-- @tparam number|nil width The width (`nil` for automatic)
+
+--- The widget opacity (transparency).
+-- @property opacity
+-- @tparam[opt=1] number opacity The opacity (between 0 and 1)
+
+--- The widget visibility.
+-- @property visible
+-- @param boolean
+
+--- Set/get a widget's buttons.
+-- @param _buttons The table of buttons that should bind to the widget.
+-- @function buttons
+
+--- Emit a signal and ensure all parent widgets in the hierarchies also
+-- forward the signal. This is useful to track signals when there is a dynamic
+-- set of containers and layouts wrapping the widget.
+-- @tparam string signal_name
+-- @param ... Other arguments
+-- @function emit_signal_recursive
+
+--- When the layout (size) change.
+-- This signal is emitted when the previous results of `:layout()` and `:fit()`
+-- are no longer valid. Unless this signal is emitted, `:layout()` and `:fit()`
+-- must return the same result when called with the same arguments.
+-- @signal widget::layout_changed
+-- @see widget::redraw_needed
+
+--- When the widget content changed.
+-- This signal is emitted when the content of the widget changes. The widget will
+-- be redrawn, it is not re-layouted. Put differently, it is assumed that
+-- `:layout()` and `:fit()` would still return the same results as before.
+-- @signal widget::redraw_needed
+-- @see widget::layout_changed
+
+--- When a mouse button is pressed over the widget.
+-- @signal button::press
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When a mouse button is released over the widget.
+-- @signal button::release
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse enter a widget.
+-- @signal mouse::enter
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse leave a widget.
+-- @signal mouse::leave
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+
+--Imported documentation
+
+
+--- Disconnect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback that should be disconnected
+-- @function disconnect_signal
+
+--- Emit a signal.
+--
+-- @tparam string name The name of the signal
+-- @param ... Extra arguments for the callback functions. Each connected
+-- function receives the object as first argument and then any extra arguments
+-- that are given to emit_signal()
+-- @function emit_signal
+
+--- Connect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function connect_signal
+
+--- Connect to a signal weakly. This allows the callback function to be garbage
+-- collected and automatically disconnects the signal when that happens.
+--
+-- **Warning:**
+-- Only use this function if you really, really, really know what you
+-- are doing.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function weak_connect_signal
+
+
+return setmetatable(graph, graph.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/wibox/widget/imagebox.lua b/lib/wibox/widget/imagebox.lua
new file mode 100644
index 0000000..bdfa499
--- /dev/null
+++ b/lib/wibox/widget/imagebox.lua
@@ -0,0 +1,395 @@
+---------------------------------------------------------------------------
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_widget_defaults_imagebox.svg)
+--
+-- @usage
+--wibox.widget {
+-- image = beautiful.awesome_icon,
+-- resize = false,
+-- widget = wibox.widget.imagebox
+--}
+-- @author Uli Schlachter
+-- @copyright 2010 Uli Schlachter
+-- @classmod wibox.widget.imagebox
+---------------------------------------------------------------------------
+
+local base = require("wibox.widget.base")
+local surface = require("gears.surface")
+local util = require("awful.util")
+local setmetatable = setmetatable
+local type = type
+local print = print
+local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
+
+local imagebox = { mt = {} }
+
+-- Draw an imagebox with the given cairo context in the given geometry.
+function imagebox:draw(_, cr, width, height)
+ if not self._private.image then return end
+ if width == 0 or height == 0 then return end
+
+ if not self._private.resize_forbidden then
+ -- Let's scale the image so that it fits into (width, height)
+ local w = self._private.image:get_width()
+ local h = self._private.image:get_height()
+ local aspect = width / w
+ local aspect_h = height / h
+ if aspect > aspect_h then aspect = aspect_h end
+
+ cr:scale(aspect, aspect)
+ end
+
+ -- Set the clip
+ if self._private.clip_shape then
+ cr:clip(self._private.clip_shape(cr, width, height, unpack(self._private.clip_args)))
+ end
+
+ cr:set_source_surface(self._private.image, 0, 0)
+ cr:paint()
+end
+
+-- Fit the imagebox into the given geometry
+function imagebox:fit(_, width, height)
+ if not self._private.image then
+ return 0, 0
+ end
+
+ local w = self._private.image:get_width()
+ local h = self._private.image:get_height()
+
+ if w > width then
+ h = h * width / w
+ w = width
+ end
+ if h > height then
+ w = w * height / h
+ h = height
+ end
+
+ if h == 0 or w == 0 then
+ return 0, 0
+ end
+
+ if not self._private.resize_forbidden then
+ local aspect = width / w
+ local aspect_h = height / h
+
+ -- Use the smaller one of the two aspect ratios.
+ if aspect > aspect_h then aspect = aspect_h end
+
+ w, h = w * aspect, h * aspect
+ end
+
+ return w, h
+end
+
+--- Set an imagebox' image
+-- @property image
+-- @param image Either a string or a cairo image surface. A string is
+-- interpreted as the path to a png image file.
+-- @return true on success, false if the image cannot be used
+
+function imagebox:set_image(image)
+ if type(image) == "string" then
+ image = surface.load(image)
+ if not image then
+ print(debug.traceback())
+ return false
+ end
+ end
+
+ image = surface.load(image)
+
+ if image then
+ local w = image.width
+ local h = image.height
+ if w <= 0 or h <= 0 then
+ return false
+ end
+ end
+
+ if self._private.image == image then
+ -- The image could have been modified, so better redraw
+ self:emit_signal("widget::redraw_needed")
+ return
+ end
+
+ self._private.image = image
+
+ self:emit_signal("widget::redraw_needed")
+ self:emit_signal("widget::layout_changed")
+ return true
+end
+
+--- Set a clip shape for this imagebox
+-- A clip shape define an area where the content is displayed and one where it
+-- is trimmed.
+--
+-- @property clip_shape
+-- @param clip_shape A `gears_shape` compatible shape function
+-- @see gears.shape
+-- @see set_clip_shape
+
+--- Set a clip shape for this imagebox
+-- A clip shape define an area where the content is displayed and one where it
+-- is trimmed.
+--
+-- Any other parameters will be passed to the clip shape function
+--
+-- @param clip_shape A `gears_shape` compatible shape function
+-- @see gears.shape
+-- @see clip_shape
+function imagebox:set_clip_shape(clip_shape, ...)
+ self._private.clip_shape = clip_shape
+ self._private.clip_args = {...}
+ self:emit_signal("widget::redraw_needed")
+end
+
+--- Should the image be resized to fit into the available space?
+-- @property resize
+-- @param allowed If false, the image will be clipped, else it will be resized
+-- to fit into the available space.
+
+function imagebox:set_resize(allowed)
+ self._private.resize_forbidden = not allowed
+ self:emit_signal("widget::redraw_needed")
+ self:emit_signal("widget::layout_changed")
+end
+
+--- Returns a new imagebox.
+-- Any other arguments will be passed to the clip shape function
+-- @param image the image to display, may be nil
+-- @param resize_allowed If false, the image will be clipped, else it will be resized
+-- to fit into the available space.
+-- @param clip_shape A `gears.shape` compatible function
+-- @treturn table A new `imagebox`
+-- @function wibox.widget.imagebox
+local function new(image, resize_allowed, clip_shape)
+ local ret = base.make_widget(nil, nil, {enable_properties = true})
+
+ util.table.crush(ret, imagebox, true)
+
+ if image then
+ ret:set_image(image)
+ end
+ if resize_allowed ~= nil then
+ ret:set_resize(resize_allowed)
+ end
+
+ ret._private.clip_shape = clip_shape
+ ret._private.clip_args = {}
+
+ return ret
+end
+
+function imagebox.mt:__call(...)
+ return new(...)
+end
+
+--Imported documentation
+
+
+--- Get a widex index.
+-- @param widget The widget to look for
+-- @param[opt] recursive Also check sub-widgets
+-- @param[opt] ... Aditional widgets to add at the end of the \"path\"
+-- @return The index
+-- @return The parent layout
+-- @return The path between \"self\" and \"widget\"
+-- @function index
+
+--- Get all direct and indirect children widgets.
+-- This will scan all containers recursively to find widgets
+-- Warning: This method it prone to stack overflow id the widget, or any of its
+-- children, contain (directly or indirectly) itself.
+-- @treturn table The children
+-- @function get_all_children
+
+--- Set a declarative widget hierarchy description.
+-- See [The declarative layout system](../documentation/03-declarative-layout.md.html)
+-- @param args An array containing the widgets disposition
+-- @function setup
+
+--- Force a widget height.
+-- @property forced_height
+-- @tparam number|nil height The height (`nil` for automatic)
+
+--- Force a widget width.
+-- @property forced_width
+-- @tparam number|nil width The width (`nil` for automatic)
+
+--- The widget opacity (transparency).
+-- @property opacity
+-- @tparam[opt=1] number opacity The opacity (between 0 and 1)
+
+--- The widget visibility.
+-- @property visible
+-- @param boolean
+
+--- Set/get a widget's buttons.
+-- @param _buttons The table of buttons that should bind to the widget.
+-- @function buttons
+
+--- Emit a signal and ensure all parent widgets in the hierarchies also
+-- forward the signal. This is useful to track signals when there is a dynamic
+-- set of containers and layouts wrapping the widget.
+-- @tparam string signal_name
+-- @param ... Other arguments
+-- @function emit_signal_recursive
+
+--- When the layout (size) change.
+-- This signal is emitted when the previous results of `:layout()` and `:fit()`
+-- are no longer valid. Unless this signal is emitted, `:layout()` and `:fit()`
+-- must return the same result when called with the same arguments.
+-- @signal widget::layout_changed
+-- @see widget::redraw_needed
+
+--- When the widget content changed.
+-- This signal is emitted when the content of the widget changes. The widget will
+-- be redrawn, it is not re-layouted. Put differently, it is assumed that
+-- `:layout()` and `:fit()` would still return the same results as before.
+-- @signal widget::redraw_needed
+-- @see widget::layout_changed
+
+--- When a mouse button is pressed over the widget.
+-- @signal button::press
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When a mouse button is released over the widget.
+-- @signal button::release
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse enter a widget.
+-- @signal mouse::enter
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse leave a widget.
+-- @signal mouse::leave
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+
+--Imported documentation
+
+
+--- Disconnect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback that should be disconnected
+-- @function disconnect_signal
+
+--- Emit a signal.
+--
+-- @tparam string name The name of the signal
+-- @param ... Extra arguments for the callback functions. Each connected
+-- function receives the object as first argument and then any extra arguments
+-- that are given to emit_signal()
+-- @function emit_signal
+
+--- Connect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function connect_signal
+
+--- Connect to a signal weakly. This allows the callback function to be garbage
+-- collected and automatically disconnects the signal when that happens.
+--
+-- **Warning:**
+-- Only use this function if you really, really, really know what you
+-- are doing.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function weak_connect_signal
+
+
+return setmetatable(imagebox, imagebox.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/wibox/widget/init.lua b/lib/wibox/widget/init.lua
new file mode 100644
index 0000000..43c6fc0
--- /dev/null
+++ b/lib/wibox/widget/init.lua
@@ -0,0 +1,22 @@
+---------------------------------------------------------------------------
+-- @author Uli Schlachter
+-- @copyright 2010 Uli Schlachter
+-- @classmod wibox.widget
+---------------------------------------------------------------------------
+local base = require("wibox.widget.base")
+
+return setmetatable({
+ base = base;
+ textbox = require("wibox.widget.textbox");
+ imagebox = require("wibox.widget.imagebox");
+ background = require("wibox.widget.background");
+ systray = require("wibox.widget.systray");
+ textclock = require("wibox.widget.textclock");
+ progressbar = require("wibox.widget.progressbar");
+ graph = require("wibox.widget.graph");
+ checkbox = require("wibox.widget.checkbox");
+ piechart = require("wibox.widget.piechart");
+ slider = require("wibox.widget.slider");
+}, {__call = function(_, args) return base.make_widget_declarative(args) end})
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/wibox/widget/piechart.lua b/lib/wibox/widget/piechart.lua
new file mode 100644
index 0000000..5a1c185
--- /dev/null
+++ b/lib/wibox/widget/piechart.lua
@@ -0,0 +1,467 @@
+---------------------------------------------------------------------------
+-- Display percentage in a circle.
+--
+-- Note that this widget makes no attempts to prevent overlapping labels or
+-- labels drawn outside of the widget boundaries.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_widget_defaults_piechart.svg)
+--
+-- @usage
+--wibox.widget {
+-- data_list = {
+-- { 'L1', 100 },
+-- { 'L2', 200 },
+-- { 'L3', 300 },
+-- },
+-- border_width = 1,
+-- colors = {
+-- beautiful.bg_normal,
+-- beautiful.bg_highlight,
+-- beautiful.border_color,
+-- },
+-- widget = wibox.widget.piechart
+--}
+-- @author Emmanuel Lepage Valle
+-- @copyright 2012 Emmanuel Lepage Vallee
+-- @classmod wibox.widget.piechart
+---------------------------------------------------------------------------
+
+local color = require( "gears.color" )
+local base = require( "wibox.widget.base" )
+local beautiful = require( "beautiful" )
+local util = require( "awful.util" )
+local pie = require( "gears.shape" ).pie
+local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
+
+local module = {}
+
+local piechart = {}
+
+local function draw_label(cr,angle,radius,center_x,center_y,text)
+ local edge_x = center_x+(radius/2)*math.cos(angle)
+ local edge_y = center_y+(radius/2)*math.sin(angle)
+
+ cr:move_to(edge_x, edge_y)
+
+ cr:rel_line_to(radius*math.cos(angle), radius*math.sin(angle))
+
+ local x,y = cr:get_current_point()
+
+ cr:rel_line_to(x > center_x and radius/2 or -radius/2, 0)
+
+ local ext = cr:text_extents(text)
+
+ cr:rel_move_to(
+ (x>center_x and radius/2.5 or (-radius/2.5 - ext.width)),
+ ext.height/2
+ )
+
+ cr:show_text(text) --TODO eventually port away from the toy API
+ cr:stroke()
+
+ cr:arc(edge_x, edge_y,2,0,2*math.pi)
+ cr:arc(x+(x>center_x and radius/2 or -radius/2),y,2,0,2*math.pi)
+
+ cr:fill()
+end
+
+local function compute_sum(data)
+ local ret = 0
+ for _, entry in ipairs(data) do
+ ret = ret + entry[2]
+ end
+
+ return ret
+end
+
+local function draw(self, _, cr, width, height)
+ if not self._private.data_list then return end
+
+ local radius = (height > width and width or height) / 4
+ local sum, start, count = compute_sum(self._private.data_list),0,0
+ local has_label = self._private.display_labels ~= false
+
+ -- Labels need to be drawn later so the original source is kept
+ -- use get_source() wont work are the reference cannot be set from Lua(?)
+ local labels = {}
+
+ local border_width = self:get_border_width() or 1
+ local border_color = self:get_border_color()
+ border_color = border_color and color(border_color)
+
+ -- Draw the pies
+ cr:save()
+ cr:set_line_width(border_width)
+
+ -- Alternate from a given sets or colors
+ local colors = self:get_colors()
+ local col_count = colors and #colors or 0
+
+ for _, entry in ipairs(self._private.data_list) do
+ local k, v = entry[1], entry[2]
+ local end_angle = start + 2*math.pi*(v/sum)
+
+ local col = colors and color(colors[math.fmod(count,col_count)+1]) or nil
+
+ pie(cr, width, height, start, end_angle, radius)
+
+ if col then
+ cr:save()
+ cr:set_source(color(col))
+ end
+
+ if border_width > 0 then
+ if col then
+ cr:fill_preserve()
+ cr:restore()
+ end
+
+ -- By default, it uses the fg color
+ if border_color then
+ cr:set_source(border_color)
+ end
+ cr:stroke()
+ elseif col then
+ cr:fill()
+ cr:restore()
+ end
+
+ -- Store the label position for later
+ if has_label then
+ table.insert(labels, {
+ --[[angle ]] start+(end_angle-start)/2,
+ --[[radius ]] radius,
+ --[[center_x]] width/2,
+ --[[center_y]] height/2,
+ --[[text ]] k,
+ })
+ end
+ start,count = end_angle,count+1
+ end
+ cr:restore()
+
+ -- Draw the labels
+ if has_label then
+ for _, v in ipairs(labels) do
+ draw_label(cr, unpack(v))
+ end
+ end
+end
+
+local function fit(_, _, width, height)
+ return width, height
+end
+
+--- The pie chart data list.
+-- @property data_list
+-- @tparam table data_list Sorted table where each entry has a label as its
+-- first value and a number as its second value.
+
+--- The pie chart data.
+-- @property data
+-- @tparam table data Labels as keys and number as value.
+
+--- The border color.
+-- If none is set, it will use current foreground (text) color.
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_widget_piechart_border_color.svg)
+--
+-- @property border_color
+-- @param color
+-- @see gears.color
+
+--- The pie elements border width.
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_widget_piechart_border_width.svg)
+--
+-- @property border_width
+-- @tparam[opt=1] number border_width
+
+--- The pie chart colors.
+-- If no color is set, only the border will be drawn. If less colors than
+-- required are set, colors will be re-used in order.
+-- @property colors
+-- @tparam table colors A table of colors, one for each elements
+-- @see gears.color
+
+--- The border color.
+-- If none is set, it will use current foreground (text) color.
+-- @beautiful beautiful.piechart_border_color
+-- @param color
+-- @see gears.color
+
+--- If the pie chart has labels.
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_widget_piechart_label.svg)
+--
+-- @property display_labels
+-- @param[opt=true] boolean
+
+--- The pie elements border width.
+-- @beautiful beautiful.piechart_border_width
+-- @tparam[opt=1] number border_width
+
+--- The pie chart colors.
+-- If no color is set, only the border will be drawn. If less colors than
+-- required are set, colors will be re-used in order.
+-- @beautiful beautiful.piechart_colors
+-- @tparam table colors A table of colors, one for each elements
+-- @see gears.color
+
+for _, prop in ipairs {"data_list", "border_color", "border_width", "colors",
+ "display_labels"
+ } do
+ piechart["set_"..prop] = function(self, value)
+ self._private[prop] = value
+ self:emit_signal("property::"..prop)
+ if prop == "data_list" then
+ self:emit_signal("property::data")
+ end
+ self:emit_signal("widget::redraw_needed")
+ end
+ piechart["get_"..prop] = function(self)
+ return self._private[prop] or beautiful["piechart_"..prop]
+ end
+end
+
+function piechart:set_data(value)
+ local list = {}
+ for k, v in pairs(value) do
+ table.insert(list, { k, v })
+ end
+ self:set_data_list(list)
+end
+
+function piechart:get_data()
+ local list = {}
+ for _, entry in ipairs(self:get_data_list()) do
+ list[entry[1]] = entry[2]
+ end
+ return list
+end
+
+local function new(data_list)
+
+ local ret = base.make_widget(nil, nil, {
+ enable_properties = true,
+ })
+
+ util.table.crush(ret, piechart)
+
+ rawset(ret, "fit" , fit )
+ rawset(ret, "draw", draw)
+
+ ret:set_data_list(data_list)
+
+ return ret
+end
+
+--Imported documentation
+
+
+--- Get a widex index.
+-- @param widget The widget to look for
+-- @param[opt] recursive Also check sub-widgets
+-- @param[opt] ... Aditional widgets to add at the end of the \"path\"
+-- @return The index
+-- @return The parent layout
+-- @return The path between \"self\" and \"widget\"
+-- @function index
+
+--- Get all direct and indirect children widgets.
+-- This will scan all containers recursively to find widgets
+-- Warning: This method it prone to stack overflow id the widget, or any of its
+-- children, contain (directly or indirectly) itself.
+-- @treturn table The children
+-- @function get_all_children
+
+--- Set a declarative widget hierarchy description.
+-- See [The declarative layout system](../documentation/03-declarative-layout.md.html)
+-- @param args An array containing the widgets disposition
+-- @function setup
+
+--- Force a widget height.
+-- @property forced_height
+-- @tparam number|nil height The height (`nil` for automatic)
+
+--- Force a widget width.
+-- @property forced_width
+-- @tparam number|nil width The width (`nil` for automatic)
+
+--- The widget opacity (transparency).
+-- @property opacity
+-- @tparam[opt=1] number opacity The opacity (between 0 and 1)
+
+--- The widget visibility.
+-- @property visible
+-- @param boolean
+
+--- Set/get a widget's buttons.
+-- @param _buttons The table of buttons that should bind to the widget.
+-- @function buttons
+
+--- Emit a signal and ensure all parent widgets in the hierarchies also
+-- forward the signal. This is useful to track signals when there is a dynamic
+-- set of containers and layouts wrapping the widget.
+-- @tparam string signal_name
+-- @param ... Other arguments
+-- @function emit_signal_recursive
+
+--- When the layout (size) change.
+-- This signal is emitted when the previous results of `:layout()` and `:fit()`
+-- are no longer valid. Unless this signal is emitted, `:layout()` and `:fit()`
+-- must return the same result when called with the same arguments.
+-- @signal widget::layout_changed
+-- @see widget::redraw_needed
+
+--- When the widget content changed.
+-- This signal is emitted when the content of the widget changes. The widget will
+-- be redrawn, it is not re-layouted. Put differently, it is assumed that
+-- `:layout()` and `:fit()` would still return the same results as before.
+-- @signal widget::redraw_needed
+-- @see widget::layout_changed
+
+--- When a mouse button is pressed over the widget.
+-- @signal button::press
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When a mouse button is released over the widget.
+-- @signal button::release
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse enter a widget.
+-- @signal mouse::enter
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse leave a widget.
+-- @signal mouse::leave
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+
+--Imported documentation
+
+
+--- Disconnect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback that should be disconnected
+-- @function disconnect_signal
+
+--- Emit a signal.
+--
+-- @tparam string name The name of the signal
+-- @param ... Extra arguments for the callback functions. Each connected
+-- function receives the object as first argument and then any extra arguments
+-- that are given to emit_signal()
+-- @function emit_signal
+
+--- Connect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function connect_signal
+
+--- Connect to a signal weakly. This allows the callback function to be garbage
+-- collected and automatically disconnects the signal when that happens.
+--
+-- **Warning:**
+-- Only use this function if you really, really, really know what you
+-- are doing.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function weak_connect_signal
+
+
+return setmetatable(module, { __call = function(_, ...) return new(...) end })
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/wibox/widget/progressbar.lua b/lib/wibox/widget/progressbar.lua
new file mode 100644
index 0000000..943c49f
--- /dev/null
+++ b/lib/wibox/widget/progressbar.lua
@@ -0,0 +1,754 @@
+---------------------------------------------------------------------------
+--- A progressbar widget.
+--
+-- To add text on top of the progressbar, a `wibox.layout.stack` can be used:
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_widget_progressbar_text.svg)
+--
+--
+-- wibox.widget {
+-- {
+-- max_value = 1,
+-- value = 0.5,
+-- forced_height = 20,
+-- forced_width = 100,
+-- paddings = 1,
+-- border_width = 1,
+-- border_color = beautiful.border_color,
+-- widget = wibox.widget.progressbar,
+-- },
+-- {
+-- text = '50%',
+-- widget = wibox.widget.textbox,
+-- },
+-- layout = wibox.layout.stack
+-- }
+--
+-- To display the progressbar vertically, use a `wibox.container.rotate` widget:
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_widget_progressbar_vertical.svg)
+--
+--
+-- wibox.widget {
+-- {
+-- max_value = 1,
+-- value = 0.33,
+-- widget = wibox.widget.progressbar,
+-- },
+-- forced_height = 100,
+-- forced_width = 20,
+-- direction = 'east',
+-- layout = wibox.container.rotate,
+-- }
+--
+-- By default, this widget will take all the available size. To prevent this,
+-- a `wibox.container.constraint` widget or the `forced_width`/`forced_height`
+-- properties have to be used.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_widget_defaults_progressbar.svg)
+--
+-- @usage
+--wibox.widget {
+-- max_value = 1,
+-- value = 0.33,
+-- forced_height = 20,
+-- forced_width = 100,
+-- shape = gears.shape.rounded_bar,
+-- border_width = 2,
+-- border_color = beautiful.border_color,
+-- widget = wibox.widget.progressbar,
+--}
+--
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2009 Julien Danjou
+-- @classmod wibox.widget.progressbar
+---------------------------------------------------------------------------
+
+local setmetatable = setmetatable
+local ipairs = ipairs
+local math = math
+local util = require("awful.util")
+local base = require("wibox.widget.base")
+local color = require("gears.color")
+local beautiful = require("beautiful")
+local shape = require("gears.shape")
+
+local progressbar = { mt = {} }
+
+--- The progressbar border color.
+-- If the value is nil, no border will be drawn.
+--
+-- @property border_color
+-- @tparam gears.color color The border color to set.
+-- @see gears.color
+
+--- The progressbar border width.
+-- @property border_width
+
+--- The progressbar inner border color.
+-- If the value is nil, no border will be drawn.
+--
+-- @property bar_border_color
+-- @tparam gears.color color The border color to set.
+-- @see gears.color
+
+--- The progressbar inner border width.
+-- @property bar_border_width
+
+--- The progressbar foreground color.
+--
+-- @property color
+-- @tparam gears.color color The progressbar color.
+-- @see gears.color
+
+--- The progressbar background color.
+--
+-- @property background_color
+-- @tparam gears.color color The progressbar background color.
+-- @see gears.color
+
+--- The progressbar inner shape.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_widget_progressbar_bar_shape.svg)
+--
+-- @usage
+--for _, shape in ipairs {'rounded_bar', 'octogon', 'hexagon', 'powerline' } do
+-- l:add(wibox.widget {
+-- value = 0.33,
+-- bar_shape = gears.shape[shape],
+-- bar_border_color = beautiful.border_color,
+-- bar_border_width = 1,
+-- border_width = 2,
+-- border_color = beautiful.border_color,
+-- paddings = 1,
+-- widget = wibox.widget.progressbar,
+-- })
+--end
+--
+-- @property bar_shape
+-- @tparam[opt=gears.shape.rectangle] gears.shape shape
+-- @see gears.shape
+
+--- The progressbar shape.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_widget_progressbar_shape.svg)
+--
+-- @usage
+--for _, shape in ipairs {'rounded_bar', 'octogon', 'hexagon', 'powerline' } do
+-- l:add(wibox.widget {
+-- value = 0.33,
+-- shape = gears.shape[shape],
+-- border_width = 2,
+-- border_color = beautiful.border_color,
+-- widget = wibox.widget.progressbar,
+-- })
+--end
+--
+-- @property shape
+-- @tparam[opt=gears.shape.rectangle] gears.shape shape
+-- @see gears.shape
+
+--- Set the progressbar to draw vertically.
+-- This doesn't do anything anymore, use a `wibox.container.rotate` widget.
+-- @deprecated set_vertical
+-- @tparam boolean vertical
+
+--- Force the inner part (the bar) to fit in the background shape.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_widget_progressbar_clip.svg)
+--
+-- @usage
+--wibox.widget {
+-- value = 75,
+-- max_value = 100,
+-- border_width = 2,
+-- border_color = beautiful.border_color,
+-- color = beautiful.border_color,
+-- shape = gears.shape.rounded_bar,
+-- bar_shape = gears.shape.rounded_bar,
+-- clip = false,
+-- forced_height = 30,
+-- forced_width = 100,
+-- paddings = 5,
+-- margins = {
+-- top = 12,
+-- bottom = 12,
+-- },
+-- widget = wibox.widget.progressbar,
+--}
+--
+-- @property clip
+-- @tparam[opt=true] boolean clip
+
+--- The progressbar to draw ticks. Default is false.
+--
+-- @property ticks
+-- @param boolean
+
+--- The progressbar ticks gap.
+--
+-- @property ticks_gap
+-- @param number
+
+--- The progressbar ticks size.
+--
+-- @property ticks_size
+-- @param number
+
+--- The maximum value the progressbar should handle.
+--
+-- @property max_value
+-- @param number
+
+--- The progressbar background color.
+-- @beautiful beautiful.progressbar_bg
+
+--- The progressbar foreground color.
+-- @beautiful beautiful.progressbar_fg
+
+--- The progressbar shape.
+-- @beautiful beautiful.progressbar_shape
+-- @see gears.shape
+
+--- The progressbar border color.
+-- @beautiful beautiful.progressbar_border_color
+
+--- The progressbar outer border width.
+-- @beautiful beautiful.progressbar_border_width
+
+--- The progressbar inner shape.
+-- @beautiful beautiful.progressbar_bar_shape
+-- @see gears.shape
+
+--- The progressbar bar border width.
+-- @beautiful beautiful.progressbar_bar_border_width
+
+--- The progressbar bar border color.
+-- @beautiful beautiful.progressbar_bar_border_color
+
+--- The progressbar margins.
+-- Note that if the `clip` is disabled, this allows the background to be smaller
+-- than the bar.
+--
+-- See the `clip` example.
+--
+-- @tparam[opt=0] (table|number|nil) margins A table for each side or a number
+-- @tparam[opt=0] number margins.top
+-- @tparam[opt=0] number margins.bottom
+-- @tparam[opt=0] number margins.left
+-- @tparam[opt=0] number margins.right
+-- @property margins
+-- @see clip
+
+--- The progressbar padding.
+-- Note that if the `clip` is disabled, this allows the bar to be taller
+-- than the background.
+--
+-- See the `clip` example.
+--
+-- @tparam[opt=0] (table|number|nil) padding A table for each side or a number
+-- @tparam[opt=0] number padding.top
+-- @tparam[opt=0] number padding.bottom
+-- @tparam[opt=0] number padding.left
+-- @tparam[opt=0] number padding.right
+-- @property paddings
+-- @see clip
+
+--- The progressbar margins.
+-- Note that if the `clip` is disabled, this allows the background to be smaller
+-- than the bar.
+-- @tparam[opt=0] (table|number|nil) margins A table for each side or a number
+-- @tparam[opt=0] number margins.top
+-- @tparam[opt=0] number margins.bottom
+-- @tparam[opt=0] number margins.left
+-- @tparam[opt=0] number margins.right
+-- @beautiful beautiful.progressbar_margins
+-- @see clip
+
+--- The progressbar padding.
+-- Note that if the `clip` is disabled, this allows the bar to be taller
+-- than the background.
+-- @tparam[opt=0] (table|number|nil) padding A table for each side or a number
+-- @tparam[opt=0] number padding.top
+-- @tparam[opt=0] number padding.bottom
+-- @tparam[opt=0] number padding.left
+-- @tparam[opt=0] number padding.right
+-- @beautiful beautiful.progressbar_paddings
+-- @see clip
+
+local properties = { "border_color", "color" , "background_color",
+ "value" , "max_value" , "ticks",
+ "ticks_gap" , "ticks_size", "border_width",
+ "shape" , "bar_shape" , "bar_border_width",
+ "clip" , "margins" , "bar_border_color",
+ "paddings",
+ }
+
+function progressbar.draw(pbar, _, cr, width, height)
+ local ticks_gap = pbar._private.ticks_gap or 1
+ local ticks_size = pbar._private.ticks_size or 4
+
+ -- We want one pixel wide lines
+ cr:set_line_width(1)
+
+ local max_value = pbar._private.max_value
+
+ local value = math.min(max_value, math.max(0, pbar._private.value))
+
+ if value >= 0 then
+ value = value / max_value
+ end
+ local border_width = pbar._private.border_width
+ or beautiful.progressbar_border_width or 0
+
+ local bcol = pbar._private.border_color or beautiful.progressbar_border_color
+
+ border_width = bcol and border_width or 0
+
+ local bg = pbar._private.background_color or
+ beautiful.progressbar_bg or "#ff0000aa"
+
+ local bg_width, bg_height = width, height
+
+ local clip = pbar._private.clip ~= false and beautiful.progressbar_clip ~= false
+
+ -- Apply the margins
+ local margin = pbar._private.margins or beautiful.progressbar_margins
+
+ if margin then
+ if type(margin) == "number" then
+ cr:translate(margin, margin)
+ bg_width, bg_height = bg_width - 2*margin, bg_height - 2*margin
+ else
+ cr:translate(margin.left or 0, margin.top or 0)
+ bg_height = bg_height -
+ (margin.top or 0) - (margin.bottom or 0)
+ bg_width = bg_width -
+ (margin.left or 0) - (margin.right or 0)
+ end
+ end
+
+ -- Draw the background shape
+ if border_width > 0 then
+ -- Cairo draw half of the border outside of the path area
+ cr:translate(border_width/2, border_width/2)
+ bg_width, bg_height = bg_width - border_width, bg_height - border_width
+ cr:set_line_width(border_width)
+ end
+
+ local background_shape = pbar._private.shape or
+ beautiful.progressbar_shape or shape.rectangle
+
+ background_shape(cr, bg_width, bg_height)
+
+ cr:set_source(color(bg))
+
+ local over_drawn_width = bg_width + border_width
+ local over_drawn_height = bg_height + border_width
+
+ if border_width > 0 then
+ cr:fill_preserve()
+
+ -- Draw the border
+ cr:set_source(color(bcol))
+
+ cr:stroke()
+
+ over_drawn_width = over_drawn_width - 2*border_width
+ over_drawn_height = over_drawn_height - 2*border_width
+ else
+ cr:fill()
+ end
+
+ -- Undo the translation
+ cr:translate(-border_width/2, -border_width/2)
+
+ -- Make sure the bar stay in the shape
+ if clip then
+ background_shape(cr, bg_width, bg_height)
+ cr:clip()
+ cr:translate(border_width, border_width)
+ else
+ -- Assume the background size is irrelevant to the bar itself
+ if type(margin) == "number" then
+ cr:translate(-margin, -margin)
+ else
+ cr:translate(-(margin.left or 0), -(margin.top or 0))
+ end
+
+ over_drawn_height = height
+ over_drawn_width = width
+ end
+
+ -- Apply the padding
+ local padding = pbar._private.paddings or beautiful.progressbar_paddings
+
+ if padding then
+ if type(padding) == "number" then
+ cr:translate(padding, padding)
+ over_drawn_height = over_drawn_height - 2*padding
+ over_drawn_width = over_drawn_width - 2*padding
+ else
+ cr:translate(padding.left or 0, padding.top or 0)
+
+ over_drawn_height = over_drawn_height -
+ (padding.top or 0) - (padding.bottom or 0)
+ over_drawn_width = over_drawn_width -
+ (padding.left or 0) - (padding.right or 0)
+ end
+ end
+
+ over_drawn_width = math.max(over_drawn_width , 0)
+ over_drawn_height = math.max(over_drawn_height, 0)
+
+ local rel_x = over_drawn_width * value
+
+
+ -- Draw the progressbar shape
+
+ local bar_shape = pbar._private.bar_shape or
+ beautiful.progressbar_bar_shape or shape.rectangle
+
+ local bar_border_width = pbar._private.bar_border_width or
+ beautiful.progressbar_bar_border_width or pbar._private.border_width or
+ beautiful.progressbar_border_width or 0
+
+ local bar_border_color = pbar._private.bar_border_color or
+ beautiful.progressbar_bar_border_color
+
+ bar_border_width = bar_border_color and bar_border_width or 0
+
+ over_drawn_width = over_drawn_width - bar_border_width
+ over_drawn_height = over_drawn_height - bar_border_width
+ cr:translate(bar_border_width/2, bar_border_width/2)
+
+ bar_shape(cr, rel_x, over_drawn_height)
+
+ cr:set_source(color(pbar._private.color or beautiful.progressbar_fg or "#ff0000"))
+
+ if bar_border_width > 0 then
+ cr:fill_preserve()
+ cr:set_source(color(bar_border_color))
+ cr:set_line_width(bar_border_width)
+ cr:stroke()
+ else
+ cr:fill()
+ end
+
+ if pbar._private.ticks then
+ for i=0, width / (ticks_size+ticks_gap)-border_width do
+ local rel_offset = over_drawn_width / 1 - (ticks_size+ticks_gap) * i
+
+ if rel_offset <= rel_x then
+ cr:rectangle(rel_offset,
+ border_width,
+ ticks_gap,
+ over_drawn_height)
+ end
+ end
+ cr:set_source(color(pbar._private.background_color or "#000000aa"))
+ cr:fill()
+ end
+end
+
+function progressbar:fit(_, width, height)
+ return width, height
+end
+
+--- Set the progressbar value.
+-- @param value The progress bar value between 0 and 1.
+function progressbar:set_value(value)
+ value = value or 0
+
+ self._private.value = value
+
+ self:emit_signal("widget::redraw_needed")
+ return self
+end
+
+function progressbar:set_max_value(max_value)
+
+ self._private.max_value = max_value
+
+ self:emit_signal("widget::redraw_needed")
+end
+
+--- Set the progressbar height.
+-- This method is deprecated. Use a `wibox.container.constraint` widget or
+-- `forced_height`.
+-- @param height The height to set.
+-- @deprecated set_height
+function progressbar:set_height(height)
+ util.deprecate("Use a `wibox.container.constraint` widget or `forced_height`")
+ self:set_forced_height(height)
+end
+
+--- Set the progressbar width.
+-- This method is deprecated. Use a `wibox.container.constraint` widget or
+-- `forced_width`.
+-- @param width The width to set.
+-- @deprecated set_width
+function progressbar:set_width(width)
+ util.deprecate("Use a `wibox.container.constraint` widget or `forced_width`")
+ self:set_forced_width(width)
+end
+
+-- Build properties function
+for _, prop in ipairs(properties) do
+ if not progressbar["set_" .. prop] then
+ progressbar["set_" .. prop] = function(pbar, value)
+ pbar._private[prop] = value
+ pbar:emit_signal("widget::redraw_needed")
+ return pbar
+ end
+ end
+end
+
+function progressbar:set_vertical(value) --luacheck: no unused_args
+ util.deprecate("Use a `wibox.container.rotate` widget")
+end
+
+
+--- Create a progressbar widget.
+-- @param args Standard widget() arguments. You should add width and height
+-- key to set progressbar geometry.
+-- @return A progressbar widget.
+-- @function wibox.widget.progressbar
+function progressbar.new(args)
+ args = args or {}
+
+ local pbar = base.make_widget(nil, nil, {
+ enable_properties = true,
+ })
+
+ pbar._private.width = args.width or 100
+ pbar._private.height = args.height or 20
+ pbar._private.value = 0
+ pbar._private.max_value = 1
+
+ util.table.crush(pbar, progressbar, true)
+
+ return pbar
+end
+
+function progressbar.mt:__call(...)
+ return progressbar.new(...)
+end
+
+--Imported documentation
+
+
+--- Get a widex index.
+-- @param widget The widget to look for
+-- @param[opt] recursive Also check sub-widgets
+-- @param[opt] ... Aditional widgets to add at the end of the \"path\"
+-- @return The index
+-- @return The parent layout
+-- @return The path between \"self\" and \"widget\"
+-- @function index
+
+--- Get all direct and indirect children widgets.
+-- This will scan all containers recursively to find widgets
+-- Warning: This method it prone to stack overflow id the widget, or any of its
+-- children, contain (directly or indirectly) itself.
+-- @treturn table The children
+-- @function get_all_children
+
+--- Set a declarative widget hierarchy description.
+-- See [The declarative layout system](../documentation/03-declarative-layout.md.html)
+-- @param args An array containing the widgets disposition
+-- @function setup
+
+--- Force a widget height.
+-- @property forced_height
+-- @tparam number|nil height The height (`nil` for automatic)
+
+--- Force a widget width.
+-- @property forced_width
+-- @tparam number|nil width The width (`nil` for automatic)
+
+--- The widget opacity (transparency).
+-- @property opacity
+-- @tparam[opt=1] number opacity The opacity (between 0 and 1)
+
+--- The widget visibility.
+-- @property visible
+-- @param boolean
+
+--- Set/get a widget's buttons.
+-- @param _buttons The table of buttons that should bind to the widget.
+-- @function buttons
+
+--- Emit a signal and ensure all parent widgets in the hierarchies also
+-- forward the signal. This is useful to track signals when there is a dynamic
+-- set of containers and layouts wrapping the widget.
+-- @tparam string signal_name
+-- @param ... Other arguments
+-- @function emit_signal_recursive
+
+--- When the layout (size) change.
+-- This signal is emitted when the previous results of `:layout()` and `:fit()`
+-- are no longer valid. Unless this signal is emitted, `:layout()` and `:fit()`
+-- must return the same result when called with the same arguments.
+-- @signal widget::layout_changed
+-- @see widget::redraw_needed
+
+--- When the widget content changed.
+-- This signal is emitted when the content of the widget changes. The widget will
+-- be redrawn, it is not re-layouted. Put differently, it is assumed that
+-- `:layout()` and `:fit()` would still return the same results as before.
+-- @signal widget::redraw_needed
+-- @see widget::layout_changed
+
+--- When a mouse button is pressed over the widget.
+-- @signal button::press
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When a mouse button is released over the widget.
+-- @signal button::release
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse enter a widget.
+-- @signal mouse::enter
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse leave a widget.
+-- @signal mouse::leave
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+
+--Imported documentation
+
+
+--- Disconnect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback that should be disconnected
+-- @function disconnect_signal
+
+--- Emit a signal.
+--
+-- @tparam string name The name of the signal
+-- @param ... Extra arguments for the callback functions. Each connected
+-- function receives the object as first argument and then any extra arguments
+-- that are given to emit_signal()
+-- @function emit_signal
+
+--- Connect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function connect_signal
+
+--- Connect to a signal weakly. This allows the callback function to be garbage
+-- collected and automatically disconnects the signal when that happens.
+--
+-- **Warning:**
+-- Only use this function if you really, really, really know what you
+-- are doing.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function weak_connect_signal
+
+
+return setmetatable(progressbar, progressbar.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/wibox/widget/slider.lua b/lib/wibox/widget/slider.lua
new file mode 100644
index 0000000..fd590c6
--- /dev/null
+++ b/lib/wibox/widget/slider.lua
@@ -0,0 +1,709 @@
+---------------------------------------------------------------------------
+-- An interactive mouse based slider widget.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_widget_defaults_slider.svg)
+--
+-- @usage
+--wibox.widget {
+-- bar_shape = gears.shape.rounded_rect,
+-- bar_height = 3,
+-- bar_color = beautiful.border_color,
+-- handle_color = beautiful.bg_normal,
+-- handle_shape = gears.shape.circle,
+-- handle_border_color = beautiful.border_color,
+-- handle_border_width = 1,
+-- value = 25,
+-- widget = wibox.widget.slider,
+--}
+--
+-- @author Grigory Mishchenko &lt;grishkokot@gmail.com&gt;
+-- @author Emmanuel Lepage Vallee &lt;elv1313@gmail.com&gt;
+-- @copyright 2015 Grigory Mishchenko, 2016 Emmanuel Lepage Vallee
+-- @classmod wibox.widget.slider
+---------------------------------------------------------------------------
+
+local setmetatable = setmetatable
+local type = type
+local color = require("gears.color")
+local util = require("awful.util")
+local beautiful = require("beautiful")
+local base = require("wibox.widget.base")
+local shape = require("gears.shape")
+local capi = {
+ mouse = mouse,
+ mousegrabber = mousegrabber,
+ root = root,
+}
+
+local slider = {mt={}}
+
+--- The slider handle shape.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_widget_slider_handle_shape.svg)
+--
+--
+-- @property handle_shape
+-- @tparam[opt=gears shape rectangle] gears.shape shape
+-- @see gears.shape
+
+--- The slider handle color.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_widget_slider_handle_color.svg)
+--
+--
+-- @property handle_color
+-- @param color
+
+--- The slider handle margins.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_widget_slider_handle_margins.svg)
+--
+--
+-- @property handle_margins
+-- @tparam[opt={}] table margins
+-- @tparam[opt=0] number margins.left
+-- @tparam[opt=0] number margins.right
+-- @tparam[opt=0] number margins.top
+-- @tparam[opt=0] number margins.bottom
+
+--- The slider handle width.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_widget_slider_handle_width.svg)
+--
+--
+-- @property handle_width
+-- @param number
+
+--- The handle border_color.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_widget_slider_handle_border.svg)
+--
+--
+-- @property handle_border_color
+-- @param color
+
+--- The handle border width.
+-- @property handle_border_width
+-- @param[opt=0] number
+
+--- The bar (background) shape.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_widget_slider_bar_shape.svg)
+--
+--
+-- @property bar_shape
+-- @tparam[opt=gears shape rectangle] gears.shape shape
+-- @see gears.shape
+
+--- The bar (background) height.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_widget_slider_bar_height.svg)
+--
+--
+-- @property bar_height
+-- @param number
+
+--- The bar (background) color.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_widget_slider_bar_color.svg)
+--
+--
+-- @property bar_color
+-- @param color
+
+--- The bar (background) margins.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_widget_slider_bar_margins.svg)
+--
+--
+-- @property bar_margins
+-- @tparam[opt={}] table margins
+-- @tparam[opt=0] number margins.left
+-- @tparam[opt=0] number margins.right
+-- @tparam[opt=0] number margins.top
+-- @tparam[opt=0] number margins.bottom
+
+--- The bar (background) border width.
+-- @property bar_border_width
+-- @param[opt=0] numbergb
+
+--- The bar (background) border_color.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_widget_slider_bar_border.svg)
+--
+--
+-- @property bar_border_color
+-- @param color
+
+--- The slider value.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_widget_slider_value.svg)
+--
+--
+-- @property value
+-- @param[opt=0] number
+
+--- The slider minimum value.
+-- @property minimum
+-- @param[opt=0] number
+
+--- The slider maximum value.
+-- @property maximum
+-- @param[opt=100] number
+
+--- The bar (background) border width.
+-- @beautiful beautiful.slider_bar_border_width
+-- @param number
+
+--- The bar (background) border color.
+-- @beautiful beautiful.slider_bar_border_color
+-- @param color
+
+--- The handle border_color.
+-- @beautiful beautiful.slider_handle_border_color
+-- @param color
+
+--- The handle border width.
+-- @beautiful beautiful.slider_handle_border_width
+-- @param number
+
+--- The handle .
+-- @beautiful beautiful.slider_handle_width
+-- @param number
+
+-- @beautiful beautiful.slider_handle_color
+-- @param color
+
+--- The handle shape.
+-- @beautiful beautiful.slider_handle_shape
+-- @tparam[opt=gears shape rectangle] gears.shape shape
+-- @see gears.shape
+
+--- The bar (background) shape.
+-- @beautiful beautiful.slider_bar_shape
+-- @tparam[opt=gears shape rectangle] gears.shape shape
+-- @see gears.shape
+
+--- The bar (background) height.
+-- @beautiful beautiful.slider_bar_height
+-- @param number
+
+--- The bar (background) margins.
+-- @beautiful beautiful.slider_bar_margins
+-- @tparam[opt={}] table margins
+-- @tparam[opt=0] number margins.left
+-- @tparam[opt=0] number margins.right
+-- @tparam[opt=0] number margins.top
+-- @tparam[opt=0] number margins.bottom
+
+--- The slider handle margins.
+-- @beautiful beautiful.slider_handle_margins
+-- @tparam[opt={}] table margins
+-- @tparam[opt=0] number margins.left
+-- @tparam[opt=0] number margins.right
+-- @tparam[opt=0] number margins.top
+-- @tparam[opt=0] number margins.bottom
+
+--- The bar (background) color.
+-- @beautiful beautiful.slider_bar_color
+-- @param color
+
+local properties = {
+ -- Handle
+ handle_shape = shape.rectangle,
+ handle_color = false,
+ handle_margins = {},
+ handle_width = false,
+ handle_border_width = 0,
+ handle_border_color = false,
+
+ -- Bar
+ bar_shape = shape.rectangle,
+ bar_height = false,
+ bar_color = false,
+ bar_margins = {},
+ bar_border_width = 0,
+ bar_border_color = false,
+
+ -- Content
+ value = 0,
+ minimum = 0,
+ maximum = 100,
+}
+
+-- Create the accessors
+for prop in pairs(properties) do
+ slider["set_"..prop] = function(self, value)
+ local changed = self._private[prop] ~= value
+ self._private[prop] = value
+
+ if changed then
+ self:emit_signal("property::"..prop)
+ self:emit_signal("widget::redraw_needed")
+ end
+ end
+
+ slider["get_"..prop] = function(self)
+ -- Ignoring the false's is on purpose
+ return self._private[prop] == nil
+ and properties[prop]
+ or self._private[prop]
+ end
+end
+
+-- Add some validation to set_value
+function slider:set_value(value)
+ value = math.min(value, self:get_maximum())
+ value = math.max(value, self:get_minimum())
+ local changed = self._private.value ~= value
+
+ self._private.value = value
+
+ if changed then
+ self:emit_signal( "property::value" )
+ self:emit_signal( "widget::redraw_needed" )
+ end
+end
+
+local function get_extremums(self)
+ local min = self._private.minimum or properties.minimum
+ local max = self._private.maximum or properties.maximum
+ local interval = max - min
+
+ return min, max, interval
+end
+
+function slider:draw(_, cr, width, height)
+ local bar_height = self._private.bar_height
+
+ -- If there is no background, then skip this
+ local bar_color = self._private.bar_color
+ or beautiful.slider_bar_color
+
+ if bar_color then
+ cr:set_source(color(bar_color))
+ end
+
+ local margins = self._private.bar_margins
+ or beautiful.slider_bar_margins
+
+ local x_offset, right_margin, y_offset = 0, 0
+
+ if margins then
+ if type(margins) == "number" then
+ bar_height = bar_height or (height - 2*margins)
+ x_offset, y_offset = margins, margins
+ right_margin = margins
+ else
+ bar_height = bar_height or (
+ height - (margins.top or 0) - (margins.bottom or 0)
+ )
+ x_offset, y_offset = margins.left or 0, margins.top or 0
+ right_margin = margins.right or 0
+ end
+ else
+ bar_height = bar_height or beautiful.slider_bar_height or height
+ y_offset = (height - bar_height)/2
+ end
+
+
+ cr:translate(x_offset, y_offset)
+
+ local bar_shape = self._private.bar_shape
+ or beautiful.slider_bar_shape
+ or properties.bar_shape
+
+ local bar_border_width = self._private.bar_border_width
+ or beautiful.slider_bar_border_width
+ or properties.bar_border_width
+
+ bar_shape(cr, width - x_offset - right_margin, bar_height or height)
+
+ if bar_color then
+ if bar_border_width == 0 then
+ cr:fill()
+ else
+ cr:fill_preserve()
+ end
+ end
+
+ -- Draw the bar border
+ if bar_border_width > 0 then
+ local bar_border_color = self._private.bar_border_color
+ or beautiful.slider_bar_border_color
+ or properties.bar_border_color
+
+ cr:set_line_width(bar_border_width)
+
+ if bar_border_color then
+ cr:save()
+ cr:set_source(color(bar_border_color))
+ cr:stroke()
+ cr:restore()
+ else
+ cr:stroke()
+ end
+ end
+
+ cr:translate(-x_offset, -y_offset)
+
+ -- Paint the handle
+ local handle_color = self._private.handle_color
+ or beautiful.slider_handle_color
+
+ -- It is ok if there is no color, it will be inherited
+ if handle_color then
+ cr:set_source(color(handle_color))
+ end
+
+ local handle_height, handle_width = height, self._private.handle_width
+ or beautiful.slider_handle_width
+ or height/2
+
+ local handle_shape = self._private.handle_shape
+ or beautiful.slider_handle_shape
+ or properties.handle_shape
+
+ -- Lets get the margins for the handle
+ margins = self._private.handle_margins
+ or beautiful.slider_handle_margins
+
+ x_offset, y_offset = 0, 0
+
+ if margins then
+ if type(margins) == "number" then
+ x_offset, y_offset = margins, margins
+ handle_width = handle_width - 2*margins
+ handle_height = handle_height - 2*margins
+ else
+ x_offset, y_offset = margins.left or 0, margins.top or 0
+ handle_width = handle_width -
+ (margins.left or 0) - (margins.right or 0)
+ handle_height = handle_height -
+ (margins.top or 0) - (margins.bottom or 0)
+ end
+ end
+
+ local value = self._private.value or self._private.min or 0
+
+ -- Get the widget size back to it's non-transfored value
+ local min, _, interval = get_extremums(self)
+ local rel_value = ((value-min)/interval) * (width-handle_width)
+
+ cr:translate(x_offset + rel_value, y_offset)
+
+ local handle_border_width = self._private.handle_border_width
+ or beautiful.slider_handle_border_width
+ or properties.handle_border_width or 0
+
+ handle_shape(cr, handle_width, handle_height)
+
+ if handle_border_width > 0 then
+ cr:fill_preserve()
+ else
+ cr:fill()
+ end
+
+ -- Draw the handle border
+ if handle_border_width > 0 then
+ local handle_border_color = self._private.handle_border_color
+ or beautiful.slider_handle_border_color
+ or properties.handle_border_color
+
+ if handle_border_color then
+ cr:set_source(color(handle_border_color))
+ end
+
+ cr:set_line_width(handle_border_width)
+ cr:stroke()
+ end
+end
+
+function slider:fit(_, width, height)
+ -- Use all the space, this should be used with a constraint widget
+ return width, height
+end
+
+-- Move the handle to the correct location
+local function move_handle(self, width, x, _)
+ local _, _, interval = get_extremums(self)
+ self:set_value(math.floor((x*interval)/width))
+end
+
+local function mouse_press(self, x, y, button_id, _, geo)
+ if button_id ~= 1 then return end
+
+ local matrix_from_device = geo.hierarchy:get_matrix_from_device()
+
+ -- Sigh. geo.width/geo.height is in device space. We need it in our own
+ -- coordinate system
+ local width = geo.widget_width
+
+ move_handle(self, width, x, y)
+
+ -- Calculate a matrix transforming from screen coordinates into widget coordinates
+ local wgeo = geo.drawable.drawable:geometry()
+ local matrix = matrix_from_device:translate(-wgeo.x, -wgeo.y)
+
+ capi.mousegrabber.run(function(mouse)
+ if not mouse.buttons[1] then
+ return false
+ end
+
+ -- Calculate the point relative to the widget
+ move_handle(self, width, matrix:transform_point(mouse.x, mouse.y))
+
+ return true
+ end,"fleur")
+end
+
+--- Create a slider widget.
+-- @tparam[opt={}] table args
+-- @function wibox.widget.slider
+local function new(args)
+ local ret = base.make_widget(nil, nil, {
+ enable_properties = true,
+ })
+
+ util.table.crush(ret._private, args or {})
+
+ util.table.crush(ret, slider, true)
+
+ ret:connect_signal("button::press", mouse_press)
+
+ return ret
+end
+
+function slider.mt:__call(_, ...)
+ return new(...)
+end
+
+--Imported documentation
+
+
+--- Get a widex index.
+-- @param widget The widget to look for
+-- @param[opt] recursive Also check sub-widgets
+-- @param[opt] ... Aditional widgets to add at the end of the \"path\"
+-- @return The index
+-- @return The parent layout
+-- @return The path between \"self\" and \"widget\"
+-- @function index
+
+--- Get all direct and indirect children widgets.
+-- This will scan all containers recursively to find widgets
+-- Warning: This method it prone to stack overflow id the widget, or any of its
+-- children, contain (directly or indirectly) itself.
+-- @treturn table The children
+-- @function get_all_children
+
+--- Set a declarative widget hierarchy description.
+-- See [The declarative layout system](../documentation/03-declarative-layout.md.html)
+-- @param args An array containing the widgets disposition
+-- @function setup
+
+--- Force a widget height.
+-- @property forced_height
+-- @tparam number|nil height The height (`nil` for automatic)
+
+--- Force a widget width.
+-- @property forced_width
+-- @tparam number|nil width The width (`nil` for automatic)
+
+--- The widget opacity (transparency).
+-- @property opacity
+-- @tparam[opt=1] number opacity The opacity (between 0 and 1)
+
+--- The widget visibility.
+-- @property visible
+-- @param boolean
+
+--- Set/get a widget's buttons.
+-- @param _buttons The table of buttons that should bind to the widget.
+-- @function buttons
+
+--- Emit a signal and ensure all parent widgets in the hierarchies also
+-- forward the signal. This is useful to track signals when there is a dynamic
+-- set of containers and layouts wrapping the widget.
+-- @tparam string signal_name
+-- @param ... Other arguments
+-- @function emit_signal_recursive
+
+--- When the layout (size) change.
+-- This signal is emitted when the previous results of `:layout()` and `:fit()`
+-- are no longer valid. Unless this signal is emitted, `:layout()` and `:fit()`
+-- must return the same result when called with the same arguments.
+-- @signal widget::layout_changed
+-- @see widget::redraw_needed
+
+--- When the widget content changed.
+-- This signal is emitted when the content of the widget changes. The widget will
+-- be redrawn, it is not re-layouted. Put differently, it is assumed that
+-- `:layout()` and `:fit()` would still return the same results as before.
+-- @signal widget::redraw_needed
+-- @see widget::layout_changed
+
+--- When a mouse button is pressed over the widget.
+-- @signal button::press
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When a mouse button is released over the widget.
+-- @signal button::release
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse enter a widget.
+-- @signal mouse::enter
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse leave a widget.
+-- @signal mouse::leave
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+
+--Imported documentation
+
+
+--- Disconnect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback that should be disconnected
+-- @function disconnect_signal
+
+--- Emit a signal.
+--
+-- @tparam string name The name of the signal
+-- @param ... Extra arguments for the callback functions. Each connected
+-- function receives the object as first argument and then any extra arguments
+-- that are given to emit_signal()
+-- @function emit_signal
+
+--- Connect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function connect_signal
+
+--- Connect to a signal weakly. This allows the callback function to be garbage
+-- collected and automatically disconnects the signal when that happens.
+--
+-- **Warning:**
+-- Only use this function if you really, really, really know what you
+-- are doing.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function weak_connect_signal
+
+
+return setmetatable(slider, slider.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/wibox/widget/systray.lua b/lib/wibox/widget/systray.lua
new file mode 100644
index 0000000..e83347c
--- /dev/null
+++ b/lib/wibox/widget/systray.lua
@@ -0,0 +1,186 @@
+---------------------------------------------------------------------------
+-- @author Uli Schlachter
+-- @copyright 2010 Uli Schlachter
+-- @classmod wibox.widget.systray
+---------------------------------------------------------------------------
+
+local wbase = require("wibox.widget.base")
+local beautiful = require("beautiful")
+local util = require("awful.util")
+local capi = {
+ awesome = awesome,
+ screen = screen
+}
+local setmetatable = setmetatable
+local error = error
+local abs = math.abs
+
+local systray = { mt = {} }
+
+local instance = nil
+local horizontal = true
+local base_size = nil
+local reverse = false
+local display_on_screen = "primary"
+
+--- The systray background color.
+-- @beautiful beautiful.bg_systray
+-- @param string The color (string like "#ff0000" only)
+
+--- The systray icon spacing.
+-- @beautiful beautiful.systray_icon_spacing
+-- @tparam[opt=0] integer The icon spacing
+
+local function should_display_on(s)
+ if display_on_screen == "primary" then
+ return s == capi.screen.primary
+ end
+ return s == display_on_screen
+end
+
+function systray:draw(context, cr, width, height)
+ if not should_display_on(context.screen) then
+ return
+ end
+
+ local x, y, _, _ = wbase.rect_to_device_geometry(cr, 0, 0, width, height)
+ local num_entries = capi.awesome.systray()
+ local bg = beautiful.bg_systray or beautiful.bg_normal or "#000000"
+ local spacing = beautiful.systray_icon_spacing or 0
+
+ if context and not context.wibox then
+ error("The systray widget can only be placed inside a wibox.")
+ end
+
+ -- Figure out if the cairo context is rotated
+ local dir_x, dir_y = cr:user_to_device_distance(1, 0)
+ local is_rotated = abs(dir_x) < abs(dir_y)
+
+ local in_dir, ortho, base
+ if horizontal then
+ in_dir, ortho = width, height
+ is_rotated = not is_rotated
+ else
+ ortho, in_dir = width, height
+ end
+ if ortho * num_entries <= in_dir then
+ base = ortho
+ else
+ base = in_dir / num_entries
+ end
+ capi.awesome.systray(context.wibox.drawin, math.ceil(x), math.ceil(y),
+ base, is_rotated, bg, reverse, spacing)
+end
+
+function systray:fit(context, width, height)
+ if not should_display_on(context.screen) then
+ return 0, 0
+ end
+
+ local num_entries = capi.awesome.systray()
+ local base = base_size
+ local spacing = beautiful.systray_icon_spacing or 0
+ if num_entries == 0 then
+ return 0, 0
+ end
+ if base == nil then
+ if width < height then
+ base = width
+ else
+ base = height
+ end
+ end
+ base = base + spacing
+ if horizontal then
+ return base * num_entries - spacing, base
+ end
+ return base, base * num_entries - spacing
+end
+
+-- Check if the function was called like :foo() or .foo() and do the right thing
+local function get_args(self, ...)
+ if self == instance then
+ return ...
+ end
+ return self, ...
+end
+
+--- Set the size of a single icon.
+-- If this is set to nil, then the size is picked dynamically based on the
+-- available space. Otherwise, any single icon has a size of `size`x`size`.
+-- @tparam integer|nil size The base size
+function systray:set_base_size(size)
+ base_size = get_args(self, size)
+ if instance then
+ instance:emit_signal("widget::layout_changed")
+ end
+end
+
+--- Decide between horizontal or vertical display.
+-- @tparam boolean horiz Use horizontal mode?
+function systray:set_horizontal(horiz)
+ horizontal = get_args(self, horiz)
+ if instance then
+ instance:emit_signal("widget::layout_changed")
+ end
+end
+
+--- Should the systray icons be displayed in reverse order?
+-- @tparam boolean rev Display in reverse order
+function systray:set_reverse(rev)
+ reverse = get_args(self, rev)
+ if instance then
+ instance:emit_signal("widget::redraw_needed")
+ end
+end
+
+--- Set the screen that the systray should be displayed on.
+-- This can either be a screen, in which case the systray will be displayed on
+-- exactly that screen, or the string `"primary"`, in which case it will be
+-- visible on the primary screen. The default value is "primary".
+-- @tparam screen|"primary" s The screen to display on.
+function systray:set_screen(s)
+ display_on_screen = get_args(self, s)
+ if instance then
+ instance:emit_signal("widget::layout_changed")
+ end
+end
+
+--- Create the systray widget.
+-- Note that this widget can only exist once.
+-- @tparam boolean revers Show in the opposite direction
+-- @treturn table The new `systray` widget
+-- @function wibox.widget.systray
+
+local function new(revers)
+ local ret = wbase.make_widget()
+
+ util.table.crush(ret, systray, true)
+
+ if revers then
+ ret:set_reverse(true)
+ end
+
+ capi.awesome.connect_signal("systray::update", function()
+ ret:emit_signal("widget::layout_changed")
+ ret:emit_signal("widget::redraw_needed")
+ end)
+ capi.screen.connect_signal("primary_changed", function()
+ if display_on_screen == "primary" then
+ ret:emit_signal("widget::layout_changed")
+ end
+ end)
+
+ return ret
+end
+
+function systray.mt:__call(...)
+ if not instance then
+ instance = new(...)
+ end
+ return instance
+end
+
+return setmetatable(systray, systray.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/wibox/widget/textbox.lua b/lib/wibox/widget/textbox.lua
new file mode 100644
index 0000000..add35e9
--- /dev/null
+++ b/lib/wibox/widget/textbox.lua
@@ -0,0 +1,507 @@
+---------------------------------------------------------------------------
+--
+--
+--
+--![Usage example](../images/AUTOGEN_wibox_widget_defaults_textbox.svg)
+--
+-- @usage
+--wibox.widget{
+-- markup = 'This <i>is</i> a <b>textbox</b>!!!',
+-- align = 'center',
+-- valign = 'center',
+-- widget = wibox.widget.textbox
+--}
+-- @author Uli Schlachter
+-- @author dodo
+-- @copyright 2010, 2011 Uli Schlachter, dodo
+-- @classmod wibox.widget.textbox
+---------------------------------------------------------------------------
+
+local base = require("wibox.widget.base")
+local gdebug = require("gears.debug")
+local beautiful = require("beautiful")
+local lgi = require("lgi")
+local util = require("awful.util")
+local Pango = lgi.Pango
+local PangoCairo = lgi.PangoCairo
+local setmetatable = setmetatable
+
+local textbox = { mt = {} }
+
+--- The textbox font.
+-- @beautiful beautiful.font
+
+--- Set the DPI of a Pango layout
+local function setup_dpi(box, dpi)
+ if box._private.dpi ~= dpi then
+ box._private.dpi = dpi
+ box._private.ctx:set_resolution(dpi)
+ box._private.layout:context_changed()
+ end
+end
+
+--- Setup a pango layout for the given textbox and dpi
+local function setup_layout(box, width, height, dpi)
+ box._private.layout.width = Pango.units_from_double(width)
+ box._private.layout.height = Pango.units_from_double(height)
+ setup_dpi(box, dpi)
+end
+
+-- Draw the given textbox on the given cairo context in the given geometry
+function textbox:draw(context, cr, width, height)
+ setup_layout(self, width, height, context.dpi)
+ cr:update_layout(self._private.layout)
+ local _, logical = self._private.layout:get_pixel_extents()
+ local offset = 0
+ if self._private.valign == "center" then
+ offset = (height - logical.height) / 2
+ elseif self._private.valign == "bottom" then
+ offset = height - logical.height
+ end
+ cr:move_to(0, offset)
+ cr:show_layout(self._private.layout)
+end
+
+local function do_fit_return(self)
+ local _, logical = self._private.layout:get_pixel_extents()
+ if logical.width == 0 or logical.height == 0 then
+ return 0, 0
+ end
+ return logical.width, logical.height
+end
+
+-- Fit the given textbox
+function textbox:fit(context, width, height)
+ setup_layout(self, width, height, context.dpi)
+ return do_fit_return(self)
+end
+
+--- Get the preferred size of a textbox.
+-- This returns the size that the textbox would use if infinite space were
+-- available.
+-- @tparam integer|screen s The screen on which the textbox will be displayed.
+-- @treturn number The preferred width.
+-- @treturn number The preferred height.
+function textbox:get_preferred_size(s)
+ return self:get_preferred_size_at_dpi(beautiful.xresources.get_dpi(s))
+end
+
+--- Get the preferred height of a textbox at a given width.
+-- This returns the height that the textbox would use when it is limited to the
+-- given width.
+-- @tparam number width The available width.
+-- @tparam integer|screen s The screen on which the textbox will be displayed.
+-- @treturn number The needed height.
+function textbox:get_height_for_width(width, s)
+ return self:get_height_for_width_at_dpi(width, beautiful.xresources.get_dpi(s))
+end
+
+--- Get the preferred size of a textbox.
+-- This returns the size that the textbox would use if infinite space were
+-- available.
+-- @tparam number dpi The DPI value to render at.
+-- @treturn number The preferred width.
+-- @treturn number The preferred height.
+function textbox:get_preferred_size_at_dpi(dpi)
+ local max_lines = 2^20
+ setup_dpi(self, dpi)
+ self._private.layout.width = -1 -- no width set
+ self._private.layout.height = -max_lines -- show this many lines per paragraph
+ return do_fit_return(self)
+end
+
+--- Get the preferred height of a textbox at a given width.
+-- This returns the height that the textbox would use when it is limited to the
+-- given width.
+-- @tparam number width The available width.
+-- @tparam number dpi The DPI value to render at.
+-- @treturn number The needed height.
+function textbox:get_height_for_width_at_dpi(width, dpi)
+ local max_lines = 2^20
+ setup_dpi(self, dpi)
+ self._private.layout.width = Pango.units_from_double(width)
+ self._private.layout.height = -max_lines -- show this many lines per paragraph
+ local _, h = do_fit_return(self)
+ return h
+end
+
+--- Set the text of the textbox (with
+-- [Pango markup](https://developer.gnome.org/pango/stable/PangoMarkupFormat.html)).
+-- @tparam string text The text to set. This can contain pango markup (e.g.
+-- `<b>bold</b>`). You can use `awful.util.escape` to escape
+-- parts of it.
+-- @treturn[1] boolean true
+-- @treturn[2] boolean false
+-- @treturn[2] string Error message explaining why the markup was invalid.
+function textbox:set_markup_silently(text)
+ if self._private.markup == text then
+ return true
+ end
+
+ local attr, parsed = Pango.parse_markup(text, -1, 0)
+ -- In case of error, attr is false and parsed is a GLib.Error instance.
+ if not attr then
+ return false, parsed.message or tostring(parsed)
+ end
+
+ self._private.markup = text
+ self._private.layout.text = parsed
+ self._private.layout.attributes = attr
+ self:emit_signal("widget::redraw_needed")
+ self:emit_signal("widget::layout_changed")
+ return true
+end
+
+--- Set the text of the textbox (with
+-- [Pango markup](https://developer.gnome.org/pango/stable/PangoMarkupFormat.html)).
+-- @property markup
+-- @tparam string text The text to set. This can contain pango markup (e.g.
+-- `<b>bold</b>`). You can use `awful.util.escape` to escape
+-- parts of it.
+-- @see text
+
+function textbox:set_markup(text)
+ local success, message = self:set_markup_silently(text)
+ if not success then
+ gdebug.print_error(message)
+ end
+end
+
+function textbox:get_markup()
+ return self._private.markup
+end
+
+--- Set a textbox' text.
+-- @property text
+-- @param text The text to display. Pango markup is ignored and shown as-is.
+-- @see markup
+
+function textbox:set_text(text)
+ if self._private.layout.text == text and self._private.layout.attributes == nil then
+ return
+ end
+ self._private.markup = nil
+ self._private.layout.text = text
+ self._private.layout.attributes = nil
+ self:emit_signal("widget::redraw_needed")
+ self:emit_signal("widget::layout_changed")
+end
+
+function textbox:get_text()
+ return self._private.layout.text
+end
+
+--- Set a textbox' ellipsize mode.
+-- @property ellipsize
+-- @param mode Where should long lines be shortened? "start", "middle" or "end"
+
+function textbox:set_ellipsize(mode)
+ local allowed = { none = "NONE", start = "START", middle = "MIDDLE", ["end"] = "END" }
+ if allowed[mode] then
+ if self._private.layout:get_ellipsize() == allowed[mode] then
+ return
+ end
+ self._private.layout:set_ellipsize(allowed[mode])
+ self:emit_signal("widget::redraw_needed")
+ self:emit_signal("widget::layout_changed")
+ end
+end
+
+--- Set a textbox' wrap mode.
+-- @property wrap
+-- @param mode Where to wrap? After "word", "char" or "word_char"
+
+function textbox:set_wrap(mode)
+ local allowed = { word = "WORD", char = "CHAR", word_char = "WORD_CHAR" }
+ if allowed[mode] then
+ if self._private.layout:get_wrap() == allowed[mode] then
+ return
+ end
+ self._private.layout:set_wrap(allowed[mode])
+ self:emit_signal("widget::redraw_needed")
+ self:emit_signal("widget::layout_changed")
+ end
+end
+
+--- The textbox' vertical alignment
+-- @property valign
+-- @param mode Where should the textbox be drawn? "top", "center" or "bottom"
+
+function textbox:set_valign(mode)
+ local allowed = { top = true, center = true, bottom = true }
+ if allowed[mode] then
+ if self._private.valign == mode then
+ return
+ end
+ self._private.valign = mode
+ self:emit_signal("widget::redraw_needed")
+ self:emit_signal("widget::layout_changed")
+ end
+end
+
+--- Set a textbox' horizontal alignment.
+-- @property align
+-- @param mode Where should the textbox be drawn? "left", "center" or "right"
+
+function textbox:set_align(mode)
+ local allowed = { left = "LEFT", center = "CENTER", right = "RIGHT" }
+ if allowed[mode] then
+ if self._private.layout:get_alignment() == allowed[mode] then
+ return
+ end
+ self._private.layout:set_alignment(allowed[mode])
+ self:emit_signal("widget::redraw_needed")
+ self:emit_signal("widget::layout_changed")
+ end
+end
+
+--- Set a textbox' font
+-- @property font
+-- @param font The font description as string
+
+function textbox:set_font(font)
+ self._private.layout:set_font_description(beautiful.get_font(font))
+ self:emit_signal("widget::redraw_needed")
+ self:emit_signal("widget::layout_changed")
+end
+
+--- Create a new textbox.
+-- @tparam[opt=""] string text The textbox content
+-- @tparam[opt=false] boolean ignore_markup Ignore the pango/HTML markup
+-- @treturn table A new textbox widget
+-- @function wibox.widget.textbox
+local function new(text, ignore_markup)
+ local ret = base.make_widget(nil, nil, {enable_properties = true})
+
+ util.table.crush(ret, textbox, true)
+
+ ret._private.dpi = -1
+ ret._private.ctx = PangoCairo.font_map_get_default():create_context()
+ ret._private.layout = Pango.Layout.new(ret._private.ctx)
+
+ ret:set_ellipsize("end")
+ ret:set_wrap("word_char")
+ ret:set_valign("center")
+ ret:set_align("left")
+ ret:set_font(beautiful and beautiful.font)
+
+ if text then
+ if ignore_markup then
+ ret:set_text(text)
+ else
+ ret:set_markup(text)
+ end
+ end
+
+ return ret
+end
+
+function textbox.mt.__call(_, ...)
+ return new(...)
+end
+
+--Imported documentation
+
+
+--- Get a widex index.
+-- @param widget The widget to look for
+-- @param[opt] recursive Also check sub-widgets
+-- @param[opt] ... Aditional widgets to add at the end of the \"path\"
+-- @return The index
+-- @return The parent layout
+-- @return The path between \"self\" and \"widget\"
+-- @function index
+
+--- Get all direct and indirect children widgets.
+-- This will scan all containers recursively to find widgets
+-- Warning: This method it prone to stack overflow id the widget, or any of its
+-- children, contain (directly or indirectly) itself.
+-- @treturn table The children
+-- @function get_all_children
+
+--- Set a declarative widget hierarchy description.
+-- See [The declarative layout system](../documentation/03-declarative-layout.md.html)
+-- @param args An array containing the widgets disposition
+-- @function setup
+
+--- Force a widget height.
+-- @property forced_height
+-- @tparam number|nil height The height (`nil` for automatic)
+
+--- Force a widget width.
+-- @property forced_width
+-- @tparam number|nil width The width (`nil` for automatic)
+
+--- The widget opacity (transparency).
+-- @property opacity
+-- @tparam[opt=1] number opacity The opacity (between 0 and 1)
+
+--- The widget visibility.
+-- @property visible
+-- @param boolean
+
+--- Set/get a widget's buttons.
+-- @param _buttons The table of buttons that should bind to the widget.
+-- @function buttons
+
+--- Emit a signal and ensure all parent widgets in the hierarchies also
+-- forward the signal. This is useful to track signals when there is a dynamic
+-- set of containers and layouts wrapping the widget.
+-- @tparam string signal_name
+-- @param ... Other arguments
+-- @function emit_signal_recursive
+
+--- When the layout (size) change.
+-- This signal is emitted when the previous results of `:layout()` and `:fit()`
+-- are no longer valid. Unless this signal is emitted, `:layout()` and `:fit()`
+-- must return the same result when called with the same arguments.
+-- @signal widget::layout_changed
+-- @see widget::redraw_needed
+
+--- When the widget content changed.
+-- This signal is emitted when the content of the widget changes. The widget will
+-- be redrawn, it is not re-layouted. Put differently, it is assumed that
+-- `:layout()` and `:fit()` would still return the same results as before.
+-- @signal widget::redraw_needed
+-- @see widget::layout_changed
+
+--- When a mouse button is pressed over the widget.
+-- @signal button::press
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When a mouse button is released over the widget.
+-- @signal button::release
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse enter a widget.
+-- @signal mouse::enter
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse leave a widget.
+-- @signal mouse::leave
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+
+--Imported documentation
+
+
+--- Disconnect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback that should be disconnected
+-- @function disconnect_signal
+
+--- Emit a signal.
+--
+-- @tparam string name The name of the signal
+-- @param ... Extra arguments for the callback functions. Each connected
+-- function receives the object as first argument and then any extra arguments
+-- that are given to emit_signal()
+-- @function emit_signal
+
+--- Connect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function connect_signal
+
+--- Connect to a signal weakly. This allows the callback function to be garbage
+-- collected and automatically disconnects the signal when that happens.
+--
+-- **Warning:**
+-- Only use this function if you really, really, really know what you
+-- are doing.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function weak_connect_signal
+
+
+return setmetatable(textbox, textbox.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/wibox/widget/textclock.lua b/lib/wibox/widget/textclock.lua
new file mode 100644
index 0000000..c688028
--- /dev/null
+++ b/lib/wibox/widget/textclock.lua
@@ -0,0 +1,254 @@
+---------------------------------------------------------------------------
+--- Text clock widget.
+--
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2009 Julien Danjou
+-- @classmod wibox.widget.textclock
+---------------------------------------------------------------------------
+
+local setmetatable = setmetatable
+local os = os
+local textbox = require("wibox.widget.textbox")
+local timer = require("gears.timer")
+local DateTime = require("lgi").GLib.DateTime
+
+local textclock = { mt = {} }
+
+--- This lowers the timeout so that it occurs "correctly". For example, a timeout
+-- of 60 is rounded so that it occurs the next time the clock reads ":00 seconds".
+local function calc_timeout(real_timeout)
+ return real_timeout - os.time() % real_timeout
+end
+
+--- Create a textclock widget. It draws the time it is in a textbox.
+--
+-- @tparam[opt=" %a %b %d, %H:%M "] string format The time format.
+-- @tparam[opt=60] number timeout How often update the time (in seconds).
+-- @treturn table A textbox widget.
+-- @function wibox.widget.textclock
+function textclock.new(format, timeout)
+ format = format or " %a %b %d, %H:%M "
+ timeout = timeout or 60
+
+ local w = textbox()
+ local t
+ function w._private.textclock_update_cb()
+ w:set_markup(DateTime.new_now_local():format(format))
+ t.timeout = calc_timeout(timeout)
+ t:again()
+ return true -- Continue the timer
+ end
+ t = timer.weak_start_new(timeout, w._private.textclock_update_cb)
+ t:emit_signal("timeout")
+ return w
+end
+
+function textclock.mt:__call(...)
+ return textclock.new(...)
+end
+
+--Imported documentation
+
+
+--- Get a widex index.
+-- @param widget The widget to look for
+-- @param[opt] recursive Also check sub-widgets
+-- @param[opt] ... Aditional widgets to add at the end of the \"path\"
+-- @return The index
+-- @return The parent layout
+-- @return The path between \"self\" and \"widget\"
+-- @function index
+
+--- Get all direct and indirect children widgets.
+-- This will scan all containers recursively to find widgets
+-- Warning: This method it prone to stack overflow id the widget, or any of its
+-- children, contain (directly or indirectly) itself.
+-- @treturn table The children
+-- @function get_all_children
+
+--- Set a declarative widget hierarchy description.
+-- See [The declarative layout system](../documentation/03-declarative-layout.md.html)
+-- @param args An array containing the widgets disposition
+-- @function setup
+
+--- Force a widget height.
+-- @property forced_height
+-- @tparam number|nil height The height (`nil` for automatic)
+
+--- Force a widget width.
+-- @property forced_width
+-- @tparam number|nil width The width (`nil` for automatic)
+
+--- The widget opacity (transparency).
+-- @property opacity
+-- @tparam[opt=1] number opacity The opacity (between 0 and 1)
+
+--- The widget visibility.
+-- @property visible
+-- @param boolean
+
+--- Set/get a widget's buttons.
+-- @param _buttons The table of buttons that should bind to the widget.
+-- @function buttons
+
+--- Emit a signal and ensure all parent widgets in the hierarchies also
+-- forward the signal. This is useful to track signals when there is a dynamic
+-- set of containers and layouts wrapping the widget.
+-- @tparam string signal_name
+-- @param ... Other arguments
+-- @function emit_signal_recursive
+
+--- When the layout (size) change.
+-- This signal is emitted when the previous results of `:layout()` and `:fit()`
+-- are no longer valid. Unless this signal is emitted, `:layout()` and `:fit()`
+-- must return the same result when called with the same arguments.
+-- @signal widget::layout_changed
+-- @see widget::redraw_needed
+
+--- When the widget content changed.
+-- This signal is emitted when the content of the widget changes. The widget will
+-- be redrawn, it is not re-layouted. Put differently, it is assumed that
+-- `:layout()` and `:fit()` would still return the same results as before.
+-- @signal widget::redraw_needed
+-- @see widget::layout_changed
+
+--- When a mouse button is pressed over the widget.
+-- @signal button::press
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When a mouse button is released over the widget.
+-- @signal button::release
+-- @tparam number lx The horizontal position relative to the (0,0) position in
+-- the widget.
+-- @tparam number ly The vertical position relative to the (0,0) position in the
+-- widget.
+-- @tparam number button The button number.
+-- @tparam table mods The modifiers (mod4, mod1 (alt), Control, Shift)
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse enter a widget.
+-- @signal mouse::enter
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+--- When the mouse leave a widget.
+-- @signal mouse::leave
+-- @tparam table find_widgets_result The entry from the result of
+-- @{wibox.drawable:find_widgets} for the position that the mouse hit.
+-- @tparam wibox.drawable find_widgets_result.drawable The drawable containing
+-- the widget.
+-- @tparam widget find_widgets_result.widget The widget being displayed.
+-- @tparam wibox.hierarchy find_widgets_result.hierarchy The hierarchy
+-- managing the widget's geometry.
+-- @tparam number find_widgets_result.x An approximation of the X position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.y An approximation of the Y position that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.width An approximation of the width that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.height An approximation of the height that
+-- the widget is visible at on the surface.
+-- @tparam number find_widgets_result.widget_width The exact width of the widget
+-- in its local coordinate system.
+-- @tparam number find_widgets_result.widget_height The exact height of the widget
+-- in its local coordinate system.
+-- @see mouse
+
+
+--Imported documentation
+
+
+--- Disconnect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback that should be disconnected
+-- @function disconnect_signal
+
+--- Emit a signal.
+--
+-- @tparam string name The name of the signal
+-- @param ... Extra arguments for the callback functions. Each connected
+-- function receives the object as first argument and then any extra arguments
+-- that are given to emit_signal()
+-- @function emit_signal
+
+--- Connect to a signal.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function connect_signal
+
+--- Connect to a signal weakly. This allows the callback function to be garbage
+-- collected and automatically disconnects the signal when that happens.
+--
+-- **Warning:**
+-- Only use this function if you really, really, really know what you
+-- are doing.
+-- @tparam string name The name of the signal
+-- @tparam function func The callback to call when the signal is emitted
+-- @function weak_connect_signal
+
+
+return setmetatable(textclock, textclock.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80