1--------------------------------------------------------------------------- 2--- An extendable mouse resizing handler. 3-- 4-- This module offers a resizing and moving mechanism for drawables such as 5-- clients and wiboxes. 6-- 7-- @author Emmanuel Lepage Vallee <elv1313@gmail.com> 8-- @copyright 2016 Emmanuel Lepage Vallee 9-- @submodule mouse 10--------------------------------------------------------------------------- 11 12local aplace = require("awful.placement") 13local capi = {mousegrabber = mousegrabber} 14local beautiful = require("beautiful") 15 16local module = {} 17 18local mode = "live" 19local req = "request::geometry" 20local callbacks = {enter={}, move={}, leave={}} 21 22local cursors = { 23 ["mouse.move" ] = "fleur", 24 ["mouse.resize" ] = "cross", 25 ["mouse.resize_left" ] = "sb_h_double_arrow", 26 ["mouse.resize_right" ] = "sb_h_double_arrow", 27 ["mouse.resize_top" ] = "sb_v_double_arrow", 28 ["mouse.resize_bottom" ] = "sb_v_double_arrow", 29 ["mouse.resize_top_left" ] = "top_left_corner", 30 ["mouse.resize_top_right" ] = "top_right_corner", 31 ["mouse.resize_bottom_left" ] = "bottom_left_corner", 32 ["mouse.resize_bottom_right"] = "bottom_right_corner", 33} 34 35--- The resize cursor name. 36-- @beautiful beautiful.cursor_mouse_resize 37-- @tparam[opt=cross] string cursor 38 39--- The move cursor name. 40-- @beautiful beautiful.cursor_mouse_move 41-- @tparam[opt=fleur] string cursor 42 43--- Set the resize mode. 44-- The available modes are: 45-- 46-- * **live**: Resize the layout everytime the mouse moves. 47-- * **after**: Resize the layout only when the mouse is released. 48-- 49-- Some clients, such as XTerm, may lose information if resized too often. 50-- 51-- @function awful.mouse.resize.set_mode 52-- @tparam string m The mode 53function module.set_mode(m) 54 assert(m == "live" or m == "after") 55 mode = m 56end 57 58--- Add an initialization callback. 59-- This callback will be executed before the mouse grabbing starts. 60-- @function awful.mouse.resize.add_enter_callback 61-- @tparam function cb The callback (or nil) 62-- @tparam[default=other] string context The callback context 63function module.add_enter_callback(cb, context) 64 context = context or "other" 65 callbacks.enter[context] = callbacks.enter[context] or {} 66 table.insert(callbacks.enter[context], cb) 67end 68 69--- Add a "move" callback. 70-- This callback is executed in "after" mode (see `set_mode`) instead of 71-- applying the operation. 72-- @function awful.mouse.resize.add_move_callback 73-- @tparam function cb The callback (or nil) 74-- @tparam[default=other] string context The callback context 75function module.add_move_callback(cb, context) 76 context = context or "other" 77 callbacks.move[context] = callbacks.move[context] or {} 78 table.insert(callbacks.move[context], cb) 79end 80 81--- Add a "leave" callback 82-- This callback is executed just before the `mousegrabber` stop 83-- @function awful.mouse.resize.add_leave_callback 84-- @tparam function cb The callback (or nil) 85-- @tparam[default=other] string context The callback context 86function module.add_leave_callback(cb, context) 87 context = context or "other" 88 callbacks.leave[context] = callbacks.leave[context] or {} 89 table.insert(callbacks.leave[context], cb) 90end 91 92--- Resize the drawable. 93-- 94-- Valid `args` are: 95-- 96-- * *enter_callback*: A function called before the `mousegrabber` starts. 97-- * *move_callback*: A function called when the mouse moves. 98-- * *leave_callback*: A function called before the `mousegrabber` is released. 99-- * *mode*: The resize mode. 100-- 101-- @function awful.mouse.resize 102-- @tparam client client A client. 103-- @tparam[default=mouse.resize] string context The resizing context. 104-- @tparam[opt={}] table args A set of `awful.placement` arguments. 105 106local function handler(_, client, context, args) --luacheck: no unused_args 107 args = args or {} 108 context = context or "mouse.resize" 109 110 local placement = args.placement 111 112 if type(placement) == "string" and aplace[placement] then 113 placement = aplace[placement] 114 end 115 116 -- Extend the table with the default arguments 117 args = setmetatable( 118 { 119 placement = placement or aplace.resize_to_mouse, 120 mode = args.mode or mode, 121 pretend = true, 122 }, 123 {__index = args or {}} 124 ) 125 126 local geo 127 128 for _, cb in ipairs(callbacks.enter[context] or {}) do 129 geo = cb(client, args) 130 131 if geo == false then 132 return false 133 end 134 end 135 136 if args.enter_callback then 137 geo = args.enter_callback(client, args) 138 139 if geo == false then 140 return false 141 end 142 end 143 144 geo = nil 145 146 -- Select the cursor 147 local tcontext = context:gsub('[.]', '_') 148 local corner = args.corner and ("_".. args.corner) or "" 149 150 local cursor = beautiful["cursor_"..tcontext] 151 or cursors[context..corner] 152 or cursors[context] 153 or "fleur" 154 155 -- Execute the placement function and use request::geometry 156 capi.mousegrabber.run(function (_mouse) 157 if not client.valid then return end 158 159 -- Resize everytime the mouse moves (default behavior) in live mode, 160 -- otherwise keep the current geometry 161 geo = setmetatable( 162 args.mode == "live" and args.placement(client, args) or client:geometry(), 163 {__index=args} 164 ) 165 166 -- Execute the move callbacks. This can be used to add features such as 167 -- snap or adding fancy graphical effects. 168 for _, cb in ipairs(callbacks.move[context] or {}) do 169 -- If something is returned, assume it is a modified geometry 170 geo = cb(client, geo, args) or geo 171 172 if geo == false then 173 return false 174 end 175 end 176 177 if args.move_callback then 178 geo = args.move_callback(client, geo, args) 179 180 if geo == false then 181 return false 182 end 183 end 184 185 -- In case it was modified 186 if geo then 187 setmetatable(geo, {__index=args}) 188 end 189 190 if args.mode == "live" then 191 -- Ask the resizing handler to resize the client 192 client:emit_signal(req, context, geo) 193 end 194 195 -- Quit when the button is released 196 for _,v in pairs(_mouse.buttons) do 197 if v then return true end 198 end 199 200 -- Only resize after the mouse is released, this avoids losing content 201 -- in resize sensitive apps such as XTerm or allows external modules 202 -- to implement custom resizing. 203 if args.mode == "after" then 204 -- Get the new geometry 205 geo = args.placement(client, args) 206 207 -- Ask the resizing handler to resize the client 208 client:emit_signal(req, context, geo) 209 end 210 211 geo = nil 212 213 for _, cb in ipairs(callbacks.leave[context] or {}) do 214 geo = cb(client, geo, args) 215 end 216 217 if args.leave_callback then 218 geo = args.leave_callback(client, geo, args) 219 end 220 221 if not geo then return false end 222 223 -- In case it was modified 224 setmetatable(geo,{__index=args}) 225 226 client:emit_signal(req, context, geo) 227 228 return false 229 end, cursor) 230end 231 232return setmetatable(module, {__call=handler}) 233 234-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 235