Merge pull request #844 from Elv13/geometry_overhaul_p2

Geometry overhaul part 2.5
This commit is contained in:
Emmanuel Lepage Vallée 2016-04-30 23:29:29 -04:00
commit dbd0931343
34 changed files with 2067 additions and 321 deletions

View File

@ -0,0 +1,6 @@
---------------------------------------------------------------------------
--- This module is deprecated, use `mouse`
-- ===============================
--
-- @module awful.mouse
---------------------------------------------------------------------------

View File

@ -35,6 +35,8 @@ new_type("function", "Functions")
new_type("property", "Object properties", false, "Type")
-- New type for signals
new_type("signal", "Signals", false, "Arguments")
-- New type for signals connections
new_type("signalhandler", "Request handlers", false, "Arguments")
-- Allow objects to define a set of beautiful properties affecting them
new_type("beautiful", "Theme variables", false, "Type")
-- Put deprecated methods in their own section
@ -67,6 +69,7 @@ file = {
'../docs/aliases/awful_client.lua',
'../docs/aliases/awful_screen.lua',
'../docs/aliases/awful_tag.lua',
'../docs/aliases/awful_mouse.lua',
exclude = {
-- exclude these modules, as they do not contain any written
-- documentation

253
docs/images/mouse.svg Normal file
View File

@ -0,0 +1,253 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="154.33519mm"
height="83.532181mm"
viewBox="0 0 546.85725 295.98017"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="mouse.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.4"
inkscape:cx="360.06759"
inkscape:cy="366.74179"
inkscape:document-units="pt"
inkscape:current-layer="g5476"
showgrid="false"
showguides="false"
inkscape:guide-bbox="true"
inkscape:window-width="1916"
inkscape:window-height="1021"
inkscape:window-x="0"
inkscape:window-y="16"
inkscape:window-maximized="1"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(738.15802,-608.17116)">
<g
id="g5476"
transform="translate(173.74631,0)">
<path
sodipodi:nodetypes="cccccccccc"
inkscape:connector-curvature="0"
id="path4136"
d="m -728.15879,608.42602 178.5142,0 c 9.38939,0.26627 10.95278,7.73823 10.83892,14.69669 l 0,230.46648 c 0.30959,23.52499 -24.37716,50.18749 -47.93254,50.01303 l -104.98806,0.29423 c -20.09841,0.0248 -45.84989,-25.43284 -46.17689,-51.01859 l 0.0204,-227.59279 c 0.18644,-8.43842 1.99483,-16.50279 9.72396,-16.85905 z"
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.50971645;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
sodipodi:nodetypes="cc"
inkscape:connector-curvature="0"
id="path4138"
d="m -733.20425,673.55031 189.76854,0"
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.87379962;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
sodipodi:nodetypes="cccccccccccc"
inkscape:connector-curvature="0"
id="path4745"
d="m -646.69902,611.50192 -79.00757,0 c -5.32157,0.16679 -6.94386,4.08162 -6.91732,11.98114 l 0,45.43998 85.97537,0 0,-7.07362 -5.78092,0 c -1.41482,0 -2.54544,-1.13579 -2.54098,-2.93674 l 0,-36.99862 c 0.0228,-1.71464 0.26476,-2.99382 2.39387,-2.99382 l 5.85448,0 z"
style="fill:#617fff;fill-opacity:0.49019608;fill-rule:evenodd;stroke:#617fff;stroke-width:1.63500068;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
style="fill:#617fff;fill-opacity:0.49019608;fill-rule:evenodd;stroke:#617fff;stroke-width:1.63500068;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m -630.47815,611.50192 79.00757,0 c 5.32158,0.16679 6.94386,4.08162 6.91732,11.98114 l 0,45.43998 -85.97537,0 0,-7.07362 5.78092,0 c 1.41482,0 2.54544,-1.13579 2.54098,-2.93674 l 0,-36.99862 c -0.0228,-1.71464 -0.26476,-2.99382 -2.39387,-2.99382 l -5.85448,0 z"
id="path4766"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccccccccc" />
<rect
ry="7.3555903"
rx="7.3555903"
y="620.00159"
x="-645.80945"
height="39.42598"
width="15.593853"
id="rect4768"
style="opacity:1;fill:#617fff;fill-opacity:0.49019608;stroke:#617fff;stroke-width:1.63500068;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m -644.91259,651.46192 -6.56599,0.12226 0,106.78495 6.87951,-0.023"
id="path8458"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:14.765769px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="-703.82648"
y="718.50195"
id="text8466"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan8468"
x="-703.82648"
y="718.50195">Button 1</tspan><tspan
id="tspan5409"
sodipodi:role="line"
x="-703.82648"
y="736.95917">(LMB)</tspan></text>
<text
sodipodi:linespacing="125%"
id="text5405"
y="717.49182"
x="-582.11389"
style="font-style:normal;font-weight:normal;font-size:14.765769px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
xml:space="preserve"><tspan
y="717.49182"
x="-582.11389"
id="tspan5407"
sodipodi:role="line">Button 2</tspan><tspan
id="tspan5411"
y="735.94904"
x="-582.11389"
sodipodi:role="line">(RMB)</tspan></text>
<path
sodipodi:nodetypes="cccc"
inkscape:connector-curvature="0"
id="path5413"
d="m -644.66005,640.0977 -12.12183,0.12226 0,139.86744 12.81415,-0.023"
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m -644.91259,627.47079 -17.42513,0.12226 0,171.18217 18.36999,0.0243"
id="path5415"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:14.765769px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="-594.89044"
y="785.26086"
id="text5417"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
x="-594.89044"
y="785.26086"
id="tspan5421">Click: Button 3</tspan></text>
<text
sodipodi:linespacing="125%"
id="text5425"
y="803.3548"
x="-592.46796"
style="font-style:normal;font-weight:normal;font-size:14.765769px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
xml:space="preserve"><tspan
id="tspan5427"
y="803.3548"
x="-592.46796"
sodipodi:role="line">Down: Button 5</tspan></text>
<text
sodipodi:linespacing="125%"
id="text5429"
y="763.70624"
x="-601.84076"
style="font-style:normal;font-weight:normal;font-size:14.765769px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
xml:space="preserve"><tspan
id="tspan5431"
y="763.70624"
x="-601.84076"
sodipodi:role="line">Up: Button 4</tspan></text>
<path
sodipodi:nodetypes="cc"
inkscape:connector-curvature="0"
id="path5433"
d="m -705.40715,646.22703 0,58.57067"
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m -582.19286,646.22703 0,58.57067"
id="path5435"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<circle
r="2.3214285"
cy="703.61224"
cx="-705.53571"
id="path5437"
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<circle
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="circle5439"
cx="-705.44647"
cy="646.91583"
r="2.3214285" />
<circle
r="2.3214285"
cy="646.02295"
cx="-582.23218"
id="circle5441"
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<circle
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="circle5443"
cx="-582.05359"
cy="704.41583"
r="2.3214285" />
<circle
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="circle5445"
cx="-643.66077"
cy="627.63007"
r="2.3214285" />
<circle
r="2.3214285"
cy="639.95148"
cx="-643.66077"
id="circle5447"
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<circle
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="circle5449"
cx="-643.66077"
cy="651.55859"
r="2.3214285" />
<circle
r="2.3214285"
cy="758.34442"
cx="-645.08929"
id="circle5451"
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<circle
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="circle5453"
cx="-645.08929"
cy="779.77301"
r="2.3214285" />
<circle
r="2.3214285"
cy="798.88013"
cx="-645.08929"
id="circle5455"
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -15,6 +15,7 @@ local math = math
local util = require("awful.util")
local aclient = require("awful.client")
local aplace = require("awful.placement")
local asuit = require("awful.layout.suit")
local ewmh = {}
@ -100,6 +101,7 @@ end
--
-- It is the default signal handler for `request::activate` on a `client`.
--
-- @signalhandler awful.ewmh.activate
-- @client c A client to use
-- @tparam string context The context where this signal was used.
-- @tparam[opt] table hints A table with additional hints:
@ -120,8 +122,11 @@ function ewmh.activate(c, context, hints) -- luacheck: no unused args
end
end
--- Tag a window with its requested tag
--- Tag a window with its requested tag.
--
-- It is the default signal handler for `request::tag` on a `client`.
--
-- @signalhandler awful.ewmh.tag
-- @client c A client to tag
-- @tag[opt] t A tag to use. If omitted, then the client is made sticky.
-- @tparam[opt={}] table hints Extra information
@ -156,10 +161,18 @@ local context_mapper = {
--
-- This is the default geometry request handler.
--
-- @signalhandler awful.ewmh.geometry
-- @tparam client c The client
-- @tparam string context The context
-- @tparam[opt={}] table hints The hints to pass to the handler
function ewmh.geometry(c, context, hints)
local layout = c.screen.selected_tag and c.screen.selected_tag.layout or nil
-- Setting the geometry wont work unless the client is floating.
if (not c.floating) and (not layout == asuit.floating) then
return
end
context = context or ""
local original_context = context

View File

@ -246,6 +246,28 @@ capi.client.connect_signal("list", function()
end
end)
--- Default handler for tiled clients request::geometry with the `mouse.move`
-- context.
-- @tparam client c The client
-- @tparam string context The context
-- @tparam table hints Additional hints
function layout.move_handler(c, context, hints) --luacheck: no unused args
-- Quit if it isn't a mouse.move on a tiled layout, that's handled elsewhere
if c.floating then return end
if context ~= "mouse.move" 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
local c_u_m = capi.mouse.current_client
if c_u_m and not c_u_m.floating then
if c_u_m ~= c then
c:swap(c_u_m)
end
end
end
capi.client.connect_signal("request::geometry", layout.move_handler)
return layout
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -0,0 +1,59 @@
---------------------------------------------------------------------------
--- 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
-- @release @AWESOME_VERSION@
-- @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 = capi.screen[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})

View File

@ -4,17 +4,15 @@
-- @author Julien Danjou &lt;julien@danjou.info&gt;
-- @copyright 2008 Julien Danjou
-- @release @AWESOME_VERSION@
-- @module awful.mouse
-- @module mouse
---------------------------------------------------------------------------
-- Grab environment we need
local layout = require("awful.layout")
local tag = require("awful.tag")
local aclient = require("awful.client")
local aplace = require("awful.placement")
local awibox = require("awful.wibox")
local util = require("awful.util")
local type = type
local math = math
local ipairs = ipairs
local capi =
{
@ -25,136 +23,63 @@ local capi =
mousegrabber = mousegrabber,
}
local mouse = {}
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()
local obj = capi.mouse.object_under_pointer()
if type(obj) == "client" then
return obj
end
end
util.deprecated("Use mouse.current_client instead of awful.mouse.client_under_pointer()")
--- Get the drawin object under the pointer.
-- @return The drawin object under the pointer, if one can be found.
function mouse.drawin_under_pointer()
local obj = capi.mouse.object_under_pointer()
if type(obj) == "drawin" then
return obj
end
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.
-- @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 mouse.client.snap(c, snap, x, y, fixed_x, fixed_y)
snap = snap or 8
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, capi.screen[c.screen].geometry, snap)
geom = snap_inside(geom, capi.screen[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
geom.x = geom.x - (2 * c.border_width)
geom.y = geom.y - (2 * c.border_width)
for _, snapper in ipairs(aclient.visible(c.screen)) do
if snapper ~= c then
geom = snap_outside(geom, snapper:geometry(), snap)
end
end
geom.width = geom.width - (2 * c.border_width)
geom.height = geom.height - (2 * c.border_width)
geom.x = geom.x + (2 * c.border_width)
geom.y = geom.y + (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
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 An optional callback function, that will be called
-- when moving the client has been finished. The client
-- that has been moved will be passed to that function.
function mouse.client.move(c, snap, finished_cb)
-- @param finished_cb Deprecated, do not use
function mouse.client.move(c, snap, finished_cb) --luacheck: no unused args
if finished_cb then
util.deprecated("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
@ -165,98 +90,39 @@ function mouse.client.move(c, snap, finished_cb)
return
end
local orig = c:geometry()
local m_c = capi.mouse.coords()
local dist_x = m_c.x - orig.x
local dist_y = m_c.y - orig.y
-- Only allow moving in the non-maximized directions
local fixed_x = c.maximized_horizontal
local fixed_y = c.maximized_vertical
-- Compute the offset
local coords = capi.mouse.coords()
local geo = aplace.centered(capi.mouse,{parent=c, pretend=true})
capi.mousegrabber.run(function (_mouse)
if not c.valid then return false end
local offset = {
x = geo.x - coords.x,
y = geo.y - coords.y,
}
for _, v in ipairs(_mouse.buttons) do
if v then
local lay = layout.get(c.screen)
if lay == layout.suit.floating or c.floating then
local x = _mouse.x - dist_x
local y = _mouse.y - dist_y
c:geometry(mouse.client.snap(c, snap, x, y, fixed_x, fixed_y))
elseif lay ~= layout.suit.magnifier then
-- Only move the client to the mouse
-- screen if the target screen is not
-- floating.
-- Otherwise, we move if via geometry.
if layout.get(capi.mouse.screen) == layout.suit.floating then
local x = _mouse.x - dist_x
local y = _mouse.y - dist_y
c:geometry(mouse.client.snap(c, snap, x, y, fixed_x, fixed_y))
else
c.screen = capi.mouse.screen
end
if layout.get(c.screen) ~= layout.suit.floating then
local c_u_m = mouse.client_under_pointer()
if c_u_m and not c_u_m.floating then
if c_u_m ~= c then
c:swap(c_u_m)
end
end
end
end
return true
end
end
if finished_cb then
finished_cb(c)
end
return false
end, "fleur")
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
--- 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)
capi.mousegrabber.run(function (_mouse)
if not c.valid then return false end
util.deprecated("Use awful.mouse.snap.drag_to_tag_enabled = true instead "..
"of awful.mouse.client.dragtotag.border(c). It will now be enabled.")
local button_down = false
for _, v in ipairs(_mouse.buttons) do
if v then button_down = true end
end
local wa = capi.screen[c.screen].workarea
if _mouse.x >= wa.x + wa.width then
capi.mouse.coords({ x = wa.x + wa.width - 1 })
elseif _mouse.x <= wa.x then
capi.mouse.coords({ x = wa.x + 1 })
end
if not button_down then
local tags = c.screen.tags
local t = c.screen.selected_tag
local idx
for i, v in ipairs(tags) do
if v == t then
idx = i
end
end
if _mouse.x > wa.x + wa.width - 10 then
local newtag = tags[util.cycle(#tags, idx + 1)]
c:move_to_tag(newtag)
tag.viewnext()
elseif _mouse.x < wa.x + 10 then
local newtag = tags[util.cycle(#tags, idx - 1)]
c:move_to_tag(newtag)
tag.viewprev()
end
return false
end
return true
end, "fleur")
-- Enable drag to border
mouse.snap.drag_to_tag_enabled = true
return mouse.client.move(c)
end
--- Move the wibox under the cursor
--- Move the wibox under the cursor.
-- @function awful.mouse.wibox.move
--@param 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()
@ -297,55 +163,41 @@ function mouse.wibox.move(w)
end
--- Get a client corner coordinates.
-- @param c The client to get corner from, focused one by default.
-- @param corner The corner to use: auto, top_left, top_right, bottom_left,
-- bottom_right. Default is auto, and auto find the nearest corner.
-- @return Actual used corner and x and y 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.deprecated(
"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 g = c:geometry()
local ngeo = nil
if not corner or corner == "auto" then
local m_c = capi.mouse.coords()
if math.abs(g.y - m_c.y) < math.abs(g.y + g.height - m_c.y) then
if math.abs(g.x - m_c.x) < math.abs(g.x + g.width - m_c.x) then
corner = "top_left"
else
corner = "top_right"
end
else
if math.abs(g.x - m_c.x) < math.abs(g.x + g.width - m_c.x) then
corner = "bottom_left"
else
corner = "bottom_right"
end
end
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
local x, y
if corner == "top_right" then
x = g.x + g.width
y = g.y
elseif corner == "top_left" then
x = g.x
y = g.y
elseif corner == "bottom_left" then
x = g.x
y = g.y + g.height
else
x = g.x + g.width
y = g.y + g.height
end
return corner, x, y
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.
-- @param corner The corner to grab on resize. Auto detected by default.
function mouse.client.resize(c, corner)
-- @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
@ -357,19 +209,162 @@ function mouse.client.resize(c, corner)
return
end
local lay = layout.get(c.screen)
local corner2, x, y = mouse.client.corner(c, corner)
-- 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})
end
if lay == layout.suit.floating or c.floating then
return layout.suit.floating.mouse_resize_handler(c, corner2, x, y)
elseif lay.mouse_resize_handler then
return lay.mouse_resize_handler(c, corner2, x, y)
mouse.resize(c, "mouse.resize", args or {include_sides=true})
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 lay = c.screen.selected_tag.layout
if lay == layout.suit.floating or c.floating then
local offset = hints and hints.offset or {}
if type(offset) == "number" then
offset = {
x = offset,
y = offset,
width = offset,
height = offset,
}
end
c:geometry {
x = hints.x + (offset.x or 0 ),
y = hints.y + (offset.y or 0 ),
width = hints.width + (offset.width or 0 ),
height = hints.height + (offset.height or 0 ),
}
elseif 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
function mouse.object.set_current_client() 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" then
return obj
end
end
function mouse.object.set_current_wibox() 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
mouse.object["set_is_".. b .."_mouse_button_pressed"] = function() 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)
else
props[key] = value
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.
--
--@DOC_awful_mouse_coords_EXAMPLE@
--
-- @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

199
lib/awful/mouse/resize.lua Normal file
View File

@ -0,0 +1,199 @@
---------------------------------------------------------------------------
--- 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
-- @release @AWESOME_VERSION@
-- @submodule 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.
--
-- @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 a initialization callback.
-- This callback will be executed before the mouse grabbing start
-- @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
-- 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})

269
lib/awful/mouse/snap.lua Normal file
View File

@ -0,0 +1,269 @@
---------------------------------------------------------------------------
--- Mouse snapping related functions
--
-- @author Julien Danjou &lt;julien@danjou.info&gt;
-- @copyright 2008 Julien Danjou
-- @release @AWESOME_VERSION@
-- @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, capi.screen[c.screen].geometry, snap)
geom = snap_inside(geom, capi.screen[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
geom.x = geom.x - (2 * c.border_width)
geom.y = geom.y - (2 * c.border_width)
for _, snapper in ipairs(aclient.visible(c.screen)) do
if snapper ~= c then
geom = snap_outside(geom, snapper:geometry(), snap)
end
end
geom.width = geom.width - (2 * c.border_width)
geom.height = geom.height - (2 * c.border_width)
geom.x = geom.x + (2 * c.border_width)
geom.y = geom.y + (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})

View File

@ -9,14 +9,21 @@
-- * Turn each function into an API with various common customization parameters.
-- * Re-use the same functions for the `mouse`, `client`s, `screen`s and `wibox`es
--
--
--
-- <h3>Compositing</h3>
--
-- It is possible to compose placement function using the `+` or `*` operator:
--
-- local f = (awful.placement.right + awful.placement.left)
-- f(client.focus)
--@DOC_awful_placement_compose_EXAMPLE@
--
-- ### Common arguments
--@DOC_awful_placement_compose2_EXAMPLE@
--
-- <h3>Common arguments</h3>
--
-- **pretend** (*boolean*):
--
-- Do not apply the new geometry. This is useful if only the return values is
-- necessary.
--
-- **honor_workarea** (*boolean*):
--
@ -44,6 +51,10 @@
--
-- **attach** (*boolean*):
--
-- **offset** (*table or number*):
--
-- The offset(s) to apply to the new geometry.
--
-- **store_geometry** (*boolean*):
--
-- Keep a single history of each type of placement. It can be restored using
@ -77,6 +88,7 @@ local capi =
local client = require("awful.client")
local layout = require("awful.layout")
local a_screen = require("awful.screen")
local util = require("awful.util")
local dpi = require("beautiful").xresources.apply_dpi
local function get_screen(s)
@ -85,20 +97,89 @@ end
local wrap_client = nil
local function compose(w1, w2)
return wrap_client(function(...)
w1(...)
w2(...)
return --It make no sense to keep a return value
end)
--- Allow multiple placement functions to be daisy chained.
-- This also allow the functions to be aware they are being chained and act
-- upon the previous nodes results to avoid unnecessary processing or deduce
-- extra paramaters/arguments.
local function compose(...)
local queue = {}
local nodes = {...}
-- Allow placement.foo + (var == 42 and placement.bar)
if not nodes[2] then
return nodes[1]
end
-- nodes[1] == self, nodes[2] == other
for _, w in ipairs(nodes) do
-- Build an execution queue
if w.context and w.context == "compose" then
for _, elem in ipairs(w.queue or {}) do
table.insert(queue, elem)
end
else
table.insert(queue, w)
end
end
local ret = wrap_client(function(d, args, ...)
local rets = {}
local last_geo = nil
-- As some functions may have to take into account results from
-- previously execued ones, add the `composition_results` hint.
args = setmetatable({composition_results=rets}, {__index=args})
-- Only apply the geometry once, not once per chain node, to do this,
-- Force the "pretend" argument and restore the original value for
-- the last node.
local pretend_real = args.pretend
args.pretend = true
for k, f in ipairs(queue) do
if k == #queue then
args.pretend = pretend_real or false
end
local r = {f(d, args, ...)}
last_geo = r[1] or last_geo
args.override_geometry = last_geo
-- Keep the return value, store one per context
if f.context then
-- When 2 composition queue are executed, merge the return values
if f.context == "compose" then
for k2,v in pairs(r) do
rets[k2] = v
end
else
rets[f.context] = r
end
end
end
return last_geo, rets
end, "compose")
ret.queue = queue
return ret
end
wrap_client = function(f)
return setmetatable({is_placement=true}, {
__call = function(_,...) return f(...) end,
__add = compose, -- Composition is usually defined as +
__mul = compose -- Make sense if you think of the functions as matrices
})
wrap_client = function(f, context)
return setmetatable(
{
is_placement= true,
context = context,
},
{
__call = function(_,...) return f(...) end,
__add = compose, -- Composition is usually defined as +
__mul = compose -- Make sense if you think of the functions as matrices
}
)
end
local placement_private = {}
@ -111,7 +192,7 @@ local placement_private = {}
local placement = setmetatable({}, {
__index = placement_private,
__newindex = function(_, k, f)
placement_private[k] = wrap_client(f)
placement_private[k] = wrap_client(f, k)
end
})
@ -143,6 +224,21 @@ local align_map = {
-- Store function -> keys
local reverse_align_map = {}
-- Some parameters to correctly compute the final size
local resize_to_point_map = {
-- Corners
top_left = {p1= nil , p2={1,1}, x_only=false, y_only=false, align="bottom_right"},
top_right = {p1={0,1} , p2= nil , x_only=false, y_only=false, align="bottom_left" },
bottom_left = {p1= nil , p2={1,0}, x_only=false, y_only=false, align="top_right" },
bottom_right = {p1={0,0} , p2= nil , x_only=false, y_only=false, align="top_left" },
-- Sides
left = {p1= nil , p2={1,1}, x_only=true , y_only=false, align="top_right" },
right = {p1={0,0} , p2= nil , x_only=true , y_only=false, align="top_left" },
top = {p1= nil , p2={1,1}, x_only=false, y_only=true , align="bottom_left" },
bottom = {p1={0,0} , p2= nil , x_only=false, y_only=true , align="top_left" },
}
--- Add a context to the arguments.
-- This function extend the argument table. The context is used by some
-- internal helper methods. If there already is a context, it has priority and
@ -163,17 +259,49 @@ local function store_geometry(d, reqtype)
data[d][reqtype].screen = d.screen
end
--- Apply some modifications before applying the new geometry.
-- @tparam table new_geo The new geometry
-- @tparam table args The common arguments
-- @treturn table|nil The new geometry
local function fix_new_geometry(new_geo, args)
if args.pretend or not new_geo then return nil end
local offset = args.offset or {}
if type(offset) == "number" then
offset = {
x = offset,
y = offset,
width = offset,
height = offset,
}
end
return {
x = new_geo.x and (new_geo.x + (offset.x or 0)),
y = new_geo.y and (new_geo.y + (offset.y or 0)),
width = new_geo.width and (new_geo.width + (offset.width or 0)),
height = new_geo.height and (new_geo.height + (offset.height or 0)),
}
end
--- Get the area covered by a drawin.
-- @param d The drawin
-- @tparam[opt=nil] table new_geo A new geometry
-- @tparam[opt=false] boolean ignore_border_width Ignore the border
-- @tparam table args the method arguments
-- @treturn The drawin's area.
local function area_common(d, new_geo, ignore_border_width)
local function area_common(d, new_geo, ignore_border_width, args)
-- The C side expect no arguments, nil isn't valid
local geometry = new_geo and d:geometry(new_geo) or d:geometry()
local border = ignore_border_width and 0 or d.border_width or 0
geometry.x = geometry.x
geometry.y = geometry.y
-- When using the placement composition along with the "pretend"
-- option, it is necessary to keep a "virtual" geometry.
if args and args.override_geometry then
geometry = util.table.clone(args.override_geometry)
end
geometry.width = geometry.width + 2 * border
geometry.height = geometry.height + 2 * border
return geometry
@ -196,14 +324,17 @@ local function geometry_common(obj, args, new_geo, ignore_border_width)
-- It's a mouse
if obj.coords then
local coords = new_geo and obj.coords(new_geo) or obj.coords()
local coords = fix_new_geometry(new_geo, args)
and obj.coords(new_geo) or obj.coords()
return {x=coords.x, y=coords.y, width=0, height=0}
elseif obj.geometry then
local geo = obj.geometry
-- It is either a drawable or something that implement its API
if type(geo) == "function" then
local dgeo = area_common(obj, new_geo, ignore_border_width)
local dgeo = area_common(
obj, fix_new_geometry(new_geo, args), ignore_border_width, args
)
-- Apply the margins
if args.margins then
@ -434,6 +565,24 @@ local function area_remove(areas, elem)
return areas
end
-- Convert 2 points into a rectangle
local function rect_from_points(p1x, p1y, p2x, p2y)
return {
x = p1x,
y = p1y,
width = p2x - p1x,
height = p2y - p1y,
}
end
-- Convert a rectangle and matrix info into a point
local function rect_to_point(rect, corner_i, corner_j)
return {
x = rect.x + corner_i * math.floor(rect.width ),
y = rect.y + corner_j * math.floor(rect.height),
}
end
--- Move a drawable to the closest corner of the parent geometry (such as the
-- screen).
--
@ -445,6 +594,7 @@ end
-- @tparam[opt=client.focus] drawable d A drawable (like `client`, `mouse`
-- or `wibox`)
-- @tparam[opt={}] table args The arguments
-- @treturn table The new geometry
-- @treturn string The corner name
function placement.closest_corner(d, args)
args = add_context(args, "closest_corner")
@ -460,8 +610,10 @@ function placement.closest_corner(d, args)
-- Use the product of 3 to get the closest point in a NxN matrix
local function f(_n, mat)
n = _n
corner_i = -math.ceil( ( (sgeo.x - pos.x) * n) / sgeo.width )
corner_j = -math.ceil( ( (sgeo.y - pos.y) * n) / sgeo.height )
-- The +1 is required to avoid a rounding error when
-- pos.x == sgeo.x+sgeo.width
corner_i = -math.ceil( ( (sgeo.x - pos.x) * n) / (sgeo.width + 1))
corner_j = -math.ceil( ( (sgeo.y - pos.y) * n) / (sgeo.height + 1))
return mat[corner_j + 1][corner_i + 1]
end
@ -476,9 +628,9 @@ function placement.closest_corner(d, args)
-- Transpose the corner back to the original size
local new_args = setmetatable({position = corner}, {__index=args})
placement_private.align(d, new_args)
local ngeo = placement_private.align(d, new_args)
return corner
return ngeo, corner
end
--- Place the client so no part of it will be outside the screen (workarea).
@ -520,6 +672,7 @@ end
--- Place the client where there's place available with minimum overlap.
--@DOC_awful_placement_no_overlap_EXAMPLE@
-- @param c The client.
-- @treturn table The new geometry
function placement.no_overlap(c)
c = c or capi.client.focus
local geometry = area_common(c)
@ -578,14 +731,24 @@ end
--- Place the client under the mouse.
--@DOC_awful_placement_under_mouse_EXAMPLE@
-- @param c The client.
-- @return The new client geometry.
function placement.under_mouse(c)
c = c or capi.client.focus
local c_geometry = area_common(c)
-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`)
-- @tparam[opt={}] table args Other arguments
-- @treturn table The new geometry
function placement.under_mouse(d, args)
args = add_context(args, "under_mouse")
d = d or capi.client.focus
local m_coords = capi.mouse.coords()
return c:geometry({ x = m_coords.x - c_geometry.width / 2,
y = m_coords.y - c_geometry.height / 2 })
local ngeo = geometry_common(d, args)
ngeo.x = m_coords.x - ngeo.width / 2
ngeo.y = m_coords.y - ngeo.height / 2
local bw = d.border_width or 0
ngeo.width = ngeo.width - 2*bw
ngeo.height = ngeo.height - 2*bw
return ngeo
end
--- Place the client next to the mouse.
@ -595,7 +758,7 @@ end
--@DOC_awful_placement_next_to_mouse_EXAMPLE@
-- @client[opt=focused] c The client.
-- @tparam[opt=apply_dpi(5)] integer offset The offset from the mouse position.
-- @return The new client geometry.
-- @treturn table The new geometry
function placement.next_to_mouse(c, offset)
c = c or capi.client.focus
offset = offset or dpi(5)
@ -627,6 +790,78 @@ function placement.next_to_mouse(c, offset)
return c:geometry({ x = x, y = y })
end
--- Resize the drawable to the cursor.
--
-- Valid args:
--
-- * *axis*: The axis (vertical or horizontal). If none is
-- specified, then the drawable will be resized on both axis.
--
--@DOC_awful_placement_resize_to_mouse_EXAMPLE@
-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`)
-- @tparam[opt={}] table args Other arguments
-- @treturn table The new geometry
function placement.resize_to_mouse(d, args)
d = d or capi.client.focus
args = add_context(args, "resize_to_mouse")
local coords = capi.mouse.coords()
local ngeo = geometry_common(d, args)
local h_only = args.axis == "horizontal"
local v_only = args.axis == "vertical"
-- To support both growing and shrinking the drawable, it is necessary
-- to decide to use either "north or south" and "east or west" directions.
-- Otherwise, the result will always be 1x1
local _, closest_corner = placement.closest_corner(capi.mouse, {
parent = d,
pretend = true,
include_sides = args.include_sides or false,
})
-- Given "include_sides" wasn't set, it will always return a name
-- with the 2 axis. If only one axis is needed, adjust the result
if h_only then
closest_corner = closest_corner:match("left") or closest_corner:match("right")
elseif v_only then
closest_corner = closest_corner:match("top") or closest_corner:match("bottom")
end
-- Use p0 (mouse), p1 and p2 to create a rectangle
local pts = resize_to_point_map[closest_corner]
local p1 = pts.p1 and rect_to_point(ngeo, pts.p1[1], pts.p1[2]) or coords
local p2 = pts.p2 and rect_to_point(ngeo, pts.p2[1], pts.p2[2]) or coords
-- Create top_left and bottom_right points, convert to rectangle
ngeo = rect_from_points(
pts.y_only and ngeo.x or math.min(p1.x, p2.x),
pts.x_only and ngeo.y or math.min(p1.y, p2.y),
pts.y_only and ngeo.x + ngeo.width or math.max(p2.x, p1.x),
pts.x_only and ngeo.y + ngeo.height or math.max(p2.y, p1.y)
)
local bw = d.border_width or 0
for _, a in ipairs {"width", "height"} do
ngeo[a] = ngeo[a] - 2*bw
end
-- Now, correct the geometry by the given size_hints offset
if d.apply_size_hints then
local w, h = d:apply_size_hints(
ngeo.width,
ngeo.height
)
local offset = align_map[pts.align](w, h, ngeo.width, ngeo.height)
ngeo.x = ngeo.x - offset.x
ngeo.y = ngeo.y - offset.y
end
geometry_common(d, args, ngeo)
return ngeo
end
--- Move the drawable (client or wibox) `d` to a screen position or side.
--
-- Supported args.positions are:
@ -646,6 +881,7 @@ end
--@DOC_awful_placement_align_EXAMPLE@
-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`)
-- @tparam[opt={}] table args Other arguments
-- @treturn table The new geometry
function placement.align(d, args)
args = add_context(args, "align")
d = d or capi.client.focus
@ -663,14 +899,18 @@ function placement.align(d, args)
dgeo.height
)
geometry_common(d, args, {
local ngeo = {
x = (pos.x and math.ceil(sgeo.x + pos.x) or dgeo.x) ,
y = (pos.y and math.ceil(sgeo.y + pos.y) or dgeo.y) ,
width = math.ceil(dgeo.width ) - 2*bw,
height = math.ceil(dgeo.height ) - 2*bw,
})
}
geometry_common(d, args, ngeo)
attach(d, placement[args.position], args)
return ngeo
end
-- Add the alias functions
@ -678,7 +918,7 @@ for k in pairs(align_map) do
placement[k] = function(d, args)
args = add_context(args, k)
args.position = k
placement_private.align(d, args)
return placement_private.align(d, args)
end
reverse_align_map[placement[k]] = k
end
@ -716,6 +956,7 @@ end
--@DOC_awful_placement_stretch_EXAMPLE@
-- @tparam[opt=client.focus] drawable d A drawable (like `client` or `wibox`)
-- @tparam[opt={}] table args The arguments
-- @treturn table The new geometry
function placement.stretch(d, args)
args = add_context(args, "stretch")
@ -757,6 +998,8 @@ function placement.stretch(d, args)
geometry_common(d, args, ngeo)
attach(d, placement["stretch_"..args.direction], args)
return ngeo
end
-- Add the alias functions
@ -764,7 +1007,7 @@ for _,v in ipairs {"left", "right", "up", "down"} do
placement["stretch_"..v] = function(d, args)
args = add_context(args, "stretch_"..v)
args.direction = v
placement_private.stretch(d, args)
return placement_private.stretch(d, args)
end
end
@ -785,6 +1028,7 @@ end
--@DOC_awful_placement_maximize_EXAMPLE@
-- @tparam[opt=client.focus] drawable d A drawable (like `client` or `wibox`)
-- @tparam[opt={}] table args The arguments
-- @treturn table The new geometry
function placement.maximize(d, args)
args = add_context(args, "maximize")
d = d or capi.client.focus
@ -808,6 +1052,8 @@ function placement.maximize(d, args)
geometry_common(d, args, ngeo)
attach(d, placement.maximize, args)
return ngeo
end
-- Add the alias functions
@ -815,10 +1061,66 @@ for _, v in ipairs {"vertically", "horizontally"} do
placement["maximize_"..v] = function(d2, args)
args = add_context(args, "maximize_"..v)
args.axis = v
placement_private.maximize(d2, args)
return placement_private.maximize(d2, args)
end
end
--- Scale the drawable by either a relative or absolute percent.
--
-- Valid args:
--
-- **to_percent** : A number between 0 and 1. It represent a percent related to
-- the parent geometry.
-- **by_percent** : A number between 0 and 1. It represent a percent related to
-- the current size.
-- **direction**: Nothing or "left", "right", "up", "down".
--
-- @tparam[opt=client.focus] drawable d A drawable (like `client` or `wibox`)
-- @tparam[opt={}] table args The arguments
-- @treturn table The new geometry
function placement.scale(d, args)
args = add_context(args, "scale_to_percent")
d = d or capi.client.focus
local to_percent = args.to_percent
local by_percent = args.by_percent
local percent = to_percent or by_percent
local direction = args.direction
local sgeo = get_parent_geometry(d, args)
local ngeo = geometry_common(d, args, nil)
local old_area = {width = ngeo.width, height = ngeo.height}
if (not direction) or direction == "left" or direction == "right" then
ngeo.width = (to_percent and sgeo or ngeo).width*percent
if direction == "left" then
ngeo.x = ngeo.x - (ngeo.width - old_area.width)
end
end
if (not direction) or direction == "up" or direction == "down" then
ngeo.height = (to_percent and sgeo or ngeo).height*percent
if direction == "up" then
ngeo.y = ngeo.y - (ngeo.height - old_area.height)
end
end
local bw = d.border_width or 0
ngeo.width = ngeo.width - 2*bw
ngeo.height = ngeo.height - 2*bw
geometry_common(d, args, ngeo)
attach(d, placement.maximize, args)
return ngeo
end
---@DOC_awful_placement_maximize_vertically_EXAMPLE@
---@DOC_awful_placement_maximize_horizontally_EXAMPLE@

134
mouse.c
View File

@ -19,7 +19,39 @@
*
*/
/** awesome mouse API
/** awesome mouse API.
*
* The mouse buttons are represented as index. The common ones are:
*
* ![Client geometry](../images/mouse.svg)
*
* It is possible to be notified of mouse events by connecting to various
* `client`, `widget`s and `wibox` signals:
*
* * `mouse::enter`
* * `mouse::leave`
* * `mouse::press`
* * `mouse::release`
* * `mouse::move`
*
* It is also possible to add generic mouse button callbacks for `client`s,
* `wiboxe`s and the `root` window. Those are set in the default `rc.lua` as such:
*
* **root**:
*
* root.buttons(awful.util.table.join(
* awful.button({ }, 3, function () mymainmenu:toggle() end),
* awful.button({ }, 4, awful.tag.viewnext),
* awful.button({ }, 5, awful.tag.viewprev)
* ))
*
* **client**:
*
* clientbuttons = awful.util.table.join(
* awful.button({ }, 1, function (c) client.focus = c; c:raise() end),
* awful.button({ modkey }, 1, awful.mouse.client.move),
* awful.button({ modkey }, 3, awful.mouse.client.resize)
* )
*
* See also `mousegrabber`
*
@ -33,27 +65,19 @@
#include "math.h"
#include "common/util.h"
#include "common/xutil.h"
#include "common/luaclass.h"
#include "globalconf.h"
#include "objects/client.h"
#include "objects/drawin.h"
#include "objects/screen.h"
/** Mouse library.
*
* @table mouse
*/
static int miss_index_handler = LUA_REFNIL;
static int miss_newindex_handler = LUA_REFNIL;
/**
* The `screen` under the cursor
* @field screen
*/
/** A table with X and Y coordinates.
* @field x X coordinate.
* @field y Y coordinate.
* @field buttons Table containing the status of buttons, e.g. field [1] is true
* when button 1 is pressed.
* @table coords_table
* @property screen
* @param screen
*/
/** Get the pointer position.
@ -119,6 +143,39 @@ mouse_warp_pointer(xcb_window_t window, int16_t x, int16_t y)
0, 0, 0, 0, x, y);
}
/**
* Allow the a Lua handler to be implemented for custom properties and
* functions.
* \param L A lua state
* \param handler A function on the LUA_REGISTRYINDEX
*/
static int
luaA_mouse_call_handler(lua_State *L, int handler)
{
int nargs = lua_gettop(L);
/* Push error handling function and move it before args */
lua_pushcfunction(L, luaA_dofunction_error);
lua_insert(L, - nargs - 1);
int error_func_pos = 1;
/* push function and move it before args */
lua_rawgeti(L, LUA_REGISTRYINDEX, handler);
lua_insert(L, - nargs - 1);
if(lua_pcall(L, nargs, LUA_MULTRET, error_func_pos))
{
warn("%s", lua_tostring(L, -1));
/* Remove error function and error string */
lua_pop(L, 2);
return 0;
}
/* Remove error function */
lua_remove(L, error_func_pos);
return lua_gettop(L);
}
/** Mouse library.
* \param L The Lua VM state.
* \return The number of elements pushed on stack.
@ -133,8 +190,13 @@ luaA_mouse_index(lua_State *L)
int16_t mouse_x, mouse_y;
/* attr is not "screen"?! */
if (A_STRNEQ(attr, "screen"))
return luaA_default_index(L);
if (A_STRNEQ(attr, "screen")) {
if (miss_index_handler != LUA_REFNIL) {
return luaA_mouse_call_handler(L, miss_index_handler);
}
else
return luaA_default_index(L);
}
if (!mouse_query_pointer_root(&mouse_x, &mouse_y, NULL, NULL))
{
@ -162,8 +224,14 @@ luaA_mouse_newindex(lua_State *L)
const char *attr = luaL_checkstring(L, 2);
screen_t *screen;
if (A_STRNEQ(attr, "screen"))
return luaA_default_newindex(L);
if (A_STRNEQ(attr, "screen")) {
/* Call the lua mouse property handler */
if (miss_newindex_handler != LUA_REFNIL) {
return luaA_mouse_call_handler(L, miss_newindex_handler);
}
else
return luaA_default_newindex(L);
}
screen = luaA_checkscreen(L, 3);
mouse_warp_pointer(globalconf.screen->root, screen->geometry.x, screen->geometry.y);
@ -201,15 +269,7 @@ luaA_mouse_pushstatus(lua_State *L, int x, int y, uint16_t mask)
return 1;
}
/** Get or set the mouse coords.
*
* @tparam coords_table coords_table None or a table with x and y keys as mouse
* coordinates.
* @tparam boolean silent Disable mouse::enter or mouse::leave events that
* could be triggered by the pointer when moving.
* @treturn coords_table A table with mouse coordinates.
* @function coords
*/
/* documented in lib/awful/mouse/init.lua */
static int
luaA_mouse_coords(lua_State *L)
{
@ -271,12 +331,32 @@ luaA_mouse_object_under_pointer(lua_State *L)
return 0;
}
/**
* Add a custom property handler (getter).
*/
static int
luaA_mouse_set_index_miss_handler(lua_State *L)
{
return luaA_registerfct(L, 1, &miss_index_handler);
}
/**
* Add a custom property handler (setter).
*/
static int
luaA_mouse_set_newindex_miss_handler(lua_State *L)
{
return luaA_registerfct(L, 1, &miss_newindex_handler);
}
const struct luaL_Reg awesome_mouse_methods[] =
{
{ "__index", luaA_mouse_index },
{ "__newindex", luaA_mouse_newindex },
{ "coords", luaA_mouse_coords },
{ "object_under_pointer", luaA_mouse_object_under_pointer },
{ "set_index_miss_handler", luaA_mouse_set_index_miss_handler},
{ "set_newindex_miss_handler", luaA_mouse_set_newindex_miss_handler},
{ NULL, NULL }
};
const struct luaL_Reg awesome_mouse_meta[] =

View File

@ -46,7 +46,6 @@ function(escape_string variable content escaped_content line_prefix)
if(variable MATCHES "--DOC_HIDE_ALL")
return()
endif()
string(REGEX REPLACE "\n" ";" var_lines "${variable}")
set(tmp_output ${content})
@ -201,10 +200,17 @@ function(run_test test_path namespace template escaped_content)
# Only add it if there is something to display.
if(NOT ${TEST_CODE} STREQUAL "\n--")
# Do not use the @usage tag, use 4 spaces
file(READ ${test_path} tmp_content)
if(NOT tmp_content MATCHES "--DOC_NO_USAGE")
set(DOC_PREFIX "@usage")
endif()
escape_string(
" @usage"
" ${DOC_PREFIX}"
"${TEST_DOC_CONTENT}" TEST_DOC_CONTENT ""
)
set(TEST_DOC_CONTENT "${TEST_DOC_CONTENT}${TEST_CODE}")
endif()

View File

@ -0,0 +1,13 @@
screen[1]._resize {x = 175, width = 128, height = 96} --DOC_HIDE
mouse.coords {x=175+60,y=60} --DOC_HIDE
-- Get the position
print(mouse.coords().x)
-- Change the position
mouse.coords {
x = 185,
y = 10
}
mouse.push_history() --DOC_HIDE

View File

@ -1,6 +1,7 @@
-- Align a client to the bottom of the parent area. --DOC_HEADER
-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`) --DOC_HEADER
-- @tparam[opt={}] table args Other arguments") --DOC_HEADER
-- @treturn table The new geometry --DOC_HEADER
-- @name bottom --DOC_HEADER
-- @class function --DOC_HEADER

View File

@ -1,6 +1,7 @@
-- Align a client to the bottom left of the parent area. --DOC_HEADER
-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`) --DOC_HEADER
-- @tparam[opt={}] table args Other arguments") --DOC_HEADER
-- @treturn table The new geometry --DOC_HEADER
-- @name bottom_left --DOC_HEADER
-- @class function --DOC_HEADER

View File

@ -1,6 +1,7 @@
-- Align a client to the bottom right of the parent area. --DOC_HEADER
-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`) --DOC_HEADER
-- @tparam[opt={}] table args Other arguments") --DOC_HEADER
-- @treturn table The new geometry --DOC_HEADER
-- @name bottom_right --DOC_HEADER
-- @class function --DOC_HEADER

View File

@ -1,6 +1,7 @@
-- Align a client to the horizontal center left of the parent area. --DOC_HEADER
-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`) --DOC_HEADER
-- @tparam[opt={}] table args Other arguments") --DOC_HEADER
-- @treturn table The new geometry --DOC_HEADER
-- @name center_horizontal --DOC_HEADER
-- @class function --DOC_HEADER
screen[1]._resize {width = 128, height = 96} --DOC_HIDE

View File

@ -1,6 +1,7 @@
-- Align a client to the center of the parent area. --DOC_HEADER
-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`) --DOC_HEADER
-- @tparam[opt={}] table args Other arguments") --DOC_HEADER
-- @treturn table The new geometry --DOC_HEADER
-- @name centered --DOC_HEADER
-- @class function --DOC_HEADER

View File

@ -55,7 +55,7 @@ assert(mouse.coords().x == c.x and mouse.coords().y == c.y+c.height+2*bw) --DOC_
-- It is possible to emulate the mouse API to get the closest corner of
-- random area
local corner = awful.placement.closest_corner(
local _, corner = awful.placement.closest_corner(
{coords=function() return {x = 100, y=100} end},
{include_sides = true, bounding_rect = {x=0, y=0, width=200, height=200}}
)

View File

@ -1,9 +1,11 @@
screen[1]._resize {width = 128, height = 96} --DOC_HIDE
screen[1]._resize {x = 175, width = 128, height = 96} --DOC_NO_USAGE --DOC_HIDE
local awful = {placement = require("awful.placement")} --DOC_HIDE
local c = client.gen_fake {x = 45, y = 35, width=40, height=30} --DOC_HIDE
local c = client.gen_fake {x = 220, y = 35, width=40, height=30} --DOC_HIDE
local f = (awful.placement.right + awful.placement.left)
f(client.focus)
-- "right" will be replaced by "left"
local f = (awful.placement.right + awful.placement.left)
f(client.focus)
assert(c.x == 0 and c.y==screen[1].geometry.height/2-30/2-c.border_width--DOC_HIDE
local sg = screen[1].geometry--DOC_HIDE
assert(c.x == sg.x and c.y==sg.height/2-30/2-c.border_width--DOC_HIDE
and c.width==40 and c.height==30)--DOC_HIDE

View File

@ -0,0 +1,19 @@
screen[1]._resize {x = 175, width = 128, height = 96} --DOC_NO_USAGE --DOC_HIDE
local awful = {placement = require("awful.placement")} --DOC_HIDE
local c = client.gen_fake {x = 220, y = 35, width=40, height=30} --DOC_HIDE
-- Simulate Windows 7 "edge snap" (also called aero snap) feature
local axis = "vertically"
local f = awful.placement.scale
+ awful.placement.left
+ (axis and awful.placement["maximize_"..axis] or nil)
local geo = f(client.focus, {honor_workarea=true, to_percent = 0.5})
local wa = screen[1].workarea--DOC_HIDE
assert(c.x == wa.x and geo.x == wa.x)--DOC_HIDE
assert(c.y == wa.y) --DOC_HIDE
assert(c.width == wa.width/2 - 2*c.border_width)--DOC_HIDE
assert(c.height == wa.height - 2*c.border_width)--DOC_HIDE

View File

@ -1,6 +1,7 @@
-- Align a client to the left of the parent area. --DOC_HEADER
-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`) --DOC_HEADER
-- @tparam[opt={}] table args Other arguments") --DOC_HEADER
-- @treturn table The new geometry --DOC_HEADER
-- @name left --DOC_HEADER
-- @class function --DOC_HEADER

View File

@ -0,0 +1,74 @@
--DOC_HIDE_ALL
local awful = {placement = require("awful.placement")}
local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
screen._setup_grid(64, 48, {4, 4, 4, 4}, {workarea_sides=0})
local function test_touch_mouse(c)
local coords = mouse.coords()
return c:geometry().x == coords.x or c:geometry().y == coords.y
or c:geometry().x+c:geometry().width+2*c.border_width == coords.x
or c:geometry().y+c:geometry().height+2*c.border_width == coords.y
end
for s=1, 8 do
local scr = screen[s]
local x, y = scr.geometry.x, scr.geometry.y
local c = client.gen_fake{x = x+22, y = y+16, width=20, height=15, screen=scr}
assert(client.get()[s] == c)
end
for s=9, 16 do
local scr = screen[s]
local x, y = scr.geometry.x, scr.geometry.y
local c = client.gen_fake{x = x+10, y = y+10, width=44, height=28, screen=scr}
assert(client.get()[s] == c)
end
local function move_corsor(s, x, y)
local sg = screen[s].geometry
mouse.coords {x=sg.x+x,y=sg.y+y}
end
local all_coords_out = {
top_left = {10, 10},
top = {32, 10},
top_right = {60, 10},
right = {60, 20},
bottom_right = {60, 40},
bottom = {32, 40},
bottom_left = {10, 40},
left = {10, 29},
}
local all_coords_in = {
top_left = {20, 18},
top = {32, 18},
top_right = {44, 18},
right = {44, 24},
bottom_right = {44, 34},
bottom = {32, 34},
bottom_left = {20, 34},
left = {32, 24},
}
-- Top left
local s = 1
for k, v in pairs(all_coords_out) do
move_corsor(s, unpack(v))
assert(client.get()[s].screen == screen[s])
awful.placement.resize_to_mouse(client.get()[s], {include_sides=true})
mouse.push_history()
assert(test_touch_mouse(client.get()[s]), k)
s = s + 1
end
for k, v in pairs(all_coords_in) do
move_corsor(s, unpack(v))
assert(client.get()[s].screen == screen[s])
awful.placement.resize_to_mouse(client.get()[s], {include_sides=true})
mouse.push_history()
assert(test_touch_mouse(client.get()[s]), k)
s = s + 1
end

View File

@ -1,6 +1,7 @@
-- Align a client to the right of the parent area. --DOC_HEADER
-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`) --DOC_HEADER
-- @tparam[opt={}] table args Other arguments") --DOC_HEADER
-- @treturn table The new geometry --DOC_HEADER
-- @name right --DOC_HEADER
-- @class function --DOC_HEADER

View File

@ -1,6 +1,7 @@
-- Stretch the drawable to the bottom of the parent area. --DOC_HEADER
-- @tparam drawable d A drawable (like `client` or `wibox`) --DOC_HEADER
-- @tparam[opt={}] table args Other arguments --DOC_HEADER
-- @treturn table The new geometry --DOC_HEADER
-- @name stretch_down --DOC_HEADER
-- @class function --DOC_HEADER

View File

@ -1,6 +1,7 @@
-- Stretch the drawable to the left of the parent area. --DOC_HEADER
-- @tparam drawable d A drawable (like `client` or `wibox`) --DOC_HEADER
-- @tparam[opt={}] table args Other arguments --DOC_HEADER
-- @treturn table The new geometry --DOC_HEADER
-- @name stretch_left --DOC_HEADER
-- @class function --DOC_HEADER

View File

@ -1,6 +1,7 @@
-- Stretch the drawable to the right of the parent area. --DOC_HEADER
-- @tparam drawable d A drawable (like `client` or `wibox`) --DOC_HEADER
-- @tparam[opt={}] table args Other arguments --DOC_HEADER
-- @treturn table The new geometry --DOC_HEADER
-- @name stretch_right --DOC_HEADER
-- @class function --DOC_HEADER

View File

@ -1,6 +1,7 @@
-- Stretch the drawable to the top of the parent area. --DOC_HEADER
-- @tparam drawable d A drawable (like `client` or `wibox`) --DOC_HEADER
-- @tparam[opt={}] table args Other arguments --DOC_HEADER
-- @treturn table The new geometry --DOC_HEADER
-- @name stretch_up --DOC_HEADER
-- @class function --DOC_HEADER

View File

@ -1,6 +1,7 @@
-- Align a client to the top of the parent area. --DOC_HEADER
-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`) --DOC_HEADER
-- @tparam[opt={}] table args Other arguments") --DOC_HEADER
-- @treturn table The new geometry --DOC_HEADER
-- @name top --DOC_HEADER
-- @class function --DOC_HEADER

View File

@ -1,6 +1,7 @@
-- Align a client to the top left of the parent area. --DOC_HEADER
-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`) --DOC_HEADER
-- @tparam[opt={}] table args Other arguments") --DOC_HEADER
-- @treturn table The new geometry --DOC_HEADER
-- @name top_left --DOC_HEADER
-- @class function --DOC_HEADER

View File

@ -1,6 +1,7 @@
-- Align a client to the top right of the parent area. --DOC_HEADER
-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`) --DOC_HEADER
-- @tparam[opt={}] table args Other arguments") --DOC_HEADER
-- @treturn table The new geometry --DOC_HEADER
-- @name top_right --DOC_HEADER
-- @class function --DOC_HEADER

View File

@ -166,6 +166,9 @@ client:_add_signal("property::fullscreen")
client:_add_signal("property::border_width")
client:_add_signal("property::hidden")
client:_add_signal("property::screen")
client:_add_signal("request::tag")
client:_add_signal("request::geometry")
client:_add_signal("request::activate")
client:_add_signal("raised")
client:_add_signal("lowered")
client:_add_signal("list")

View File

@ -1,5 +1,6 @@
-- Test set_{,new}index_miss_handler
local mouse = mouse
local class = tag
local obj = class({})
local handler = require("gears.object.properties")
@ -30,4 +31,8 @@ assert(not obj.key)
obj.key = 1337
assert(obj.key == 1337)
-- The the custom mouse handler
mouse.foo = "bar"
assert(mouse.foo == "bar")
require("_runner").run_steps({ function() return true end })

409
tests/test-resize.lua Normal file
View File

@ -0,0 +1,409 @@
local test_client = require("_client")
local placement = require("awful.placement")
local amouse = require("awful.mouse")
local steps = {}
table.insert(steps, function(count)
if count == 1 then -- Setup.
test_client("foobar", "foobar")
elseif #client.get() > 0 then
client.get()[1] : geometry {
x = 200,
y = 200,
width = 300,
height = 300,
}
return true
end
end)
table.insert(steps, function()
-- The mousegrabber expect a button to be pressed.
root.fake_input("button_press",1)
local c = client.get()[1]
-- Just in case there is an accidental delayed geometry callback
assert(c:geometry().x == 200)
assert(c:geometry().y == 200)
assert(c:geometry().width == 300)
assert(c:geometry().height == 300)
mouse.coords {x = 500+2*c.border_width, y= 500+2*c.border_width}
local corner = amouse.client.resize(c)
assert(corner == "bottom_right")
return true
end)
-- The geometry should remain the same, as the cursor is placed at the end of
-- the geometry.
table.insert(steps, function()
local c = client.get()[1]
assert(c:geometry().x == 200)
assert(c:geometry().y == 200)
assert(c:geometry().width == 300)
assert(c:geometry().height == 300)
mouse.coords {x = 600+2*c.border_width, y= 600+2*c.border_width}
return true
end)
-- Grow the client by 100px
table.insert(steps, function()
local c = client.get()[1]
assert(c:geometry().x == 200)
assert(c:geometry().y == 200)
assert(c:geometry().width == 400)
assert(c:geometry().height == 400)
mouse.coords {x = 400+2*c.border_width, y= 400+2*c.border_width}
return true
end)
-- Shirnk the client by 200px
table.insert(steps, function()
local c = client.get()[1]
assert(c:geometry().x == 200)
assert(c:geometry().y == 200)
assert(c:geometry().width == 200)
assert(c:geometry().height == 200)
mouse.coords {x = 100, y= 100}
return true
end)
-- Grow the client by 100px from the top left
table.insert(steps, function()
local c = client.get()[1]
assert(c:geometry().x == 100)
assert(c:geometry().y == 100)
assert(c:geometry().width == 300)
assert(c:geometry().height == 300)
mouse.coords {x = 300, y= 200}
return true
end)
-- Shirnk the client by 100px from the top right
table.insert(steps, function()
local c = client.get()[1]
assert(c:geometry().x == 100)
assert(c:geometry().y == 200)
-- assert(c:geometry().width == 200-2*c.border_width) --FIXME off by border width...
-- assert(c:geometry().height == 200-2*c.border_width) --FIXME off by border width...
mouse.coords {x = 300, y= 200}
return true
end)
-- Stop the resize
table.insert(steps, function()
root.fake_input("button_release",1)
-- if not mousegrabber.isrunning then --FIXME it should work, but doesn't
-- return true
-- end
mousegrabber.stop()
return true
end)
-- Setup for move testing
table.insert(steps, function()
assert(not mousegrabber.isrunning())
local c = client.get()[1]
c:geometry {
width = 200,
height = 200,
}
placement.bottom_right(c)
mouse.coords {x = c.screen.geometry.width -150,
y = c.screen.geometry.height-150}
return true
end)
-- Start the move mouse grabber
table.insert(steps, function()
local c = client.get()[1]
-- The resize is over, it should not move the client anymore
assert(c:geometry().x == c.screen.geometry.width - 200 - 2*c.border_width)
assert(c:geometry().y == c.screen.geometry.height - 200 - 2*c.border_width)
assert(c:geometry().width == 200)
assert(c:geometry().height == 200)
assert(c.valid)
root.fake_input("button_press",1)
-- Begin the move
amouse.client.move(c)
-- Make sure nothing unwanted happen by accident
assert(c:geometry().x == c.screen.geometry.width - 200 - 2*c.border_width)
assert(c:geometry().y == c.screen.geometry.height - 200 - 2*c.border_width)
assert(c:geometry().width == 200)
assert(c:geometry().height == 200)
-- The cursor should not have moved
assert(mouse.coords().x == c.screen.geometry.width - 150)
assert(mouse.coords().y == c.screen.geometry.height - 150)
mouse.coords {x = 50 + 2*c.border_width, y= 50 + 2*c.border_width}
assert(mousegrabber.isrunning())
return true
end)
-- The client should now be in the top left
table.insert(steps, function()
local c = client.get()[1]
assert(c:geometry().x == 0)
assert(c:geometry().y == 0)
assert(c:geometry().width == 200)
assert(c:geometry().height == 200)
-- Move to the bottom left
mouse.coords {
x = 50 + 2*c.border_width,
y = c.screen.geometry.height - 200
}
return true
end)
-- Ensure the move was correct, the snap to the top part of the screen
table.insert(steps, function()
local c = client.get()[1]
assert(c:geometry().x == 0)
assert(c:geometry().y == c.screen.geometry.height - 250 - 2*c.border_width)
assert(c:geometry().width == 200)
assert(c:geometry().height == 200)
-- Should trigger the top snap
mouse.coords {x = 600, y= 0}
-- The snap is only applied on release
root.fake_input("button_release",1)
return true
end)
-- The client should now fill the top half of the screen
table.insert(steps, function()
local c = client.get()[1]
assert(c:geometry().x == c.screen.workarea.x )
assert(c:geometry().y == c.screen.workarea.y )
assert(c:geometry().width == c.screen.workarea.width - 2*c.border_width )
assert(c:geometry().height == math.ceil(c.screen.workarea.height/2 - 2*c.border_width))
-- Snap to the top right
root.fake_input("button_press",1)
amouse.client.move(c)
placement.top_right(mouse, {honor_workarea=false})
root.fake_input("button_release",1)
return true
end)
-- The client should now fill the top right corner of the screen
table.insert(steps, function()
local c = client.get()[1]
assert(c:geometry().x == c.screen.workarea.x+c.screen.workarea.width/2 )
assert(c:geometry().y == c.screen.workarea.y )
assert(c:geometry().width == math.ceil(c.screen.workarea.width/2 - 2*c.border_width) )
assert(c:geometry().height == math.ceil(c.screen.workarea.height/2 - 2*c.border_width))
-- Snap to the top right
root.fake_input("button_press",1)
amouse.client.move(c)
placement.right(mouse, {honor_workarea=false})
root.fake_input("button_release",1)
return true
end)
-- The client should now fill the top right half of the screen
table.insert(steps, function()
local c = client.get()[1]
assert(c:geometry().x == c.screen.workarea.x+c.screen.workarea.width/2 )
assert(c:geometry().y == c.screen.workarea.y )
assert(c:geometry().width == math.ceil(c.screen.workarea.width/2 - 2*c.border_width))
assert(c:geometry().height == c.screen.workarea.height - 2*c.border_width )
-- Snap to the top right
root.fake_input("button_press",1)
amouse.client.move(c)
placement.bottom(mouse, {honor_workarea=false})
root.fake_input("button_release",1)
return true
end)
-- The client should now fill the bottom half of the screen
table.insert(steps, function()
local c = client.get()[1]
assert(c:geometry().x == c.screen.workarea.x )
assert(c:geometry().y == c.screen.workarea.y+c.screen.workarea.height/2 )
assert(c:geometry().width == c.screen.workarea.width - 2*c.border_width )
assert(c:geometry().height == math.ceil(c.screen.workarea.height/2 - 2*c.border_width))
-- Snap to the top right
root.fake_input("button_press",1)
amouse.client.move(c)
placement.bottom_left(mouse, {honor_workarea=false})
root.fake_input("button_release",1)
return true
end)
-- The client should now fill the bottom left corner of the screen
table.insert(steps, function()
local c = client.get()[1]
assert(c:geometry().x == c.screen.workarea.x )
assert(c:geometry().y == c.screen.workarea.y+c.screen.workarea.height/2 )
assert(c:geometry().width == math.ceil(c.screen.workarea.width/2 - 2*c.border_width) )
assert(c:geometry().height == math.ceil(c.screen.workarea.height/2 - 2*c.border_width))
-- Snap to the top right
root.fake_input("button_press",1)
amouse.client.move(c)
placement.left(mouse, {honor_workarea=false})
root.fake_input("button_release",1)
return true
end)
-- The client should now fill the left half of the screen
table.insert(steps, function()
local c = client.get()[1]
assert(c:geometry().x == c.screen.workarea.x )
assert(c:geometry().y == c.screen.workarea.y )
assert(c:geometry().width == math.ceil(c.screen.workarea.width/2 - 2*c.border_width) )
assert(c:geometry().height == c.screen.workarea.height - 2*c.border_width )
-- Snap to the top right
root.fake_input("button_press",1)
amouse.client.move(c)
placement.top_left(mouse, {honor_workarea=false})
root.fake_input("button_release",1)
return true
end)
local cur_tag = nil
-- The client should now fill the top left corner of the screen
table.insert(steps, function()
local c = client.get()[1]
assert(c:geometry().x == c.screen.workarea.x )
assert(c:geometry().y == c.screen.workarea.y )
assert(c:geometry().width == math.ceil(c.screen.workarea.width/2 - 2*c.border_width) )
assert(c:geometry().height == math.ceil(c.screen.workarea.height/2 - 2*c.border_width))
-- Change the mode to test drag_to_tag
amouse.drag_to_tag.enabled = true
amouse.snap.edge_enabled = false
cur_tag = c.first_tag
root.fake_input("button_press",1)
amouse.client.move(c)
placement.right(mouse, {honor_workarea=false})
root.fake_input("button_release",1)
return true
end)
-- The tag should have changed
table.insert(steps, function()
local c = client.get()[1]
assert(c.first_tag ~= cur_tag )
assert(c.first_tag.index == cur_tag.index + 1)
-- Move it back
root.fake_input("button_press",1)
amouse.client.move(c)
placement.left(mouse, {honor_workarea=false})
root.fake_input("button_release",1)
return true
end)
-- The tag should now be the same as before
table.insert(steps, function()
local c = client.get()[1]
assert(c.first_tag == cur_tag)
assert(c.first_tag.index == 1)
-- Wrap
root.fake_input("button_press",1)
amouse.client.move(c)
placement.left(mouse, {honor_workarea=false})
root.fake_input("button_release",1)
return true
end)
-- The tag should now be the last
table.insert(steps, function()
local c = client.get()[1]
assert(c.first_tag.index == #c.screen.tags)
-- Wrap back
root.fake_input("button_press",1)
amouse.client.move(c)
placement.right(mouse, {honor_workarea=false})
root.fake_input("button_release",1)
return true
end)
-- The tag should now original one again
table.insert(steps, function()
local c = client.get()[1]
assert(c.first_tag == cur_tag)
return true
end)
require("_runner").run_steps(steps)