2016-04-22 07:59:02 +02:00
|
|
|
---------------------------------------------------------------------------
|
|
|
|
--- Mouse snapping related functions
|
|
|
|
--
|
|
|
|
-- @author Julien Danjou <julien@danjou.info>
|
|
|
|
-- @copyright 2008 Julien Danjou
|
2016-04-25 04:09:14 +02:00
|
|
|
-- @submodule mouse
|
2016-04-22 07:59:02 +02:00
|
|
|
---------------------------------------------------------------------------
|
|
|
|
|
2016-04-23 08:14:01 +02:00
|
|
|
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
|
2016-04-22 07:59:02 +02:00
|
|
|
|
|
|
|
local capi = {
|
|
|
|
root = root,
|
|
|
|
mouse = mouse,
|
|
|
|
screen = screen,
|
|
|
|
client = client,
|
|
|
|
mousegrabber = mousegrabber,
|
|
|
|
}
|
|
|
|
|
2016-04-22 08:16:59 +02:00
|
|
|
local module = {
|
|
|
|
default_distance = 8
|
|
|
|
}
|
2016-04-22 07:59:02 +02:00
|
|
|
|
2016-04-23 08:14:01 +02:00
|
|
|
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
|
|
|
|
|
2016-04-24 02:08:42 +02:00
|
|
|
local function build_placement(snap, axis)
|
|
|
|
return aplace.scale
|
|
|
|
+ aplace[snap]
|
|
|
|
+ (
|
|
|
|
axis and aplace["maximize_"..axis] or nil
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2016-04-22 09:26:43 +02:00
|
|
|
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"
|
2016-04-23 08:14:01 +02:00
|
|
|
elseif math.abs((sg.x + sg.width) - coords.x) <= snap then
|
2016-04-22 09:26:43 +02:00
|
|
|
h = "right"
|
|
|
|
end
|
|
|
|
|
|
|
|
if math.abs(coords.y) <= snap + sg.y and coords.y >= sg.y then
|
|
|
|
v = "top"
|
2016-04-23 08:14:01 +02:00
|
|
|
elseif math.abs((sg.y + sg.height) - coords.y) <= snap then
|
2016-04-22 09:26:43 +02:00
|
|
|
v = "bottom"
|
|
|
|
end
|
|
|
|
|
|
|
|
return v, h
|
|
|
|
end
|
|
|
|
|
2016-04-24 02:08:42 +02:00
|
|
|
local current_snap, current_axis = nil
|
2016-04-22 09:26:43 +02:00
|
|
|
|
|
|
|
local function detect_areasnap(c, distance)
|
2016-04-23 08:14:01 +02:00
|
|
|
local old_snap = current_snap
|
2016-04-22 09:26:43 +02:00
|
|
|
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
|
|
|
|
|
2016-04-24 02:08:42 +02:00
|
|
|
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
|
|
|
|
|
2016-04-23 08:14:01 +02:00
|
|
|
-- Show the expected geometry outline
|
2016-04-24 02:08:42 +02:00
|
|
|
show_placeholder(
|
|
|
|
current_snap and build_placement(current_snap, current_axis)(c, {
|
|
|
|
to_percent = 0.5,
|
|
|
|
honor_workarea = true,
|
|
|
|
pretend = true
|
|
|
|
}) or nil
|
|
|
|
)
|
2016-04-23 08:14:01 +02:00
|
|
|
|
2016-04-22 09:26:43 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
local function apply_areasnap(c, args)
|
|
|
|
if not current_snap then return end
|
|
|
|
|
|
|
|
-- Remove the move offset
|
|
|
|
args.offset = {}
|
|
|
|
|
2016-04-23 08:14:01 +02:00
|
|
|
placeholder_w.visible = false
|
|
|
|
|
2016-04-24 02:08:42 +02:00
|
|
|
return build_placement(current_snap, current_axis)(c,{
|
|
|
|
to_percent = 0.5,
|
|
|
|
honor_workarea = true,
|
|
|
|
})
|
2016-04-22 09:26:43 +02:00
|
|
|
end
|
|
|
|
|
2016-04-22 07:59:02 +02:00
|
|
|
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.
|
2016-04-25 04:09:14 +02:00
|
|
|
-- @function awful.mouse.snap
|
2016-04-22 07:59:02 +02:00
|
|
|
-- @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)
|
2016-04-22 08:16:59 +02:00
|
|
|
snap = snap or module.default_distance
|
2016-04-22 07:59:02 +02:00
|
|
|
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
|
|
|
|
|
2016-09-11 08:06:26 +02:00
|
|
|
geom, edge = snap_inside(geom, c.screen.geometry, snap)
|
|
|
|
geom = snap_inside(geom, c.screen.workarea, snap)
|
2016-04-22 07:59:02 +02:00
|
|
|
|
|
|
|
-- 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
|
Fix client snapping (#951)
First some reminder on how client geometries works (in X11, awesome just copied
that!):
- The position (x,y) defines where the border of the client begins
- This means that the content starts at (x+border_width,y+border_width)
- However, the size is the size of the client without border
- Thus, the client covers the rectangle from (x,y) to (x+2*bw,y+2*bw)
The client snapping code got this wrong. It only deals with rectangles and thus
for things to work as expected, the width/height have to be increased by two
times the border width. When snapping a client against other visible clients,
the geometry of the client to snap against wasn't calculated correctly.
This was apparently noticed at one point and worked around by decreasing the
position by two times the border width. While this is terribly wrong, it
actually makes things work correctly when snapping to the right or bottom edge
of a client, but breaks for the other edges.
Fix this by just calculating things correctly.
This is based on a patch from jk411.
Fixes: https://github.com/awesomeWM/awesome/issues/928
Signed-off-by: Uli Schlachter <psychon@znc.in>
2016-06-05 22:00:11 +02:00
|
|
|
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)
|
2016-04-22 07:59:02 +02:00
|
|
|
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
|
|
|
|
|
2016-04-22 09:04:28 +02:00
|
|
|
-- Enable edge snapping
|
|
|
|
resize.add_move_callback(function(c, geo, args)
|
2016-04-22 09:26:43 +02:00
|
|
|
-- Screen edge snapping (areosnap)
|
2016-04-28 03:16:20 +02:00
|
|
|
if (module.edge_enabled ~= false)
|
|
|
|
and args and (args.snap == nil or args.snap) then
|
2016-04-22 09:26:43 +02:00
|
|
|
detect_areasnap(c, 16)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Snapping between clients
|
2016-04-28 03:16:20 +02:00
|
|
|
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)
|
2016-04-22 09:04:28 +02:00
|
|
|
end
|
|
|
|
end, "mouse.move")
|
|
|
|
|
2016-04-22 09:26:43 +02:00
|
|
|
-- Apply the aerosnap
|
|
|
|
resize.add_leave_callback(function(c, _, args)
|
2016-04-28 03:16:20 +02:00
|
|
|
if module.edge_enabled == false then return end
|
2016-04-22 09:26:43 +02:00
|
|
|
return apply_areasnap(c, args)
|
|
|
|
end, "mouse.move")
|
2016-04-22 07:59:02 +02:00
|
|
|
|
2016-04-22 09:26:43 +02:00
|
|
|
return setmetatable(module, {__call = function(_, ...) return module.snap(...) end})
|