--------------------------------------------------------------------------- --- Mouse module for awful -- -- @author Julien Danjou <julien@danjou.info> -- @copyright 2008 Julien Danjou -- @release @AWESOME_VERSION@ -- @module awful.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 = { root = root, mouse = mouse, screen = screen, client = client, mousegrabber = mousegrabber, } local mouse = { resize = require("awful.mouse.resize") } mouse.client = {} mouse.wibox = {} --- Get the client object under the pointer. -- @return The client object under the pointer, if one can be found. function mouse.client_under_pointer() local obj = capi.mouse.object_under_pointer() if type(obj) == "client" then return obj end end --- 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 end --- Move a client. -- @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) 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 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) if not c.valid then return false end 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") end mouse.client.dragtotag = { } --- Move a client to a tag by dragging it onto the left / right side of the screen -- @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 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") end --- Move the wibox under the cursor --@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() if not w then return end local offset = { x = w.x - capi.mouse.coords().x, y = w.y - capi.mouse.coords().y } capi.mousegrabber.run(function (_mouse) local button_down = false if awibox.get_position(w) == "floating" then w.x = capi.mouse.coords().x + offset.x w.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 awibox.set_position(w, "bottom", w.screen) elseif capi.mouse.coords()["y"] < wa.y + 10 then awibox.set_position(w, "top", w.screen) elseif capi.mouse.coords()["x"] > wa.x + wa.width - 10 then awibox.set_position(w, "right", w.screen) elseif capi.mouse.coords()["x"] < wa.x + 10 then awibox.set_position(w, "left", w.screen) end w.screen = capi.mouse.screen end for _, 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. -- @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 ngeo = nil if (not corner) or corner == "auto" then ngeo, corner = aplace.closest_corner(mouse, {parent = c}) elseif corner and aplace[corner] then ngeo = aplace[corner](mouse, {parent = c}) end return corner, ngeo and ngeo.x or nil, ngeo and ngeo.y or nil end --- Resize a client. -- @param c The client to resize, or the focused one by default. -- @tparam string corner The corner to grab on resize. Auto detected by default. -- @tparam[opt={}] table args A set of `awful.placement` arguments function mouse.client.resize(c, corner, args) c = c or capi.client.focus if not c then return end if c.fullscreen or c.type == "desktop" or c.type == "splash" or c.type == "dock" then return end -- Move the mouse to the corner if corner and aplace[corner] then aplace[corner](capi.mouse, {parent=c}) end mouse.resize(c, "mouse.resize", args or {include_sides=true}) end --- Default handler for `request::geometry` signals with `mouse.resize` context. -- @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 c:geometry { x = hints.x, y = hints.y, width = hints.width, height = hints.height, } elseif lay.resize_handler then lay.resize_handler(c, context, hints) end end end capi.client.connect_signal("request::geometry", mouse.resize_handler) -- Set the cursor at startup capi.root.cursor("left_ptr") return mouse -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80