summaryrefslogtreecommitdiff
path: root/lib/awful/mouse
diff options
context:
space:
mode:
authorache <ache@ache.one>2017-03-13 23:17:19 +0100
committerache <ache@ache.one>2017-03-13 23:17:19 +0100
commit22d656903563f75678f3634964731ccf93355dfd (patch)
treee3cb6279d95c9764093072d5e946566ea6533799 /lib/awful/mouse
Init commit
Diffstat (limited to 'lib/awful/mouse')
-rw-r--r--lib/awful/mouse/drag_to_tag.lua58
-rw-r--r--lib/awful/mouse/init.lua437
-rw-r--r--lib/awful/mouse/resize.lua229
-rw-r--r--lib/awful/mouse/snap.lua266
4 files changed, 990 insertions, 0 deletions
diff --git a/lib/awful/mouse/drag_to_tag.lua b/lib/awful/mouse/drag_to_tag.lua
new file mode 100644
index 0000000..141456b
--- /dev/null
+++ b/lib/awful/mouse/drag_to_tag.lua
@@ -0,0 +1,58 @@
+---------------------------------------------------------------------------
+--- When the the mouse reach the end of the screen, then switch tag instead
+-- of screens.
+--
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2008 Julien Danjou
+-- @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 = 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
new file mode 100644
index 0000000..03f7e89
--- /dev/null
+++ b/lib/awful/mouse/init.lua
@@ -0,0 +1,437 @@
+---------------------------------------------------------------------------
+--- Mouse module for awful
+--
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2008 Julien Danjou
+-- @module mouse
+---------------------------------------------------------------------------
+
+-- Grab environment we need
+local layout = require("awful.layout")
+local aplace = require("awful.placement")
+local util = require("awful.util")
+local type = type
+local ipairs = ipairs
+local capi =
+{
+ root = root,
+ mouse = mouse,
+ screen = screen,
+ client = client,
+ mousegrabber = mousegrabber,
+}
+
+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()
+ util.deprecate("Use mouse.current_client instead of awful.mouse.client_under_pointer()")
+
+ 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 Deprecated, do not use
+function mouse.client.move(c, snap, finished_cb) --luacheck: no unused args
+ if finished_cb then
+ util.deprecate("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
+ or c.fullscreen
+ or c.type == "desktop"
+ or c.type == "splash"
+ or c.type == "dock" then
+ return
+ end
+
+ -- Compute the offset
+ local coords = capi.mouse.coords()
+ local geo = aplace.centered(capi.mouse,{parent=c, pretend=true})
+
+ local offset = {
+ x = geo.x - coords.x,
+ y = geo.y - coords.y,
+ }
+
+ 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.
+-- @deprecated awful.mouse.client.dragtotag.border
+-- @param c The client to move
+function mouse.client.dragtotag.border(c)
+ util.deprecate("Use awful.mouse.snap.drag_to_tag_enabled = true instead "..
+ "of awful.mouse.client.dragtotag.border(c). It will now be enabled.")
+
+ -- Enable drag to border
+ mouse.snap.drag_to_tag_enabled = true
+
+ return mouse.client.move(c)
+end
+
+--- Move the wibox under the cursor.
+-- @function awful.mouse.wibox.move
+--@tparam wibox 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()
+ if not w then return end
+
+ if not w
+ or w.type == "desktop"
+ or w.type == "splash"
+ or w.type == "dock" then
+ return
+ end
+
+ -- Compute the offset
+ local coords = capi.mouse.coords()
+ local geo = aplace.centered(capi.mouse,{parent=w, pretend=true})
+
+ local offset = {
+ x = geo.x - coords.x,
+ y = geo.y - coords.y,
+ }
+
+ mouse.resize(w, "mouse.move", {
+ placement = aplace.under_mouse,
+ offset = offset
+ })
+end
+
+--- Get a client corner 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.deprecate(
+ "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 ngeo = nil
+
+ 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
+
+ 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.
+-- @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
+
+ if c.fullscreen
+ or c.type == "desktop"
+ or c.type == "splash"
+ or c.type == "dock" then
+ return
+ end
+
+ -- Set some default arguments
+ local new_args = setmetatable(
+ {
+ include_sides = (not args) or args.include_sides ~= false
+ },
+ {
+ __index = args or {}
+ }
+ )
+
+ -- 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,
+ include_sides = new_args.include_sides ~= false,
+ })
+ end
+
+ new_args.corner = corner
+
+ mouse.resize(c, "mouse.resize", new_args)
+
+ 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 t = c.screen.selected_tag
+ local lay = t and t.layout or nil
+
+ if (lay and lay == layout.suit.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
+
+-- 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
+
+--- 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" and obj.get_wibox then
+ return obj:get_wibox()
+ end
+end
+
+--- Get the widgets currently under the mouse cursor.
+--
+-- @property current_widgets
+-- @tparam nil|table list The widget list
+-- @treturn table The list of widgets.The first element is the biggest
+-- container while the last is the topmost widget. The table contains *x*, *y*,
+-- *width*, *height* and *widget*.
+-- @see wibox.find_widgets
+
+function mouse.object.get_current_widgets()
+ local w = mouse.object.get_current_wibox()
+ if w then
+ local geo, coords = w:geometry(), capi.mouse:coords()
+
+ local list = w:find_widgets(coords.x - geo.x, coords.y - geo.y)
+
+ local ret = {}
+
+ for k, v in ipairs(list) do
+ ret[k] = v.widget
+ end
+
+ return ret, list
+ end
+end
+
+--- Get the topmost widget currently under the mouse cursor.
+-- @property current_widget
+-- @tparam widget|nil widget The widget
+-- @treturn ?widget The widget
+-- @see wibox.find_widgets
+-- @see current_widget_geometry
+
+function mouse.object.get_current_widget()
+ local wdgs, geos = mouse.object.get_current_widgets()
+
+ if wdgs then
+ return wdgs[#wdgs], geos[#geos]
+ end
+end
+
+--- Get the current widget geometry.
+-- @property current_widget_geometry
+-- @tparam ?table The geometry.
+-- @see current_widget
+
+function mouse.object.get_current_widget_geometry()
+ local _, ret = mouse.object.get_current_widget()
+
+ return ret
+end
+
+--- Get the current widget geometries.
+-- @property current_widget_geometries
+-- @tparam ?table A list of geometry tables.
+-- @see current_widgets
+
+function mouse.object.get_current_widget_geometries()
+ local _, ret = mouse.object.get_current_widgets()
+
+ return ret
+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
+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)
+ elseif not mouse.object["get_"..key] then
+ props[key] = value
+ else
+ -- If there is a getter, but no setter, then the property is read-only
+ error("Cannot set '" .. tostring(key) .. " because it is read-only")
+ 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.
+--
+--
+--
+--![Usage example](../images/AUTOGEN_awful_mouse_coords.svg)
+--
+--**Usage example output**:
+--
+-- 235
+--
+--
+-- @usage
+-- -- Get the position
+--print(mouse.coords().x)
+-- -- Change the position
+--mouse.coords {
+-- x = 185,
+-- y = 10
+--}
+--
+-- @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 0000000..edd278d
--- /dev/null
+++ b/lib/awful/mouse/resize.lua
@@ -0,0 +1,229 @@
+---------------------------------------------------------------------------
+--- An extandable mouse resizing handler.
+--
+-- This module offer a resizing and moving mechanism for drawable such as
+-- clients and wiboxes.
+--
+-- @author Emmanuel Lepage Vallee &lt;elv1313@gmail.com&gt;
+-- @copyright 2016 Emmanuel Lepage Vallee
+-- @submodule mouse
+---------------------------------------------------------------------------
+
+local aplace = require("awful.placement")
+local capi = {mousegrabber = mousegrabber}
+local beautiful = require("beautiful")
+
+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 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 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` 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
+
+ -- 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 (_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, cursor)
+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 0000000..048a679
--- /dev/null
+++ b/lib/awful/mouse/snap.lua
@@ -0,0 +1,266 @@
+---------------------------------------------------------------------------
+--- Mouse snapping related functions
+--
+-- @author Julien Danjou &lt;julien@danjou.info&gt;
+-- @copyright 2008 Julien Danjou
+-- @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, c.screen.geometry, snap)
+ geom = snap_inside(geom, 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
+
+ for _, snapper in ipairs(aclient.visible(c.screen)) do
+ if snapper ~= c then
+ local snapper_geom = snapper:geometry()
+ snapper_geom.width = snapper_geom.width + (2 * snapper.border_width)
+ snapper_geom.height = snapper_geom.height + (2 * snapper.border_width)
+ geom = snap_outside(geom, snapper_geom, snap)
+ end
+ end
+
+ geom.width = geom.width - (2 * c.border_width)
+ geom.height = geom.height - (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})