awesome/lib/awful/mouse.lua.in

592 lines
23 KiB
Lua

---------------------------------------------------------------------------
-- @author Julien Danjou <julien@danjou.info>
-- @copyright 2008 Julien Danjou
-- @release @AWESOME_VERSION@
---------------------------------------------------------------------------
-- Grab environment we need
local layout = require("awful.layout")
local tag = require("awful.tag")
local hooks = require("awful.hooks")
local aclient = require("awful.client")
local widget = require("awful.widget")
local util = require("awful.util")
local type = type
local math = math
local ipairs = ipairs
local capi =
{
mouse = mouse,
screen = screen,
client = client,
mousegrabber = mousegrabber,
}
--- Mouse module for awful
module("awful.mouse")
client = {}
wibox = {}
--- Get the client object under the pointer.
-- @return The client object under the pointer, if one can be found.
function client_under_pointer()
local obj = capi.mouse.object_under_pointer()
if type(obj) == "client" then
return obj
end
end
--- Get the wibox object under the pointer.
-- @return The wibox object under the pointer, if one can be found.
function wibox_under_pointer()
local obj = capi.mouse.object_under_pointer()
if type(obj) == "wibox" then
return obj
end
end
--- Get the widget under the pointer.
-- @return The widget object under the pointer, if it can be found.
function widget_under_pointer()
local obj, obj2 = capi.mouse.object_under_pointer()
if type(obj2) == "widget" then
return obj2
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 client.snap(c, snap, x, y, fixed_x, fixed_y)
local snap = snap or 8
local c = c or client.focus
local cur_geom = c:geometry()
local geom = c:geometry()
local edge = "none"
local edge2 = "none"
geom.x = x or geom.x
geom.y = y or geom.y
geom, edge = snap_inside(geom, capi.screen[c.screen].geometry, snap)
geom, edge2 = 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 aclient.dockable.get(c) then
local struts = c:struts()
struts['left'] = 0
struts['right'] = 0
struts['top'] = 0
struts['bottom'] = 0
if edge2 ~= "none" and aclient.floating.get(c) then
if edge2 == "left" or edge2 == "right" then
struts[edge2] = cur_geom.width
elseif edge2 == "top" or edge2 == "bottom" then
struts[edge2] = cur_geom.height
end
end
c:struts(struts)
end
for k, snapper in ipairs(aclient.visible(c.screen)) do
if snapper ~= c then
geom = snap_outside(geom, snapper:geometry(), snap)
end
end
-- 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
--- Move a client.
-- @param c The client to move, or the focused one if nil.
-- @param snap The pixel to snap clients.
function client.move(c, snap)
local c = c or capi.client.focus
if not c
or c.fullscreen
or c.type == "desktop"
or c.type == "splash"
or c.type == "dock" then
return
end
c:raise()
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
capi.mousegrabber.run(function (mouse)
for k, v in ipairs(mouse.buttons) do
if v then
local lay = layout.get(c.screen)
if lay == layout.suit.floating or aclient.floating.get(c) then
local x = mouse.x - dist_x
local y = mouse.y - dist_y
c:geometry(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(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 = client_under_pointer()
if c_u_m and not aclient.floating.get(c_u_m) then
if c_u_m ~= c then
c:swap(c_u_m)
end
end
end
end
return true
end
end
return false
end, "fleur")
end
client.dragtotag = { }
--- Move a client to a tag by drag'n'dropping it over a taglist widget
-- @param c The client to move
function client.dragtotag.widget(c)
capi.mousegrabber.run(function (mouse)
local button_down = false
for _, v in ipairs(mouse.buttons) do
if v then button_down = true end
end
if not button_down then
local w = widget_under_pointer()
if w and widget.taglist.gettag(w) then
local t = widget.taglist.gettag(w)
if t.screen ~= c.screen then
aclient.movetoscreen(c, t.screen)
end
aclient.movetotag(t, c)
end
return false
end
return true
end, "fleur")
end
--- Move a client to a tag by dragging it onto the left / right side of the screen
-- @param c The client to move
function client.dragtotag.border(c)
capi.mousegrabber.run(function (mouse)
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 = capi.screen[c.screen]:tags()
local t = tag.selected()
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)]
aclient.movetotag(newtag, c)
tag.viewnext()
elseif mouse.x < wa.x + 10 then
local newtag = tags[util.cycle(#tags, idx - 1)]
aclient.movetotag(newtag, c)
tag.viewprev()
end
return false
end
return true
end, "fleur")
end
--- Move the wibox under the cursor
--@param w The wibox to move, or none to use that under the pointer
function wibox.move(w)
if not w then w = wibox_under_pointer() end
if not w then return end
local offset = {
x = w:geometry()["x"] - capi.mouse.coords()["x"],
y = w:geometry()["y"] - capi.mouse.coords()["y"]
}
capi.mousegrabber.run(function (mouse)
local button_down = false
if w.position == "floating" then
w:geometry({
x = capi.mouse.coords()["x"] + offset["x"],
y = capi.mouse.coords()["y"] + offset["y"],
})
else
local wa = capi.screen[capi.mouse.screen].workarea
if capi.mouse.coords()["y"] > wa.y + wa.height - 10 then
w.position = "bottom"
elseif capi.mouse.coords()["y"] < wa.y + 10 then
w.position = "top"
elseif capi.mouse.coords()["x"] > wa.x + wa.width - 10 then
w.position = "right"
elseif capi.mouse.coords()["x"] < wa.x + 10 then
w.position = "left"
end
w.screen = capi.mouse.screen
end
for k, v in ipairs(mouse.buttons) do
if v then button_down = true end
end
if not button_down then
return false
end
return true
end, "fleur")
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.
function client.corner(c, corner)
local c = c or capi.client.focus
if not c then return end
local g = c:geometry()
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
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
end
local function client_resize_magnifier(c, corner)
local corner, x, y = client.corner(c, corner)
capi.mouse.coords({ x = x, y = y })
local wa = capi.screen[c.screen].workarea
local center_x = wa.x + wa.width / 2
local center_y = wa.y + wa.height / 2
local maxdist_pow = (wa.width^2 + wa.height^2) / 4
capi.mousegrabber.run(function (mouse)
for k, v in ipairs(mouse.buttons) do
if v then
local dx = center_x - mouse.x
local dy = center_y - mouse.y
local dist = dx^2 + dy^2
-- New master width factor
local mwfact = dist / maxdist_pow
tag.setmwfact(math.min(math.max(0.01, mwfact), 0.99), tag.selected(c.screen))
return true
end
end
return false
end, corner .. "_corner")
end
local function client_resize_tiled(c, lay)
local wa = capi.screen[c.screen].workarea
local mwfact = tag.getmwfact()
local cursor
local g = c:geometry()
local offset = 0
local x,y
if lay == layout.suit.tile then
cursor = "cross"
if g.height+15 > wa.height then
offset = g.height * .5
cursor = "sb_h_double_arrow"
elseif not (g.y+g.height+15 > wa.y+wa.height) then
offset = g.height
end
capi.mouse.coords({ x = wa.x + wa.width * mwfact, y = g.y + offset })
elseif lay == layout.suit.tile.left then
cursor = "cross"
if g.height+15 >= wa.height then
offset = g.height * .5
cursor = "sb_h_double_arrow"
elseif not (g.y+g.height+15 > wa.y+wa.height) then
offset = g.height
end
capi.mouse.coords({ x = wa.x + wa.width * (1 - mwfact), y = g.y + offset })
elseif lay == layout.suit.tile.bottom then
cursor = "cross"
if g.width+15 >= wa.width then
offset = g.width * .5
cursor = "sb_v_double_arrow"
elseif not (g.x+g.width+15 > wa.x+wa.width) then
offset = g.width
end
capi.mouse.coords({ y = wa.y + wa.height * mwfact, x = g.x + offset})
else
cursor = "cross"
if g.width+15 >= wa.width then
offset = g.width * .5
cursor = "sb_v_double_arrow"
elseif not (g.x+g.width+15 > wa.x+wa.width) then
offset = g.width
end
capi.mouse.coords({ y = wa.y + wa.height * (1 - mwfact), x= g.x + offset })
end
capi.mousegrabber.run(function (mouse)
for k, v in ipairs(mouse.buttons) do
if v then
local fact_x = (mouse.x - wa.x) / wa.width
local fact_y = (mouse.y - wa.y) / wa.height
local mwfact
local g = c:geometry()
-- we have to make sure we're not on the last visible client where we have to use different settings.
local wfact
local wfact_x, wfact_y
if (g.y+g.height+15) > (wa.y+wa.height) then
wfact_y = (g.y + g.height - mouse.y) / wa.height
else
wfact_y = (mouse.y - g.y) / wa.height
end
if (g.x+g.width+15) > (wa.x+wa.width) then
wfact_x = (g.x + g.width - mouse.x) / wa.width
else
wfact_x = (mouse.x - g.x) / wa.width
end
if lay == layout.suit.tile then
mwfact = fact_x
wfact = wfact_y
elseif lay == layout.suit.tile.left then
mwfact = 1 - fact_x
wfact = wfact_y
elseif lay == layout.suit.tile.bottom then
mwfact = fact_y
wfact = wfact_x
else
mwfact = 1 - fact_y
wfact = wfact_x
end
tag.setmwfact(math.min(math.max(mwfact, 0.01), 0.99), tag.selected(c.screen))
aclient.setwfact(math.min(math.max(wfact,0.01), 0.99), c)
return true
end
end
return false
end, cursor)
end
local function client_resize_floating(c, corner, fixed_x, fixed_y)
local corner, x, y = client.corner(c, corner)
local g = c:geometry()
-- Warp mouse pointer
capi.mouse.coords({ x = x, y = y })
capi.mousegrabber.run(function (mouse)
for k, v in ipairs(mouse.buttons) do
if v then
-- Ignore screen changes
if not aclient.floating.get(c)
and capi.mouse.screen ~= c.screen then
return true
end
local ng
if corner == "bottom_right" then
ng = { width = mouse.x - g.x,
height = mouse.y - g.y }
elseif corner == "bottom_left" then
ng = { x = mouse.x,
width = (g.x + g.width) - mouse.x,
height = mouse.y - g.y }
elseif corner == "top_left" then
ng = { x = mouse.x,
width = (g.x + g.width) - mouse.x,
y = mouse.y,
height = (g.y + g.height) - mouse.y }
else
ng = { width = mouse.x - g.x,
y = mouse.y,
height = (g.y + g.height) - mouse.y }
end
if ng.width <= 0 then ng.width = nil end
if ng.height <= 0 then ng.height = nil end
if fixed_x then ng.width = g.width ng.x = g.x end
if fixed_y then ng.height = g.height ng.y = g.y end
c:geometry(ng)
-- Get real geometry that has been applied
-- in case we honor size hints
-- XXX: This should be rewritten when size
-- hints are available from Lua.
local rg = c:geometry()
if corner == "bottom_right" then
ng = {}
elseif corner == "bottom_left" then
ng = { x = (g.x + g.width) - rg.width }
elseif corner == "top_left" then
ng = { x = (g.x + g.width) - rg.width,
y = (g.y + g.height) - rg.height }
else
ng = { y = (g.y + g.height) - rg.height }
end
c:geometry({ x = ng.x, y = ng.y })
return true
end
end
return false
end, corner .. "_corner")
end
--- Resize a client.
-- @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 client.resize(c, corner)
local c = c or capi.client.focus
if not c then return end
if c.fullscreen
or c.type == "desktop"
or c.type == "splash"
or c.type == "dock" then
return
end
-- Do not allow maximized clients to be resized by mouse
local fixed_x = c.maximized_horizontal
local fixed_y = c.maximized_vertical
local lay = layout.get(c.screen)
if lay == layout.suit.floating or aclient.floating.get(c) then
return client_resize_floating(c, corner, fixed_x, fixed_y)
elseif lay == layout.suit.tile
or lay == layout.suit.tile.left
or lay == layout.suit.tile.top
or lay == layout.suit.tile.bottom
then
return client_resize_tiled(c, lay)
elseif lay == layout.suit.magnifier then
return client_resize_magnifier(c, corner)
end
end
-- Disable struts when resizing
local function update_struts(c, prop)
if prop == "geometry" then
local struts = c:struts()
struts['left'] = 0
struts['right'] = 0
struts['top'] = 0
struts['bottom'] = 0
c:struts(struts)
end
end
hooks.property.register(update_struts)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80