diff --git a/docs/aliases/awful_mouse.lua b/docs/aliases/awful_mouse.lua
new file mode 100644
index 00000000..9b339a6f
--- /dev/null
+++ b/docs/aliases/awful_mouse.lua
@@ -0,0 +1,6 @@
+---------------------------------------------------------------------------
+--- This module is deprecated, use `mouse`
+-- ===============================
+--
+-- @module awful.mouse
+---------------------------------------------------------------------------
diff --git a/docs/config.ld b/docs/config.ld
index b220b720..19886170 100644
--- a/docs/config.ld
+++ b/docs/config.ld
@@ -35,6 +35,8 @@ new_type("function", "Functions")
new_type("property", "Object properties", false, "Type")
-- New type for signals
new_type("signal", "Signals", false, "Arguments")
+-- New type for signals connections
+new_type("signalhandler", "Request handlers", false, "Arguments")
-- Allow objects to define a set of beautiful properties affecting them
new_type("beautiful", "Theme variables", false, "Type")
-- Put deprecated methods in their own section
@@ -67,6 +69,7 @@ file = {
'../docs/aliases/awful_client.lua',
'../docs/aliases/awful_screen.lua',
'../docs/aliases/awful_tag.lua',
+ '../docs/aliases/awful_mouse.lua',
exclude = {
-- exclude these modules, as they do not contain any written
-- documentation
diff --git a/docs/images/mouse.svg b/docs/images/mouse.svg
new file mode 100644
index 00000000..e56eff51
--- /dev/null
+++ b/docs/images/mouse.svg
@@ -0,0 +1,253 @@
+
+
+
+
diff --git a/lib/awful/ewmh.lua b/lib/awful/ewmh.lua
index ffe04fd7..d110cd31 100644
--- a/lib/awful/ewmh.lua
+++ b/lib/awful/ewmh.lua
@@ -15,6 +15,7 @@ local math = math
local util = require("awful.util")
local aclient = require("awful.client")
local aplace = require("awful.placement")
+local asuit = require("awful.layout.suit")
local ewmh = {}
@@ -100,6 +101,7 @@ end
--
-- 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:
@@ -120,8 +122,11 @@ function ewmh.activate(c, context, hints) -- luacheck: no unused args
end
end
---- Tag a window with its requested tag
+--- 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
-- @tag[opt] t A tag to use. If omitted, then the client is made sticky.
-- @tparam[opt={}] table hints Extra information
@@ -156,10 +161,18 @@ local context_mapper = {
--
-- 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
diff --git a/lib/awful/layout/init.lua b/lib/awful/layout/init.lua
index 482bab63..df99b1b0 100644
--- a/lib/awful/layout/init.lua
+++ b/lib/awful/layout/init.lua
@@ -246,6 +246,28 @@ capi.client.connect_signal("list", function()
end
end)
+--- Default handler for tiled clients request::geometry 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
+ 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)
+
return layout
-- 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 00000000..ee4cb895
--- /dev/null
+++ b/lib/awful/mouse/drag_to_tag.lua
@@ -0,0 +1,59 @@
+---------------------------------------------------------------------------
+--- When the the mouse reach the end of the screen, then switch tag instead
+-- of screens.
+--
+-- @author Julien Danjou <julien@danjou.info>
+-- @copyright 2008 Julien Danjou
+-- @release @AWESOME_VERSION@
+-- @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 = capi.screen[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
index 7a8a86a7..13cd23ae 100644
--- a/lib/awful/mouse/init.lua
+++ b/lib/awful/mouse/init.lua
@@ -4,17 +4,15 @@
-- @author Julien Danjou <julien@danjou.info>
-- @copyright 2008 Julien Danjou
-- @release @AWESOME_VERSION@
--- @module awful.mouse
+-- @module mouse
---------------------------------------------------------------------------
-- Grab environment we need
local layout = require("awful.layout")
-local tag = require("awful.tag")
-local aclient = require("awful.client")
+local aplace = require("awful.placement")
local awibox = require("awful.wibox")
local util = require("awful.util")
local type = type
-local math = math
local ipairs = ipairs
local capi =
{
@@ -25,136 +23,63 @@ local capi =
mousegrabber = mousegrabber,
}
-local mouse = {}
+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()
- local obj = capi.mouse.object_under_pointer()
- if type(obj) == "client" then
- return obj
- end
-end
+ util.deprecated("Use mouse.current_client instead of awful.mouse.client_under_pointer()")
---- Get the drawin object under the pointer.
--- @return The drawin object under the pointer, if one can be found.
-function mouse.drawin_under_pointer()
- local obj = capi.mouse.object_under_pointer()
- if type(obj) == "drawin" then
- return obj
- end
-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.
--- @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 mouse.client.snap(c, snap, x, y, fixed_x, fixed_y)
- snap = snap or 8
- 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, capi.screen[c.screen].geometry, snap)
- geom = snap_inside(geom, capi.screen[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
-
- geom.x = geom.x - (2 * c.border_width)
- geom.y = geom.y - (2 * c.border_width)
-
- for _, snapper in ipairs(aclient.visible(c.screen)) do
- if snapper ~= c then
- geom = snap_outside(geom, snapper:geometry(), snap)
- end
- end
-
- geom.width = geom.width - (2 * c.border_width)
- geom.height = geom.height - (2 * c.border_width)
- geom.x = geom.x + (2 * c.border_width)
- geom.y = geom.y + (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
+ 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 An optional callback function, that will be called
--- when moving the client has been finished. The client
--- that has been moved will be passed to that function.
-function mouse.client.move(c, snap, finished_cb)
+-- @param finished_cb Deprecated, do not use
+function mouse.client.move(c, snap, finished_cb) --luacheck: no unused args
+ if finished_cb then
+ util.deprecated("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
@@ -165,98 +90,39 @@ function mouse.client.move(c, snap, finished_cb)
return
end
- local orig = c:geometry()
- local m_c = capi.mouse.coords()
- local dist_x = m_c.x - orig.x
- local dist_y = m_c.y - orig.y
- -- Only allow moving in the non-maximized directions
- local fixed_x = c.maximized_horizontal
- local fixed_y = c.maximized_vertical
+ -- Compute the offset
+ local coords = capi.mouse.coords()
+ local geo = aplace.centered(capi.mouse,{parent=c, pretend=true})
- capi.mousegrabber.run(function (_mouse)
- if not c.valid then return false end
+ local offset = {
+ x = geo.x - coords.x,
+ y = geo.y - coords.y,
+ }
- for _, v in ipairs(_mouse.buttons) do
- if v then
- local lay = layout.get(c.screen)
- if lay == layout.suit.floating or c.floating then
- local x = _mouse.x - dist_x
- local y = _mouse.y - dist_y
- c:geometry(mouse.client.snap(c, snap, x, y, fixed_x, fixed_y))
- elseif lay ~= layout.suit.magnifier then
- -- Only move the client to the mouse
- -- screen if the target screen is not
- -- floating.
- -- Otherwise, we move if via geometry.
- if layout.get(capi.mouse.screen) == layout.suit.floating then
- local x = _mouse.x - dist_x
- local y = _mouse.y - dist_y
- c:geometry(mouse.client.snap(c, snap, x, y, fixed_x, fixed_y))
- else
- c.screen = capi.mouse.screen
- end
- if layout.get(c.screen) ~= layout.suit.floating then
- local c_u_m = mouse.client_under_pointer()
- 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
- end
- return true
- end
- end
- if finished_cb then
- finished_cb(c)
- end
- return false
- end, "fleur")
+ 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
+--- 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)
- capi.mousegrabber.run(function (_mouse)
- if not c.valid then return false end
+ util.deprecated("Use awful.mouse.snap.drag_to_tag_enabled = true instead "..
+ "of awful.mouse.client.dragtotag.border(c). It will now be enabled.")
- local button_down = false
- for _, v in ipairs(_mouse.buttons) do
- if v then button_down = true end
- end
- local wa = capi.screen[c.screen].workarea
- if _mouse.x >= wa.x + wa.width then
- capi.mouse.coords({ x = wa.x + wa.width - 1 })
- elseif _mouse.x <= wa.x then
- capi.mouse.coords({ x = wa.x + 1 })
- end
- if not button_down then
- local tags = c.screen.tags
- local t = c.screen.selected_tag
- local idx
- for i, v in ipairs(tags) do
- if v == t then
- idx = i
- end
- end
- if _mouse.x > wa.x + wa.width - 10 then
- local newtag = tags[util.cycle(#tags, idx + 1)]
- c:move_to_tag(newtag)
- tag.viewnext()
- elseif _mouse.x < wa.x + 10 then
- local newtag = tags[util.cycle(#tags, idx - 1)]
- c:move_to_tag(newtag)
- tag.viewprev()
- end
- return false
- end
- return true
- end, "fleur")
+ -- Enable drag to border
+ mouse.snap.drag_to_tag_enabled = true
+
+ return mouse.client.move(c)
end
---- Move the wibox under the cursor
+--- Move the wibox under the cursor.
+-- @function awful.mouse.wibox.move
--@param 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()
@@ -297,55 +163,41 @@ function mouse.wibox.move(w)
end
--- Get a client corner coordinates.
--- @param c The client to get corner from, focused one by default.
--- @param corner The corner to use: auto, top_left, top_right, bottom_left,
--- bottom_right. Default is auto, and auto find the nearest corner.
--- @return Actual used corner and x and y 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.deprecated(
+ "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 g = c:geometry()
+ local ngeo = nil
- if not corner or corner == "auto" then
- local m_c = capi.mouse.coords()
- if math.abs(g.y - m_c.y) < math.abs(g.y + g.height - m_c.y) then
- if math.abs(g.x - m_c.x) < math.abs(g.x + g.width - m_c.x) then
- corner = "top_left"
- else
- corner = "top_right"
- end
- else
- if math.abs(g.x - m_c.x) < math.abs(g.x + g.width - m_c.x) then
- corner = "bottom_left"
- else
- corner = "bottom_right"
- end
- end
+ 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
- local x, y
- if corner == "top_right" then
- x = g.x + g.width
- y = g.y
- elseif corner == "top_left" then
- x = g.x
- y = g.y
- elseif corner == "bottom_left" then
- x = g.x
- y = g.y + g.height
- else
- x = g.x + g.width
- y = g.y + g.height
- end
-
- return corner, x, y
+ 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.
--- @param corner The corner to grab on resize. Auto detected by default.
-function mouse.client.resize(c, corner)
+-- @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
@@ -357,19 +209,162 @@ function mouse.client.resize(c, corner)
return
end
- local lay = layout.get(c.screen)
- local corner2, x, y = mouse.client.corner(c, corner)
+ -- 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})
+ end
- if lay == layout.suit.floating or c.floating then
- return layout.suit.floating.mouse_resize_handler(c, corner2, x, y)
- elseif lay.mouse_resize_handler then
- return lay.mouse_resize_handler(c, corner2, x, y)
+ mouse.resize(c, "mouse.resize", args or {include_sides=true})
+
+ 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 lay = c.screen.selected_tag.layout
+
+ if lay == layout.suit.floating or c.floating then
+ local offset = hints and hints.offset or {}
+
+ if type(offset) == "number" then
+ offset = {
+ x = offset,
+ y = offset,
+ width = offset,
+ height = offset,
+ }
+ end
+
+ c:geometry {
+ x = hints.x + (offset.x or 0 ),
+ y = hints.y + (offset.y or 0 ),
+ width = hints.width + (offset.width or 0 ),
+ height = hints.height + (offset.height or 0 ),
+ }
+ elseif 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
+
+function mouse.object.set_current_client() 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" then
+ return obj
+ end
+end
+
+function mouse.object.set_current_wibox() 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
+
+ mouse.object["set_is_".. b .."_mouse_button_pressed"] = function() 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)
+ else
+ props[key] = value
+ 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.
+--
+--@DOC_awful_mouse_coords_EXAMPLE@
+--
+-- @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 00000000..ed95fbbd
--- /dev/null
+++ b/lib/awful/mouse/resize.lua
@@ -0,0 +1,199 @@
+---------------------------------------------------------------------------
+--- An extandable mouse resizing handler.
+--
+-- This module offer a resizing and moving mechanism for drawable such as
+-- clients and wiboxes.
+--
+-- @author Emmanuel Lepage Vallee <elv1313@gmail.com>
+-- @copyright 2016 Emmanuel Lepage Vallee
+-- @release @AWESOME_VERSION@
+-- @submodule mouse
+---------------------------------------------------------------------------
+
+local aplace = require("awful.placement")
+local capi = {mousegrabber = mousegrabber}
+
+local module = {}
+
+local mode = "live"
+local req = "request::geometry"
+local callbacks = {enter={}, move={}, leave={}}
+
+--- 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 a initialization callback.
+-- This callback will be executed before the mouse grabbing start
+-- @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
+
+ -- 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, "cross")
+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 00000000..684217cd
--- /dev/null
+++ b/lib/awful/mouse/snap.lua
@@ -0,0 +1,269 @@
+---------------------------------------------------------------------------
+--- Mouse snapping related functions
+--
+-- @author Julien Danjou <julien@danjou.info>
+-- @copyright 2008 Julien Danjou
+-- @release @AWESOME_VERSION@
+-- @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, capi.screen[c.screen].geometry, snap)
+ geom = snap_inside(geom, capi.screen[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
+
+ geom.x = geom.x - (2 * c.border_width)
+ geom.y = geom.y - (2 * c.border_width)
+
+ for _, snapper in ipairs(aclient.visible(c.screen)) do
+ if snapper ~= c then
+ geom = snap_outside(geom, snapper:geometry(), snap)
+ end
+ end
+
+ geom.width = geom.width - (2 * c.border_width)
+ geom.height = geom.height - (2 * c.border_width)
+ geom.x = geom.x + (2 * c.border_width)
+ geom.y = geom.y + (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
index b98b2a67..1f2dad32 100644
--- a/lib/awful/placement.lua
+++ b/lib/awful/placement.lua
@@ -9,14 +9,21 @@
-- * 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
--
---
+--
+--
Compositing
--
-- It is possible to compose placement function using the `+` or `*` operator:
--
--- local f = (awful.placement.right + awful.placement.left)
--- f(client.focus)
+--@DOC_awful_placement_compose_EXAMPLE@
--
--- ### Common arguments
+--@DOC_awful_placement_compose2_EXAMPLE@
+--
+--
Common arguments
+--
+-- **pretend** (*boolean*):
+--
+-- Do not apply the new geometry. This is useful if only the return values is
+-- necessary.
--
-- **honor_workarea** (*boolean*):
--
@@ -44,6 +51,10 @@
--
-- **attach** (*boolean*):
--
+-- **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
@@ -77,6 +88,7 @@ local capi =
local client = require("awful.client")
local layout = require("awful.layout")
local a_screen = require("awful.screen")
+local util = require("awful.util")
local dpi = require("beautiful").xresources.apply_dpi
local function get_screen(s)
@@ -85,20 +97,89 @@ end
local wrap_client = nil
-local function compose(w1, w2)
- return wrap_client(function(...)
- w1(...)
- w2(...)
- return --It make no sense to keep a return value
- end)
+--- 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 = 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 pretend_real = args.pretend
+
+ args.pretend = true
+
+ for k, f in ipairs(queue) do
+ if k == #queue then
+ args.pretend = pretend_real or false
+ 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
+
+ return last_geo, rets
+ end, "compose")
+
+ ret.queue = queue
+
+ return ret
end
-wrap_client = function(f)
- return setmetatable({is_placement=true}, {
- __call = function(_,...) return f(...) end,
- __add = compose, -- Composition is usually defined as +
- __mul = compose -- Make sense if you think of the functions as matrices
- })
+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 = {}
@@ -111,7 +192,7 @@ local placement_private = {}
local placement = setmetatable({}, {
__index = placement_private,
__newindex = function(_, k, f)
- placement_private[k] = wrap_client(f)
+ placement_private[k] = wrap_client(f, k)
end
})
@@ -143,6 +224,21 @@ local align_map = {
-- Store function -> keys
local reverse_align_map = {}
+-- 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" },
+}
+
--- 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
@@ -163,17 +259,49 @@ local function store_geometry(d, reqtype)
data[d][reqtype].screen = d.screen
end
+--- Apply some modifications before applying the new geometry.
+-- @tparam table new_geo The new geometry
+-- @tparam table args The common arguments
+-- @treturn table|nil The new geometry
+local function fix_new_geometry(new_geo, args)
+ if args.pretend or not new_geo then return nil end
+
+ local offset = args.offset or {}
+
+ if type(offset) == "number" then
+ offset = {
+ x = offset,
+ y = offset,
+ width = offset,
+ height = offset,
+ }
+ end
+
+ return {
+ x = new_geo.x and (new_geo.x + (offset.x or 0)),
+ y = new_geo.y and (new_geo.y + (offset.y or 0)),
+ width = new_geo.width and (new_geo.width + (offset.width or 0)),
+ height = new_geo.height and (new_geo.height + (offset.height 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.
-local function area_common(d, new_geo, ignore_border_width)
+local function area_common(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
- geometry.x = geometry.x
- geometry.y = geometry.y
+
+ -- 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
@@ -196,14 +324,17 @@ local function geometry_common(obj, args, new_geo, ignore_border_width)
-- It's a mouse
if obj.coords then
- local coords = new_geo and obj.coords(new_geo) or obj.coords()
+ 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, new_geo, ignore_border_width)
+ local dgeo = area_common(
+ obj, fix_new_geometry(new_geo, args), ignore_border_width, args
+ )
-- Apply the margins
if args.margins then
@@ -434,6 +565,24 @@ local function area_remove(areas, elem)
return areas
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
+
--- Move a drawable to the closest corner of the parent geometry (such as the
-- screen).
--
@@ -445,6 +594,7 @@ end
-- @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")
@@ -460,8 +610,10 @@ function placement.closest_corner(d, args)
-- Use the product of 3 to get the closest point in a NxN matrix
local function f(_n, mat)
n = _n
- corner_i = -math.ceil( ( (sgeo.x - pos.x) * n) / sgeo.width )
- corner_j = -math.ceil( ( (sgeo.y - pos.y) * n) / sgeo.height )
+ -- 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
@@ -476,9 +628,9 @@ function placement.closest_corner(d, args)
-- Transpose the corner back to the original size
local new_args = setmetatable({position = corner}, {__index=args})
- placement_private.align(d, new_args)
+ local ngeo = placement_private.align(d, new_args)
- return corner
+ return ngeo, corner
end
--- Place the client so no part of it will be outside the screen (workarea).
@@ -520,6 +672,7 @@ end
--- Place the client where there's place available with minimum overlap.
--@DOC_awful_placement_no_overlap_EXAMPLE@
-- @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)
@@ -578,14 +731,24 @@ end
--- Place the client under the mouse.
--@DOC_awful_placement_under_mouse_EXAMPLE@
--- @param c The client.
--- @return The new client geometry.
-function placement.under_mouse(c)
- c = c or capi.client.focus
- local c_geometry = area_common(c)
+-- @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()
- return c:geometry({ x = m_coords.x - c_geometry.width / 2,
- y = m_coords.y - c_geometry.height / 2 })
+
+ local ngeo = geometry_common(d, args)
+ ngeo.x = m_coords.x - ngeo.width / 2
+ ngeo.y = 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
+
+ return ngeo
end
--- Place the client next to the mouse.
@@ -595,7 +758,7 @@ end
--@DOC_awful_placement_next_to_mouse_EXAMPLE@
-- @client[opt=focused] c The client.
-- @tparam[opt=apply_dpi(5)] integer offset The offset from the mouse position.
--- @return The new client geometry.
+-- @treturn table The new geometry
function placement.next_to_mouse(c, offset)
c = c or capi.client.focus
offset = offset or dpi(5)
@@ -627,6 +790,78 @@ function placement.next_to_mouse(c, offset)
return c:geometry({ x = x, y = y })
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.
+--
+--@DOC_awful_placement_resize_to_mouse_EXAMPLE@
+-- @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 ngeo
+end
+
--- Move the drawable (client or wibox) `d` to a screen position or side.
--
-- Supported args.positions are:
@@ -646,6 +881,7 @@ end
--@DOC_awful_placement_align_EXAMPLE@
-- @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
@@ -663,14 +899,18 @@ function placement.align(d, args)
dgeo.height
)
- geometry_common(d, args, {
+ 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 ngeo
end
-- Add the alias functions
@@ -678,7 +918,7 @@ for k in pairs(align_map) do
placement[k] = function(d, args)
args = add_context(args, k)
args.position = k
- placement_private.align(d, args)
+ return placement_private.align(d, args)
end
reverse_align_map[placement[k]] = k
end
@@ -716,6 +956,7 @@ end
--@DOC_awful_placement_stretch_EXAMPLE@
-- @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")
@@ -757,6 +998,8 @@ function placement.stretch(d, args)
geometry_common(d, args, ngeo)
attach(d, placement["stretch_"..args.direction], args)
+
+ return ngeo
end
-- Add the alias functions
@@ -764,7 +1007,7 @@ for _,v in ipairs {"left", "right", "up", "down"} do
placement["stretch_"..v] = function(d, args)
args = add_context(args, "stretch_"..v)
args.direction = v
- placement_private.stretch(d, args)
+ return placement_private.stretch(d, args)
end
end
@@ -785,6 +1028,7 @@ end
--@DOC_awful_placement_maximize_EXAMPLE@
-- @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
@@ -808,6 +1052,8 @@ function placement.maximize(d, args)
geometry_common(d, args, ngeo)
attach(d, placement.maximize, args)
+
+ return ngeo
end
-- Add the alias functions
@@ -815,10 +1061,66 @@ for _, v in ipairs {"vertically", "horizontally"} do
placement["maximize_"..v] = function(d2, args)
args = add_context(args, "maximize_"..v)
args.axis = v
- placement_private.maximize(d2, args)
+ return placement_private.maximize(d2, args)
end
end
+--- 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 ngeo
+end
+
---@DOC_awful_placement_maximize_vertically_EXAMPLE@
---@DOC_awful_placement_maximize_horizontally_EXAMPLE@
diff --git a/mouse.c b/mouse.c
index 5c80b17b..272b12ad 100644
--- a/mouse.c
+++ b/mouse.c
@@ -19,7 +19,39 @@
*
*/
-/** awesome mouse API
+/** awesome mouse API.
+ *
+ * The mouse buttons are represented as index. The common ones are:
+ *
+ * ![Client geometry](../images/mouse.svg)
+ *
+ * It is possible to be notified of mouse events by connecting to various
+ * `client`, `widget`s and `wibox` signals:
+ *
+ * * `mouse::enter`
+ * * `mouse::leave`
+ * * `mouse::press`
+ * * `mouse::release`
+ * * `mouse::move`
+ *
+ * It is also possible to add generic mouse button callbacks for `client`s,
+ * `wiboxe`s and the `root` window. Those are set in the default `rc.lua` as such:
+ *
+ * **root**:
+ *
+ * root.buttons(awful.util.table.join(
+ * awful.button({ }, 3, function () mymainmenu:toggle() end),
+ * awful.button({ }, 4, awful.tag.viewnext),
+ * awful.button({ }, 5, awful.tag.viewprev)
+ * ))
+ *
+ * **client**:
+ *
+ * clientbuttons = awful.util.table.join(
+ * awful.button({ }, 1, function (c) client.focus = c; c:raise() end),
+ * awful.button({ modkey }, 1, awful.mouse.client.move),
+ * awful.button({ modkey }, 3, awful.mouse.client.resize)
+ * )
*
* See also `mousegrabber`
*
@@ -33,27 +65,19 @@
#include "math.h"
#include "common/util.h"
#include "common/xutil.h"
+#include "common/luaclass.h"
#include "globalconf.h"
#include "objects/client.h"
#include "objects/drawin.h"
#include "objects/screen.h"
-/** Mouse library.
- *
- * @table mouse
- */
+static int miss_index_handler = LUA_REFNIL;
+static int miss_newindex_handler = LUA_REFNIL;
/**
* The `screen` under the cursor
- * @field screen
- */
-
-/** A table with X and Y coordinates.
- * @field x X coordinate.
- * @field y Y coordinate.
- * @field buttons Table containing the status of buttons, e.g. field [1] is true
- * when button 1 is pressed.
- * @table coords_table
+ * @property screen
+ * @param screen
*/
/** Get the pointer position.
@@ -119,6 +143,39 @@ mouse_warp_pointer(xcb_window_t window, int16_t x, int16_t y)
0, 0, 0, 0, x, y);
}
+/**
+ * Allow the a Lua handler to be implemented for custom properties and
+ * functions.
+ * \param L A lua state
+ * \param handler A function on the LUA_REGISTRYINDEX
+ */
+static int
+luaA_mouse_call_handler(lua_State *L, int handler)
+{
+ int nargs = lua_gettop(L);
+
+ /* Push error handling function and move it before args */
+ lua_pushcfunction(L, luaA_dofunction_error);
+ lua_insert(L, - nargs - 1);
+ int error_func_pos = 1;
+
+ /* push function and move it before args */
+ lua_rawgeti(L, LUA_REGISTRYINDEX, handler);
+ lua_insert(L, - nargs - 1);
+
+ if(lua_pcall(L, nargs, LUA_MULTRET, error_func_pos))
+ {
+ warn("%s", lua_tostring(L, -1));
+ /* Remove error function and error string */
+ lua_pop(L, 2);
+ return 0;
+ }
+ /* Remove error function */
+ lua_remove(L, error_func_pos);
+
+ return lua_gettop(L);
+}
+
/** Mouse library.
* \param L The Lua VM state.
* \return The number of elements pushed on stack.
@@ -133,8 +190,13 @@ luaA_mouse_index(lua_State *L)
int16_t mouse_x, mouse_y;
/* attr is not "screen"?! */
- if (A_STRNEQ(attr, "screen"))
- return luaA_default_index(L);
+ if (A_STRNEQ(attr, "screen")) {
+ if (miss_index_handler != LUA_REFNIL) {
+ return luaA_mouse_call_handler(L, miss_index_handler);
+ }
+ else
+ return luaA_default_index(L);
+ }
if (!mouse_query_pointer_root(&mouse_x, &mouse_y, NULL, NULL))
{
@@ -162,8 +224,14 @@ luaA_mouse_newindex(lua_State *L)
const char *attr = luaL_checkstring(L, 2);
screen_t *screen;
- if (A_STRNEQ(attr, "screen"))
- return luaA_default_newindex(L);
+ if (A_STRNEQ(attr, "screen")) {
+ /* Call the lua mouse property handler */
+ if (miss_newindex_handler != LUA_REFNIL) {
+ return luaA_mouse_call_handler(L, miss_newindex_handler);
+ }
+ else
+ return luaA_default_newindex(L);
+ }
screen = luaA_checkscreen(L, 3);
mouse_warp_pointer(globalconf.screen->root, screen->geometry.x, screen->geometry.y);
@@ -201,15 +269,7 @@ luaA_mouse_pushstatus(lua_State *L, int x, int y, uint16_t mask)
return 1;
}
-/** Get or set the mouse coords.
- *
- * @tparam coords_table coords_table None or a table with x and y keys as mouse
- * coordinates.
- * @tparam boolean silent Disable mouse::enter or mouse::leave events that
- * could be triggered by the pointer when moving.
- * @treturn coords_table A table with mouse coordinates.
- * @function coords
- */
+/* documented in lib/awful/mouse/init.lua */
static int
luaA_mouse_coords(lua_State *L)
{
@@ -271,12 +331,32 @@ luaA_mouse_object_under_pointer(lua_State *L)
return 0;
}
+/**
+ * Add a custom property handler (getter).
+ */
+static int
+luaA_mouse_set_index_miss_handler(lua_State *L)
+{
+ return luaA_registerfct(L, 1, &miss_index_handler);
+}
+
+/**
+ * Add a custom property handler (setter).
+ */
+static int
+luaA_mouse_set_newindex_miss_handler(lua_State *L)
+{
+ return luaA_registerfct(L, 1, &miss_newindex_handler);
+}
+
const struct luaL_Reg awesome_mouse_methods[] =
{
{ "__index", luaA_mouse_index },
{ "__newindex", luaA_mouse_newindex },
{ "coords", luaA_mouse_coords },
{ "object_under_pointer", luaA_mouse_object_under_pointer },
+ { "set_index_miss_handler", luaA_mouse_set_index_miss_handler},
+ { "set_newindex_miss_handler", luaA_mouse_set_newindex_miss_handler},
{ NULL, NULL }
};
const struct luaL_Reg awesome_mouse_meta[] =
diff --git a/tests/examples/CMakeLists.txt b/tests/examples/CMakeLists.txt
index 71469153..0c40ede3 100644
--- a/tests/examples/CMakeLists.txt
+++ b/tests/examples/CMakeLists.txt
@@ -46,7 +46,6 @@ function(escape_string variable content escaped_content line_prefix)
if(variable MATCHES "--DOC_HIDE_ALL")
return()
endif()
-
string(REGEX REPLACE "\n" ";" var_lines "${variable}")
set(tmp_output ${content})
@@ -201,10 +200,17 @@ function(run_test test_path namespace template escaped_content)
# Only add it if there is something to display.
if(NOT ${TEST_CODE} STREQUAL "\n--")
+ # Do not use the @usage tag, use 4 spaces
+ file(READ ${test_path} tmp_content)
+ if(NOT tmp_content MATCHES "--DOC_NO_USAGE")
+ set(DOC_PREFIX "@usage")
+ endif()
+
escape_string(
- " @usage"
+ " ${DOC_PREFIX}"
"${TEST_DOC_CONTENT}" TEST_DOC_CONTENT ""
)
+
set(TEST_DOC_CONTENT "${TEST_DOC_CONTENT}${TEST_CODE}")
endif()
diff --git a/tests/examples/awful/mouse/coords.lua b/tests/examples/awful/mouse/coords.lua
new file mode 100644
index 00000000..b4b0ed95
--- /dev/null
+++ b/tests/examples/awful/mouse/coords.lua
@@ -0,0 +1,13 @@
+screen[1]._resize {x = 175, width = 128, height = 96} --DOC_HIDE
+mouse.coords {x=175+60,y=60} --DOC_HIDE
+
+ -- Get the position
+print(mouse.coords().x)
+
+ -- Change the position
+mouse.coords {
+ x = 185,
+ y = 10
+}
+
+mouse.push_history() --DOC_HIDE
diff --git a/tests/examples/awful/placement/bottom.lua b/tests/examples/awful/placement/bottom.lua
index 10dff9ab..6af09262 100644
--- a/tests/examples/awful/placement/bottom.lua
+++ b/tests/examples/awful/placement/bottom.lua
@@ -1,6 +1,7 @@
-- Align a client to the bottom of the parent area. --DOC_HEADER
-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`) --DOC_HEADER
-- @tparam[opt={}] table args Other arguments") --DOC_HEADER
+-- @treturn table The new geometry --DOC_HEADER
-- @name bottom --DOC_HEADER
-- @class function --DOC_HEADER
diff --git a/tests/examples/awful/placement/bottom_left.lua b/tests/examples/awful/placement/bottom_left.lua
index a9ebc062..4d4cff37 100644
--- a/tests/examples/awful/placement/bottom_left.lua
+++ b/tests/examples/awful/placement/bottom_left.lua
@@ -1,6 +1,7 @@
-- Align a client to the bottom left of the parent area. --DOC_HEADER
-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`) --DOC_HEADER
-- @tparam[opt={}] table args Other arguments") --DOC_HEADER
+-- @treturn table The new geometry --DOC_HEADER
-- @name bottom_left --DOC_HEADER
-- @class function --DOC_HEADER
diff --git a/tests/examples/awful/placement/bottom_right.lua b/tests/examples/awful/placement/bottom_right.lua
index 0ad1072e..2abb4490 100644
--- a/tests/examples/awful/placement/bottom_right.lua
+++ b/tests/examples/awful/placement/bottom_right.lua
@@ -1,6 +1,7 @@
-- Align a client to the bottom right of the parent area. --DOC_HEADER
-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`) --DOC_HEADER
-- @tparam[opt={}] table args Other arguments") --DOC_HEADER
+-- @treturn table The new geometry --DOC_HEADER
-- @name bottom_right --DOC_HEADER
-- @class function --DOC_HEADER
diff --git a/tests/examples/awful/placement/center_horizontal.lua b/tests/examples/awful/placement/center_horizontal.lua
index b9d1d6bb..931b947c 100644
--- a/tests/examples/awful/placement/center_horizontal.lua
+++ b/tests/examples/awful/placement/center_horizontal.lua
@@ -1,6 +1,7 @@
-- Align a client to the horizontal center left of the parent area. --DOC_HEADER
-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`) --DOC_HEADER
-- @tparam[opt={}] table args Other arguments") --DOC_HEADER
+-- @treturn table The new geometry --DOC_HEADER
-- @name center_horizontal --DOC_HEADER
-- @class function --DOC_HEADER
screen[1]._resize {width = 128, height = 96} --DOC_HIDE
diff --git a/tests/examples/awful/placement/centered.lua b/tests/examples/awful/placement/centered.lua
index af1a1c84..d6b1e3ae 100644
--- a/tests/examples/awful/placement/centered.lua
+++ b/tests/examples/awful/placement/centered.lua
@@ -1,6 +1,7 @@
-- Align a client to the center of the parent area. --DOC_HEADER
-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`) --DOC_HEADER
-- @tparam[opt={}] table args Other arguments") --DOC_HEADER
+-- @treturn table The new geometry --DOC_HEADER
-- @name centered --DOC_HEADER
-- @class function --DOC_HEADER
diff --git a/tests/examples/awful/placement/closest_mouse.lua b/tests/examples/awful/placement/closest_mouse.lua
index 90642641..4269d1d8 100644
--- a/tests/examples/awful/placement/closest_mouse.lua
+++ b/tests/examples/awful/placement/closest_mouse.lua
@@ -55,7 +55,7 @@ assert(mouse.coords().x == c.x and mouse.coords().y == c.y+c.height+2*bw) --DOC_
-- It is possible to emulate the mouse API to get the closest corner of
-- random area
-local corner = awful.placement.closest_corner(
+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}}
)
diff --git a/tests/examples/awful/placement/compose.lua b/tests/examples/awful/placement/compose.lua
index 28098677..f6710762 100644
--- a/tests/examples/awful/placement/compose.lua
+++ b/tests/examples/awful/placement/compose.lua
@@ -1,9 +1,11 @@
-screen[1]._resize {width = 128, height = 96} --DOC_HIDE
+screen[1]._resize {x = 175, width = 128, height = 96} --DOC_NO_USAGE --DOC_HIDE
local awful = {placement = require("awful.placement")} --DOC_HIDE
-local c = client.gen_fake {x = 45, y = 35, width=40, height=30} --DOC_HIDE
+local c = client.gen_fake {x = 220, y = 35, width=40, height=30} --DOC_HIDE
-local f = (awful.placement.right + awful.placement.left)
-f(client.focus)
+ -- "right" will be replaced by "left"
+ local f = (awful.placement.right + awful.placement.left)
+ f(client.focus)
-assert(c.x == 0 and c.y==screen[1].geometry.height/2-30/2-c.border_width--DOC_HIDE
+local sg = screen[1].geometry--DOC_HIDE
+assert(c.x == sg.x and c.y==sg.height/2-30/2-c.border_width--DOC_HIDE
and c.width==40 and c.height==30)--DOC_HIDE
diff --git a/tests/examples/awful/placement/compose2.lua b/tests/examples/awful/placement/compose2.lua
new file mode 100644
index 00000000..68cbf5b3
--- /dev/null
+++ b/tests/examples/awful/placement/compose2.lua
@@ -0,0 +1,19 @@
+screen[1]._resize {x = 175, width = 128, height = 96} --DOC_NO_USAGE --DOC_HIDE
+local awful = {placement = require("awful.placement")} --DOC_HIDE
+local c = client.gen_fake {x = 220, y = 35, width=40, height=30} --DOC_HIDE
+
+
+ -- 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})
+
+local wa = screen[1].workarea--DOC_HIDE
+assert(c.x == wa.x and geo.x == wa.x)--DOC_HIDE
+assert(c.y == wa.y) --DOC_HIDE
+assert(c.width == wa.width/2 - 2*c.border_width)--DOC_HIDE
+assert(c.height == wa.height - 2*c.border_width)--DOC_HIDE
diff --git a/tests/examples/awful/placement/left.lua b/tests/examples/awful/placement/left.lua
index 49cf93ac..d3e4e698 100644
--- a/tests/examples/awful/placement/left.lua
+++ b/tests/examples/awful/placement/left.lua
@@ -1,6 +1,7 @@
-- Align a client to the left of the parent area. --DOC_HEADER
-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`) --DOC_HEADER
-- @tparam[opt={}] table args Other arguments") --DOC_HEADER
+-- @treturn table The new geometry --DOC_HEADER
-- @name left --DOC_HEADER
-- @class function --DOC_HEADER
diff --git a/tests/examples/awful/placement/resize_to_mouse.lua b/tests/examples/awful/placement/resize_to_mouse.lua
new file mode 100644
index 00000000..3114fb38
--- /dev/null
+++ b/tests/examples/awful/placement/resize_to_mouse.lua
@@ -0,0 +1,74 @@
+--DOC_HIDE_ALL
+local awful = {placement = require("awful.placement")}
+local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
+
+screen._setup_grid(64, 48, {4, 4, 4, 4}, {workarea_sides=0})
+
+local function test_touch_mouse(c)
+ local coords = mouse.coords()
+
+ return c:geometry().x == coords.x or c:geometry().y == coords.y
+ or c:geometry().x+c:geometry().width+2*c.border_width == coords.x
+ or c:geometry().y+c:geometry().height+2*c.border_width == coords.y
+end
+
+for s=1, 8 do
+ local scr = screen[s]
+ local x, y = scr.geometry.x, scr.geometry.y
+ local c = client.gen_fake{x = x+22, y = y+16, width=20, height=15, screen=scr}
+ assert(client.get()[s] == c)
+end
+
+for s=9, 16 do
+ local scr = screen[s]
+ local x, y = scr.geometry.x, scr.geometry.y
+ local c = client.gen_fake{x = x+10, y = y+10, width=44, height=28, screen=scr}
+ assert(client.get()[s] == c)
+end
+
+local function move_corsor(s, x, y)
+ local sg = screen[s].geometry
+ mouse.coords {x=sg.x+x,y=sg.y+y}
+end
+
+local all_coords_out = {
+ top_left = {10, 10},
+ top = {32, 10},
+ top_right = {60, 10},
+ right = {60, 20},
+ bottom_right = {60, 40},
+ bottom = {32, 40},
+ bottom_left = {10, 40},
+ left = {10, 29},
+}
+
+local all_coords_in = {
+ top_left = {20, 18},
+ top = {32, 18},
+ top_right = {44, 18},
+ right = {44, 24},
+ bottom_right = {44, 34},
+ bottom = {32, 34},
+ bottom_left = {20, 34},
+ left = {32, 24},
+}
+
+-- Top left
+local s = 1
+for k, v in pairs(all_coords_out) do
+ move_corsor(s, unpack(v))
+ assert(client.get()[s].screen == screen[s])
+ awful.placement.resize_to_mouse(client.get()[s], {include_sides=true})
+ mouse.push_history()
+ assert(test_touch_mouse(client.get()[s]), k)
+ s = s + 1
+end
+
+for k, v in pairs(all_coords_in) do
+ move_corsor(s, unpack(v))
+ assert(client.get()[s].screen == screen[s])
+ awful.placement.resize_to_mouse(client.get()[s], {include_sides=true})
+ mouse.push_history()
+ assert(test_touch_mouse(client.get()[s]), k)
+ s = s + 1
+end
diff --git a/tests/examples/awful/placement/right.lua b/tests/examples/awful/placement/right.lua
index d2450116..4e16cea6 100644
--- a/tests/examples/awful/placement/right.lua
+++ b/tests/examples/awful/placement/right.lua
@@ -1,6 +1,7 @@
-- Align a client to the right of the parent area. --DOC_HEADER
-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`) --DOC_HEADER
-- @tparam[opt={}] table args Other arguments") --DOC_HEADER
+-- @treturn table The new geometry --DOC_HEADER
-- @name right --DOC_HEADER
-- @class function --DOC_HEADER
diff --git a/tests/examples/awful/placement/stretch_down.lua b/tests/examples/awful/placement/stretch_down.lua
index 258647f8..720da36d 100644
--- a/tests/examples/awful/placement/stretch_down.lua
+++ b/tests/examples/awful/placement/stretch_down.lua
@@ -1,6 +1,7 @@
-- Stretch the drawable to the bottom of the parent area. --DOC_HEADER
-- @tparam drawable d A drawable (like `client` or `wibox`) --DOC_HEADER
-- @tparam[opt={}] table args Other arguments --DOC_HEADER
+-- @treturn table The new geometry --DOC_HEADER
-- @name stretch_down --DOC_HEADER
-- @class function --DOC_HEADER
diff --git a/tests/examples/awful/placement/stretch_left.lua b/tests/examples/awful/placement/stretch_left.lua
index 51364697..4fdd5857 100644
--- a/tests/examples/awful/placement/stretch_left.lua
+++ b/tests/examples/awful/placement/stretch_left.lua
@@ -1,6 +1,7 @@
-- Stretch the drawable to the left of the parent area. --DOC_HEADER
-- @tparam drawable d A drawable (like `client` or `wibox`) --DOC_HEADER
-- @tparam[opt={}] table args Other arguments --DOC_HEADER
+-- @treturn table The new geometry --DOC_HEADER
-- @name stretch_left --DOC_HEADER
-- @class function --DOC_HEADER
diff --git a/tests/examples/awful/placement/stretch_right.lua b/tests/examples/awful/placement/stretch_right.lua
index 9965d1a6..47dcce80 100644
--- a/tests/examples/awful/placement/stretch_right.lua
+++ b/tests/examples/awful/placement/stretch_right.lua
@@ -1,6 +1,7 @@
-- Stretch the drawable to the right of the parent area. --DOC_HEADER
-- @tparam drawable d A drawable (like `client` or `wibox`) --DOC_HEADER
-- @tparam[opt={}] table args Other arguments --DOC_HEADER
+-- @treturn table The new geometry --DOC_HEADER
-- @name stretch_right --DOC_HEADER
-- @class function --DOC_HEADER
diff --git a/tests/examples/awful/placement/stretch_up.lua b/tests/examples/awful/placement/stretch_up.lua
index 817a11d5..0ccfd2bc 100644
--- a/tests/examples/awful/placement/stretch_up.lua
+++ b/tests/examples/awful/placement/stretch_up.lua
@@ -1,6 +1,7 @@
-- Stretch the drawable to the top of the parent area. --DOC_HEADER
-- @tparam drawable d A drawable (like `client` or `wibox`) --DOC_HEADER
-- @tparam[opt={}] table args Other arguments --DOC_HEADER
+-- @treturn table The new geometry --DOC_HEADER
-- @name stretch_up --DOC_HEADER
-- @class function --DOC_HEADER
diff --git a/tests/examples/awful/placement/top.lua b/tests/examples/awful/placement/top.lua
index f84ea306..b3375fbc 100644
--- a/tests/examples/awful/placement/top.lua
+++ b/tests/examples/awful/placement/top.lua
@@ -1,6 +1,7 @@
-- Align a client to the top of the parent area. --DOC_HEADER
-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`) --DOC_HEADER
-- @tparam[opt={}] table args Other arguments") --DOC_HEADER
+-- @treturn table The new geometry --DOC_HEADER
-- @name top --DOC_HEADER
-- @class function --DOC_HEADER
diff --git a/tests/examples/awful/placement/top_left.lua b/tests/examples/awful/placement/top_left.lua
index a2bb3b58..f70b85f6 100644
--- a/tests/examples/awful/placement/top_left.lua
+++ b/tests/examples/awful/placement/top_left.lua
@@ -1,6 +1,7 @@
-- Align a client to the top left of the parent area. --DOC_HEADER
-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`) --DOC_HEADER
-- @tparam[opt={}] table args Other arguments") --DOC_HEADER
+-- @treturn table The new geometry --DOC_HEADER
-- @name top_left --DOC_HEADER
-- @class function --DOC_HEADER
diff --git a/tests/examples/awful/placement/top_right.lua b/tests/examples/awful/placement/top_right.lua
index a94890c9..ef6fc833 100644
--- a/tests/examples/awful/placement/top_right.lua
+++ b/tests/examples/awful/placement/top_right.lua
@@ -1,6 +1,7 @@
-- Align a client to the top right of the parent area. --DOC_HEADER
-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`) --DOC_HEADER
-- @tparam[opt={}] table args Other arguments") --DOC_HEADER
+-- @treturn table The new geometry --DOC_HEADER
-- @name top_right --DOC_HEADER
-- @class function --DOC_HEADER
diff --git a/tests/examples/shims/client.lua b/tests/examples/shims/client.lua
index 97d19fd9..5fcc6486 100644
--- a/tests/examples/shims/client.lua
+++ b/tests/examples/shims/client.lua
@@ -166,6 +166,9 @@ client:_add_signal("property::fullscreen")
client:_add_signal("property::border_width")
client:_add_signal("property::hidden")
client:_add_signal("property::screen")
+client:_add_signal("request::tag")
+client:_add_signal("request::geometry")
+client:_add_signal("request::activate")
client:_add_signal("raised")
client:_add_signal("lowered")
client:_add_signal("list")
diff --git a/tests/test-miss-handlers.lua b/tests/test-miss-handlers.lua
index f97d089b..fe61a860 100644
--- a/tests/test-miss-handlers.lua
+++ b/tests/test-miss-handlers.lua
@@ -1,5 +1,6 @@
-- Test set_{,new}index_miss_handler
+local mouse = mouse
local class = tag
local obj = class({})
local handler = require("gears.object.properties")
@@ -30,4 +31,8 @@ assert(not obj.key)
obj.key = 1337
assert(obj.key == 1337)
+-- The the custom mouse handler
+mouse.foo = "bar"
+assert(mouse.foo == "bar")
+
require("_runner").run_steps({ function() return true end })
diff --git a/tests/test-resize.lua b/tests/test-resize.lua
new file mode 100644
index 00000000..a9eb7168
--- /dev/null
+++ b/tests/test-resize.lua
@@ -0,0 +1,409 @@
+local test_client = require("_client")
+local placement = require("awful.placement")
+local amouse = require("awful.mouse")
+
+local steps = {}
+
+table.insert(steps, function(count)
+ if count == 1 then -- Setup.
+ test_client("foobar", "foobar")
+ elseif #client.get() > 0 then
+
+ client.get()[1] : geometry {
+ x = 200,
+ y = 200,
+ width = 300,
+ height = 300,
+ }
+
+ return true
+ end
+end)
+
+table.insert(steps, function()
+ -- The mousegrabber expect a button to be pressed.
+ root.fake_input("button_press",1)
+ local c = client.get()[1]
+
+ -- Just in case there is an accidental delayed geometry callback
+ assert(c:geometry().x == 200)
+ assert(c:geometry().y == 200)
+ assert(c:geometry().width == 300)
+ assert(c:geometry().height == 300)
+
+ mouse.coords {x = 500+2*c.border_width, y= 500+2*c.border_width}
+
+ local corner = amouse.client.resize(c)
+
+ assert(corner == "bottom_right")
+
+ return true
+end)
+
+-- The geometry should remain the same, as the cursor is placed at the end of
+-- the geometry.
+table.insert(steps, function()
+
+ local c = client.get()[1]
+
+ assert(c:geometry().x == 200)
+ assert(c:geometry().y == 200)
+ assert(c:geometry().width == 300)
+ assert(c:geometry().height == 300)
+
+ mouse.coords {x = 600+2*c.border_width, y= 600+2*c.border_width}
+
+ return true
+end)
+
+-- Grow the client by 100px
+table.insert(steps, function()
+
+ local c = client.get()[1]
+
+ assert(c:geometry().x == 200)
+ assert(c:geometry().y == 200)
+ assert(c:geometry().width == 400)
+ assert(c:geometry().height == 400)
+
+ mouse.coords {x = 400+2*c.border_width, y= 400+2*c.border_width}
+
+ return true
+end)
+
+-- Shirnk the client by 200px
+table.insert(steps, function()
+
+ local c = client.get()[1]
+
+ assert(c:geometry().x == 200)
+ assert(c:geometry().y == 200)
+ assert(c:geometry().width == 200)
+ assert(c:geometry().height == 200)
+
+ mouse.coords {x = 100, y= 100}
+
+ return true
+end)
+
+-- Grow the client by 100px from the top left
+table.insert(steps, function()
+
+ local c = client.get()[1]
+
+ assert(c:geometry().x == 100)
+ assert(c:geometry().y == 100)
+ assert(c:geometry().width == 300)
+ assert(c:geometry().height == 300)
+
+ mouse.coords {x = 300, y= 200}
+
+ return true
+end)
+
+-- Shirnk the client by 100px from the top right
+table.insert(steps, function()
+
+ local c = client.get()[1]
+
+ assert(c:geometry().x == 100)
+ assert(c:geometry().y == 200)
+-- assert(c:geometry().width == 200-2*c.border_width) --FIXME off by border width...
+-- assert(c:geometry().height == 200-2*c.border_width) --FIXME off by border width...
+
+ mouse.coords {x = 300, y= 200}
+
+ return true
+end)
+
+-- Stop the resize
+table.insert(steps, function()
+ root.fake_input("button_release",1)
+
+-- if not mousegrabber.isrunning then --FIXME it should work, but doesn't
+-- return true
+-- end
+
+ mousegrabber.stop()
+
+ return true
+end)
+
+-- Setup for move testing
+table.insert(steps, function()
+ assert(not mousegrabber.isrunning())
+
+ local c = client.get()[1]
+
+ c:geometry {
+ width = 200,
+ height = 200,
+ }
+
+ placement.bottom_right(c)
+
+ mouse.coords {x = c.screen.geometry.width -150,
+ y = c.screen.geometry.height-150}
+
+
+ return true
+end)
+
+-- Start the move mouse grabber
+table.insert(steps, function()
+ local c = client.get()[1]
+
+ -- The resize is over, it should not move the client anymore
+ assert(c:geometry().x == c.screen.geometry.width - 200 - 2*c.border_width)
+ assert(c:geometry().y == c.screen.geometry.height - 200 - 2*c.border_width)
+ assert(c:geometry().width == 200)
+ assert(c:geometry().height == 200)
+
+ assert(c.valid)
+
+ root.fake_input("button_press",1)
+
+ -- Begin the move
+ amouse.client.move(c)
+
+ -- Make sure nothing unwanted happen by accident
+ assert(c:geometry().x == c.screen.geometry.width - 200 - 2*c.border_width)
+ assert(c:geometry().y == c.screen.geometry.height - 200 - 2*c.border_width)
+ assert(c:geometry().width == 200)
+ assert(c:geometry().height == 200)
+
+ -- The cursor should not have moved
+ assert(mouse.coords().x == c.screen.geometry.width - 150)
+ assert(mouse.coords().y == c.screen.geometry.height - 150)
+ mouse.coords {x = 50 + 2*c.border_width, y= 50 + 2*c.border_width}
+
+ assert(mousegrabber.isrunning())
+
+ return true
+end)
+
+-- The client should now be in the top left
+table.insert(steps, function()
+ local c = client.get()[1]
+ assert(c:geometry().x == 0)
+ assert(c:geometry().y == 0)
+ assert(c:geometry().width == 200)
+ assert(c:geometry().height == 200)
+
+ -- Move to the bottom left
+ mouse.coords {
+ x = 50 + 2*c.border_width,
+ y = c.screen.geometry.height - 200
+ }
+
+ return true
+end)
+
+-- Ensure the move was correct, the snap to the top part of the screen
+table.insert(steps, function()
+ local c = client.get()[1]
+
+ assert(c:geometry().x == 0)
+ assert(c:geometry().y == c.screen.geometry.height - 250 - 2*c.border_width)
+ assert(c:geometry().width == 200)
+ assert(c:geometry().height == 200)
+
+ -- Should trigger the top snap
+ mouse.coords {x = 600, y= 0}
+
+ -- The snap is only applied on release
+ root.fake_input("button_release",1)
+
+ return true
+end)
+
+-- The client should now fill the top half of the screen
+table.insert(steps, function()
+ local c = client.get()[1]
+
+ assert(c:geometry().x == c.screen.workarea.x )
+ assert(c:geometry().y == c.screen.workarea.y )
+ assert(c:geometry().width == c.screen.workarea.width - 2*c.border_width )
+ assert(c:geometry().height == math.ceil(c.screen.workarea.height/2 - 2*c.border_width))
+
+ -- Snap to the top right
+ root.fake_input("button_press",1)
+ amouse.client.move(c)
+ placement.top_right(mouse, {honor_workarea=false})
+ root.fake_input("button_release",1)
+
+ return true
+end)
+
+-- The client should now fill the top right corner of the screen
+table.insert(steps, function()
+ local c = client.get()[1]
+
+ assert(c:geometry().x == c.screen.workarea.x+c.screen.workarea.width/2 )
+ assert(c:geometry().y == c.screen.workarea.y )
+ assert(c:geometry().width == math.ceil(c.screen.workarea.width/2 - 2*c.border_width) )
+ assert(c:geometry().height == math.ceil(c.screen.workarea.height/2 - 2*c.border_width))
+
+ -- Snap to the top right
+ root.fake_input("button_press",1)
+ amouse.client.move(c)
+ placement.right(mouse, {honor_workarea=false})
+ root.fake_input("button_release",1)
+
+ return true
+end)
+
+-- The client should now fill the top right half of the screen
+table.insert(steps, function()
+ local c = client.get()[1]
+
+ assert(c:geometry().x == c.screen.workarea.x+c.screen.workarea.width/2 )
+ assert(c:geometry().y == c.screen.workarea.y )
+ assert(c:geometry().width == math.ceil(c.screen.workarea.width/2 - 2*c.border_width))
+ assert(c:geometry().height == c.screen.workarea.height - 2*c.border_width )
+
+ -- Snap to the top right
+ root.fake_input("button_press",1)
+ amouse.client.move(c)
+ placement.bottom(mouse, {honor_workarea=false})
+ root.fake_input("button_release",1)
+
+ return true
+end)
+
+-- The client should now fill the bottom half of the screen
+table.insert(steps, function()
+ local c = client.get()[1]
+
+ assert(c:geometry().x == c.screen.workarea.x )
+ assert(c:geometry().y == c.screen.workarea.y+c.screen.workarea.height/2 )
+ assert(c:geometry().width == c.screen.workarea.width - 2*c.border_width )
+ assert(c:geometry().height == math.ceil(c.screen.workarea.height/2 - 2*c.border_width))
+
+ -- Snap to the top right
+ root.fake_input("button_press",1)
+ amouse.client.move(c)
+ placement.bottom_left(mouse, {honor_workarea=false})
+ root.fake_input("button_release",1)
+
+ return true
+end)
+
+-- The client should now fill the bottom left corner of the screen
+table.insert(steps, function()
+ local c = client.get()[1]
+
+ assert(c:geometry().x == c.screen.workarea.x )
+ assert(c:geometry().y == c.screen.workarea.y+c.screen.workarea.height/2 )
+ assert(c:geometry().width == math.ceil(c.screen.workarea.width/2 - 2*c.border_width) )
+ assert(c:geometry().height == math.ceil(c.screen.workarea.height/2 - 2*c.border_width))
+
+ -- Snap to the top right
+ root.fake_input("button_press",1)
+ amouse.client.move(c)
+ placement.left(mouse, {honor_workarea=false})
+ root.fake_input("button_release",1)
+
+ return true
+end)
+
+-- The client should now fill the left half of the screen
+table.insert(steps, function()
+ local c = client.get()[1]
+
+ assert(c:geometry().x == c.screen.workarea.x )
+ assert(c:geometry().y == c.screen.workarea.y )
+ assert(c:geometry().width == math.ceil(c.screen.workarea.width/2 - 2*c.border_width) )
+ assert(c:geometry().height == c.screen.workarea.height - 2*c.border_width )
+
+ -- Snap to the top right
+ root.fake_input("button_press",1)
+ amouse.client.move(c)
+ placement.top_left(mouse, {honor_workarea=false})
+ root.fake_input("button_release",1)
+
+ return true
+end)
+
+local cur_tag = nil
+
+-- The client should now fill the top left corner of the screen
+table.insert(steps, function()
+ local c = client.get()[1]
+
+ assert(c:geometry().x == c.screen.workarea.x )
+ assert(c:geometry().y == c.screen.workarea.y )
+ assert(c:geometry().width == math.ceil(c.screen.workarea.width/2 - 2*c.border_width) )
+ assert(c:geometry().height == math.ceil(c.screen.workarea.height/2 - 2*c.border_width))
+
+ -- Change the mode to test drag_to_tag
+ amouse.drag_to_tag.enabled = true
+ amouse.snap.edge_enabled = false
+
+ cur_tag = c.first_tag
+
+ root.fake_input("button_press",1)
+ amouse.client.move(c)
+ placement.right(mouse, {honor_workarea=false})
+ root.fake_input("button_release",1)
+
+ return true
+end)
+
+-- The tag should have changed
+table.insert(steps, function()
+ local c = client.get()[1]
+
+ assert(c.first_tag ~= cur_tag )
+ assert(c.first_tag.index == cur_tag.index + 1)
+
+ -- Move it back
+ root.fake_input("button_press",1)
+ amouse.client.move(c)
+ placement.left(mouse, {honor_workarea=false})
+ root.fake_input("button_release",1)
+
+ return true
+end)
+
+-- The tag should now be the same as before
+table.insert(steps, function()
+ local c = client.get()[1]
+
+ assert(c.first_tag == cur_tag)
+ assert(c.first_tag.index == 1)
+
+ -- Wrap
+ root.fake_input("button_press",1)
+ amouse.client.move(c)
+ placement.left(mouse, {honor_workarea=false})
+ root.fake_input("button_release",1)
+
+ return true
+end)
+
+-- The tag should now be the last
+table.insert(steps, function()
+ local c = client.get()[1]
+
+ assert(c.first_tag.index == #c.screen.tags)
+
+ -- Wrap back
+ root.fake_input("button_press",1)
+ amouse.client.move(c)
+ placement.right(mouse, {honor_workarea=false})
+ root.fake_input("button_release",1)
+
+ return true
+end)
+
+-- The tag should now original one again
+table.insert(steps, function()
+ local c = client.get()[1]
+
+ assert(c.first_tag == cur_tag)
+
+ return true
+end)
+
+require("_runner").run_steps(steps)