From f8f57fb6b71a69a6a5f9efb8218912984e597e8c Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Thu, 21 Apr 2016 04:03:20 -0400 Subject: [PATCH] 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. --- lib/awful/mouse/init.lua | 4 +- lib/awful/mouse/resize.lua | 193 +++++++++++++++++++++++++++++++++++++ 2 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 lib/awful/mouse/resize.lua diff --git a/lib/awful/mouse/init.lua b/lib/awful/mouse/init.lua index bd8d01d48..4bc78d0fd 100644 --- a/lib/awful/mouse/init.lua +++ b/lib/awful/mouse/init.lua @@ -26,7 +26,9 @@ local capi = mousegrabber = mousegrabber, } -local mouse = {} +local mouse = { + resize = require("awful.mouse.resize") +} mouse.client = {} mouse.wibox = {} diff --git a/lib/awful/mouse/resize.lua b/lib/awful/mouse/resize.lua new file mode 100644 index 000000000..3662358e4 --- /dev/null +++ b/lib/awful/mouse/resize.lua @@ -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})