awful.mouse: Add a generic mousegrabber

Previously, all layouts had their own mouse grabbing logic. The
new one is based on the client request::geometry feature.
This commit is contained in:
Emmanuel Lepage Vallee 2016-04-21 04:03:20 -04:00
parent e93e2913b6
commit f8f57fb6b7
2 changed files with 196 additions and 1 deletions

View File

@ -26,7 +26,9 @@ local capi =
mousegrabber = mousegrabber,
}
local mouse = {}
local mouse = {
resize = require("awful.mouse.resize")
}
mouse.client = {}
mouse.wibox = {}

193
lib/awful/mouse/resize.lua Normal file
View File

@ -0,0 +1,193 @@
---------------------------------------------------------------------------
--- 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 awful.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.
--
-- @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
-- @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.
-- @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
-- @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
--
-- @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})