--------------------------------------------------------------------------- --- An extensible mouse resizing handler. -- -- This module offers a resizing and moving mechanism for drawables such as -- clients and wiboxes. -- -- @author Emmanuel Lepage Vallee <elv1313@gmail.com> -- @copyright 2016 Emmanuel Lepage Vallee -- @submodule mouse --------------------------------------------------------------------------- local aplace = require("awful.placement") local capi = {mousegrabber = mousegrabber, client = client} local beautiful = require("beautiful") local floating = require("awful.layout.suit.floating") local module = {} local mode = "live" local req = "request::geometry" local callbacks = {enter={}, move={}, leave={}} local cursors = { ["mouse.move" ] = "fleur", ["mouse.resize" ] = "cross", ["mouse.resize_left" ] = "sb_h_double_arrow", ["mouse.resize_right" ] = "sb_h_double_arrow", ["mouse.resize_top" ] = "sb_v_double_arrow", ["mouse.resize_bottom" ] = "sb_v_double_arrow", ["mouse.resize_top_left" ] = "top_left_corner", ["mouse.resize_top_right" ] = "top_right_corner", ["mouse.resize_bottom_left" ] = "bottom_left_corner", ["mouse.resize_bottom_right"] = "bottom_right_corner", } --- The resize cursor name. -- @beautiful beautiful.cursor_mouse_resize -- @tparam[opt=cross] string cursor --- The move cursor name. -- @beautiful beautiful.cursor_mouse_move -- @tparam[opt=fleur] string cursor --- Set the resize mode. -- The available modes are: -- -- * **live**: Resize the layout everytime the mouse moves. -- * **after**: Resize the layout only when the mouse is released. -- -- Some clients, such as XTerm, may lose information if resized too often. -- -- @function awful.mouse.resize.set_mode -- @tparam string m The mode function module.set_mode(m) assert(m == "live" or m == "after") mode = m end --- Add an initialization callback. -- This callback will be executed before the mouse grabbing starts. -- @function awful.mouse.resize.add_enter_callback -- @tparam function cb The callback (or nil) -- @tparam[default=other] string context The callback context function module.add_enter_callback(cb, context) context = context or "other" callbacks.enter[context] = callbacks.enter[context] or {} table.insert(callbacks.enter[context], cb) end --- Add a "move" callback. -- This callback is executed in "after" mode (see `set_mode`) instead of -- applying the operation. -- @function awful.mouse.resize.add_move_callback -- @tparam function cb The callback (or nil) -- @tparam[default=other] string context The callback context function module.add_move_callback(cb, context) context = context or "other" callbacks.move[context] = callbacks.move[context] or {} table.insert(callbacks.move[context], cb) end --- Add a "leave" callback -- This callback is executed just before the `mousegrabber` stop -- @function awful.mouse.resize.add_leave_callback -- @tparam function cb The callback (or nil) -- @tparam[default=other] string context The callback context function module.add_leave_callback(cb, context) context = context or "other" callbacks.leave[context] = callbacks.leave[context] or {} table.insert(callbacks.leave[context], cb) end --- Resize the drawable. -- -- Valid `args` are: -- -- * *enter_callback*: A function called before the `mousegrabber` starts. -- * *move_callback*: A function called when the mouse moves. -- * *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. -- @request wibox geometry mouse.resize granted Requests to resize the wibox. local function handler(_, client, context, args) --luacheck: no unused_args args = args or {} context = context or "mouse.resize" local placement = args.placement if type(placement) == "string" and aplace[placement] then placement = aplace[placement] end -- Extend the table with the default arguments args = setmetatable( { placement = placement or aplace.resize_to_mouse, mode = args.mode or mode, pretend = true, }, {__index = args or {}} ) local geo for _, cb in ipairs(callbacks.enter[context] or {}) do geo = cb(client, args) if geo == false then return false end end if args.enter_callback then geo = args.enter_callback(client, args) if geo == false then return false end end geo = nil -- Select the cursor local tcontext = context:gsub('[.]', '_') local corner = args.corner and ("_".. args.corner) or "" local cursor = beautiful["cursor_"..tcontext] or cursors[context..corner] or cursors[context] or "fleur" -- Execute the placement function and use request::geometry capi.mousegrabber.run(function (coords) if not client.valid then return end -- Resize everytime the mouse moves (default behavior) in live mode, -- otherwise keep the current geometry geo = setmetatable( args.mode == "live" and args.placement(client, args) or client:geometry(), {__index=args} ) -- 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 if geo then setmetatable(geo, {__index=args}) end 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(coords.buttons) do if v then return true end end -- Only resize after the mouse is released, this avoids losing content -- in resize sensitive apps such as XTerm or allows external modules -- to implement custom resizing. if args.mode == "after" then -- Get the new geometry geo = args.placement(client, args) -- Ask the resizing handler to resize the client client:emit_signal(req, context, geo) end geo = nil for _, cb in ipairs(callbacks.leave[context] or {}) do geo = cb(client, geo, args) end if args.leave_callback then geo = args.leave_callback(client, geo, args) end if not geo then return false end -- In case it was modified setmetatable(geo,{__index=args}) client:emit_signal(req, context, geo) return false end, cursor) end function module._resize_handler(c, context, hints) if hints and context and context:find("mouse.*") then -- This handler only handle the floating clients. If the client is tiled, -- then it let the layouts handle it. local t = c.screen.selected_tag local lay = t and t.layout or nil if (lay and lay == floating) or c.floating then c:geometry { x = hints.x, y = hints.y, width = hints.width, height = hints.height, } elseif lay and lay.resize_handler then lay.resize_handler(c, context, hints) end end end capi.client.connect_signal("request::geometry", module._resize_handler) return setmetatable(module, {__call=handler}) -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80