1 ---------------------------------------------------------------------------
2 -- @author Julien Danjou <julien@danjou.info>
3 -- @copyright 2008 Julien Danjou
4 -- @release @AWESOME_VERSION@
5 ---------------------------------------------------------------------------
7 -- Grab environment we need
8 local layout
= require("awful.layout")
9 local tag = require("awful.tag")
10 local aclient
= require("awful.client")
11 local widget
= require("awful.widget")
12 local awibox
= require("awful.wibox")
13 local util
= require("awful.util")
23 mousegrabber
= mousegrabber
,
26 require("awful.mouse.finder")
28 --- Mouse module for awful
34 --- Get the client object under the pointer.
35 -- @return The client object under the pointer, if one can be found.
36 function client_under_pointer()
37 local obj
= capi
.mouse
.object_under_pointer()
38 if type(obj
) == "client" then
43 --- Get the wibox object under the pointer.
44 -- @return The wibox object under the pointer, if one can be found.
45 function wibox_under_pointer()
46 local obj
= capi
.mouse
.object_under_pointer()
47 if type(obj
) == "wibox" then
52 --- Get the widget under the pointer.
53 -- @return The widget object under the pointer, if it can be found.
54 function widget_under_pointer()
55 local obj
, obj2
= capi
.mouse
.object_under_pointer()
56 if type(obj2
) == "widget" then
61 local function snap_outside(g
, sg
, snap
)
62 if g
.x
< snap
+ sg
.x
+ sg
.width
and g
.x
> sg
.x
+ sg
.width
then
64 elseif g
.x
+ g
.width
< sg
.x
and g
.x
+ g
.width
> sg
.x
- snap
then
67 if g
.y
< snap
+ sg
.y
+ sg
.height
and g
.y
> sg
.y
+ sg
.height
then
68 g
.y
= sg
.y
+ sg
.height
69 elseif g
.y
+ g
.height
< sg
.y
and g
.y
+ g
.height
> sg
.y
- snap
then
75 local function snap_inside(g
, sg
, snap
)
78 if math
.abs(g
.x
) < snap
+ sg
.x
and g
.x
> sg
.x
then
81 elseif math
.abs((sg
.x
+ sg
.width
) - (g
.x
+ g
.width
)) < snap
then
83 g
.x
= sg
.x
+ sg
.width
- g
.width
85 if math
.abs(g
.y
) < snap
+ sg
.y
and g
.y
> sg
.y
then
88 elseif math
.abs((sg
.y
+ sg
.height
) - (g
.y
+ g
.height
)) < snap
then
90 g
.y
= sg
.y
+ sg
.height
- g
.height
93 -- What is the dominant dimension?
94 if g
.width
> g
.height
then
101 --- Snap a client to the closest client or screen edge.
102 -- @param c The client to snap.
103 -- @param snap The pixel to snap clients.
104 -- @param x The client x coordinate.
105 -- @param y The client y coordinate.
106 -- @param fixed_x True if the client isn't allowed to move in the x direction.
107 -- @param fixed_y True if the client isn't allowed to move in the y direction.
108 function client
.snap(c
, snap
, x
, y
, fixed_x
, fixed_y
)
109 local snap
= snap
or 8
110 local c
= c
or client
.focus
111 local cur_geom
= c
:geometry()
112 local geom
= c
:geometry()
113 geom
.width
= geom
.width
+ (2 * c
.border_width
)
114 geom
.height
= geom
.height
+ (2 * c
.border_width
)
120 geom
, edge
= snap_inside(geom
, capi
.screen
[c
.screen
].geometry
, snap
)
121 geom
= snap_inside(geom
, capi
.screen
[c
.screen
].workarea
, snap
)
123 -- Allow certain windows to snap to the edge of the workarea.
124 -- Only allow docking to workarea for consistency/to avoid problems.
125 if aclient
.dockable
.get(c
) then
126 local struts
= c
:struts()
131 if edge
~= "none" and aclient
.floating
.get(c
) then
132 if edge
== "left" or edge
== "right" then
133 struts
[edge
] = cur_geom
.width
134 elseif edge
== "top" or edge
== "bottom" then
135 struts
[edge
] = cur_geom
.height
141 geom
.x
= geom
.x
- (2 * c
.border_width
)
142 geom
.y
= geom
.y
- (2 * c
.border_width
)
144 for k
, snapper
in ipairs(aclient
.visible(c
.screen
)) do
146 geom
= snap_outside(geom
, snapper
:geometry(), snap
)
150 geom
.width
= geom
.width
- (2 * c
.border_width
)
151 geom
.height
= geom
.height
- (2 * c
.border_width
)
152 geom
.x
= geom
.x
+ (2 * c
.border_width
)
153 geom
.y
= geom
.y
+ (2 * c
.border_width
)
155 -- It's easiest to undo changes afterwards if they're not allowed
156 if fixed_x
then geom
.x
= cur_geom
.x
end
157 if fixed_y
then geom
.y
= cur_geom
.y
end
163 -- @param c The client to move, or the focused one if nil.
164 -- @param snap The pixel to snap clients.
165 function client
.move(c
, snap
)
166 local c
= c
or capi
.client
.focus
170 or c
.type == "desktop"
171 or c
.type == "splash"
172 or c
.type == "dock" then
178 local orig
= c
:geometry()
179 local m_c
= capi
.mouse
.coords()
180 local dist_x
= m_c
.x
- orig
.x
181 local dist_y
= m_c
.y
- orig
.y
182 -- Only allow moving in the non-maximized directions
183 local fixed_x
= c
.maximized_horizontal
184 local fixed_y
= c
.maximized_vertical
186 capi
.mousegrabber
.run(function (mouse
)
187 for k
, v
in ipairs(mouse
.buttons
) do
189 local lay
= layout
.get(c
.screen
)
190 if lay
== layout
.suit
.floating
or aclient
.floating
.get(c
) then
191 local x
= mouse
.x
- dist_x
192 local y
= mouse
.y
- dist_y
193 c
:geometry(client
.snap(c
, snap
, x
, y
, fixed_x
, fixed_y
))
194 elseif lay
~= layout
.suit
.magnifier
then
195 -- Only move the client to the mouse
196 -- screen if the target screen is not
198 -- Otherwise, we move if via geometry.
199 if layout
.get(capi
.mouse
.screen
) == layout
.suit
.floating
then
200 local x
= mouse
.x
- dist_x
201 local y
= mouse
.y
- dist_y
202 c
:geometry(client
.snap(c
, snap
, x
, y
, fixed_x
, fixed_y
))
204 c
.screen
= capi
.mouse
.screen
206 if layout
.get(c
.screen
) ~= layout
.suit
.floating
then
207 local c_u_m
= client_under_pointer()
208 if c_u_m
and not aclient
.floating
.get(c_u_m
) then
222 client
.dragtotag
= { }
224 --- Move a client to a tag by drag'n'dropping it over a taglist widget
225 -- @param c The client to move
226 function client
.dragtotag
.widget(c
)
227 capi
.mousegrabber
.run(function (mouse
)
228 local button_down
= false
229 for _
, v
in ipairs(mouse
.buttons
) do
230 if v
then button_down
= true end
232 if not button_down
then
233 local w
= widget_under_pointer()
234 if w
and widget
.taglist
.gettag(w
) then
235 local t
= widget
.taglist
.gettag(w
)
236 if t
.screen
~= c
.screen
then
237 aclient
.movetoscreen(c
, t
.screen
)
239 aclient
.movetotag(t
, c
)
247 --- Move a client to a tag by dragging it onto the left / right side of the screen
248 -- @param c The client to move
249 function client
.dragtotag
.border(c
)
250 capi
.mousegrabber
.run(function (mouse
)
251 local button_down
= false
252 for _
, v
in ipairs(mouse
.buttons
) do
253 if v
then button_down
= true end
255 local wa
= capi
.screen
[c
.screen
].workarea
256 if mouse
.x
>= wa
.x
+ wa
.width
then
257 capi
.mouse
.coords({ x
= wa
.x
+ wa
.width
- 1 })
258 elseif mouse
.x
<= wa
.x
then
259 capi
.mouse
.coords({ x
= wa
.x
+ 1 })
261 if not button_down
then
262 local tags
= capi
.screen
[c
.screen
]:tags()
263 local t
= tag.selected()
265 for i
, v
in ipairs(tags
) do
270 if mouse
.x
> wa
.x
+ wa
.width
- 10 then
271 local newtag = tags
[util
.cycle(#tags
, idx
+ 1)]
272 aclient
.movetotag(newtag, c
)
274 elseif mouse
.x
< wa
.x
+ 10 then
275 local newtag = tags
[util
.cycle(#tags
, idx
- 1)]
276 aclient
.movetotag(newtag, c
)
285 --- Move the wibox under the cursor
286 --@param w The wibox to move, or none to use that under the pointer
287 function wibox
.move(w
)
288 local w
= w
or wibox_under_pointer()
289 if not w
then return end
292 x
= w
.x
- capi
.mouse
.coords().x
,
293 y
= w
.y
- capi
.mouse
.coords().y
296 capi
.mousegrabber
.run(function (mouse
)
297 local button_down
= false
298 if awibox
.get_position(w
) == "floating" then
299 w
.x
= capi
.mouse
.coords().x
+ offset
.x
300 w
.y
= capi
.mouse
.coords().y
+ offset
.y
302 local wa
= capi
.screen
[capi
.mouse
.screen
].workarea
304 if capi
.mouse
.coords()["y"] > wa
.y
+ wa
.height
- 10 then
305 awibox
.set_position(w
, "bottom", w
.screen
)
306 elseif capi
.mouse
.coords()["y"] < wa
.y
+ 10 then
307 awibox
.set_position(w
, "top", w
.screen
)
308 elseif capi
.mouse
.coords()["x"] > wa
.x
+ wa
.width
- 10 then
309 awibox
.set_position(w
, "right", w
.screen
)
310 elseif capi
.mouse
.coords()["x"] < wa
.x
+ 10 then
311 awibox
.set_position(w
, "left", w
.screen
)
313 w
.screen
= capi
.mouse
.screen
315 for k
, v
in ipairs(mouse
.buttons
) do
316 if v
then button_down
= true end
318 if not button_down
then
325 --- Get a client corner coordinates.
326 -- @param c The client to get corner from, focused one by default.
327 -- @param corner The corner to use: auto, top_left, top_right, bottom_left,
328 -- bottom_right. Default is auto, and auto find the nearest corner.
329 -- @return Actual used corner and x and y coordinates.
330 function client
.corner(c
, corner
)
331 local c
= c
or capi
.client
.focus
332 if not c
then return end
334 local g
= c
:geometry()
336 if not corner
or corner
== "auto" then
337 local m_c
= capi
.mouse
.coords()
338 if math
.abs(g
.y
- m_c
.y
) < math
.abs(g
.y
+ g
.height
- m_c
.y
) then
339 if math
.abs(g
.x
- m_c
.x
) < math
.abs(g
.x
+ g
.width
- m_c
.x
) then
345 if math
.abs(g
.x
- m_c
.x
) < math
.abs(g
.x
+ g
.width
- m_c
.x
) then
346 corner
= "bottom_left"
348 corner
= "bottom_right"
354 if corner
== "top_right" then
357 elseif corner
== "top_left" then
360 elseif corner
== "bottom_left" then
371 local function client_resize_magnifier(c
, corner
)
372 local corner
, x
, y
= client
.corner(c
, corner
)
373 capi
.mouse
.coords({ x
= x
, y
= y
})
375 local wa
= capi
.screen
[c
.screen
].workarea
376 local center_x
= wa
.x
+ wa
.width
/ 2
377 local center_y
= wa
.y
+ wa
.height
/ 2
378 local maxdist_pow
= (wa
.width^
2 + wa
.height^
2) / 4
380 capi
.mousegrabber
.run(function (mouse
)
381 for k
, v
in ipairs(mouse
.buttons
) do
383 local dx
= center_x
- mouse
.x
384 local dy
= center_y
- mouse
.y
385 local dist
= dx^
2 + dy^
2
387 -- New master width factor
388 local mwfact
= dist
/ maxdist_pow
389 tag.setmwfact(math
.min(math
.max(0.01, mwfact
), 0.99), tag.selected(c
.screen
))
394 end, corner
.. "_corner")
397 local function client_resize_tiled(c
, lay
)
398 local wa
= capi
.screen
[c
.screen
].workarea
399 local mwfact
= tag.getmwfact()
401 local g
= c
:geometry()
404 if lay
== layout
.suit
.tile
then
406 if g
.height
+15 > wa
.height
then
407 offset
= g
.height
* .5
408 cursor
= "sb_h_double_arrow"
409 elseif not (g
.y
+g
.height
+15 > wa
.y
+wa
.height
) then
412 capi
.mouse
.coords({ x
= wa
.x
+ wa
.width
* mwfact
, y
= g
.y
+ offset
})
413 elseif lay
== layout
.suit
.tile
.left
then
415 if g
.height
+15 >= wa
.height
then
416 offset
= g
.height
* .5
417 cursor
= "sb_h_double_arrow"
418 elseif not (g
.y
+g
.height
+15 > wa
.y
+wa
.height
) then
421 capi
.mouse
.coords({ x
= wa
.x
+ wa
.width
* (1 - mwfact
), y
= g
.y
+ offset
})
422 elseif lay
== layout
.suit
.tile
.bottom
then
424 if g
.width
+15 >= wa
.width
then
425 offset
= g
.width
* .5
426 cursor
= "sb_v_double_arrow"
427 elseif not (g
.x
+g
.width
+15 > wa
.x
+wa
.width
) then
430 capi
.mouse
.coords({ y
= wa
.y
+ wa
.height
* mwfact
, x
= g
.x
+ offset
})
433 if g
.width
+15 >= wa
.width
then
434 offset
= g
.width
* .5
435 cursor
= "sb_v_double_arrow"
436 elseif not (g
.x
+g
.width
+15 > wa
.x
+wa
.width
) then
439 capi
.mouse
.coords({ y
= wa
.y
+ wa
.height
* (1 - mwfact
), x
= g
.x
+ offset
})
442 capi
.mousegrabber
.run(function (mouse
)
443 for k
, v
in ipairs(mouse
.buttons
) do
445 local fact_x
= (mouse
.x
- wa
.x
) / wa
.width
446 local fact_y
= (mouse
.y
- wa
.y
) / wa
.height
449 local g
= c
:geometry()
452 -- we have to make sure we're not on the last visible client where we have to use different settings.
454 local wfact_x
, wfact_y
455 if (g
.y
+g
.height
+15) > (wa
.y
+wa
.height
) then
456 wfact_y
= (g
.y
+ g
.height
- mouse
.y
) / wa
.height
458 wfact_y
= (mouse
.y
- g
.y
) / wa
.height
461 if (g
.x
+g
.width
+15) > (wa
.x
+wa
.width
) then
462 wfact_x
= (g
.x
+ g
.width
- mouse
.x
) / wa
.width
464 wfact_x
= (mouse
.x
- g
.x
) / wa
.width
468 if lay
== layout
.suit
.tile
then
471 elseif lay
== layout
.suit
.tile
.left
then
474 elseif lay
== layout
.suit
.tile
.bottom
then
482 tag.setmwfact(math
.min(math
.max(mwfact
, 0.01), 0.99), tag.selected(c
.screen
))
483 aclient
.setwfact(math
.min(math
.max(wfact
,0.01), 0.99), c
)
491 local function client_resize_floating(c
, corner
, fixed_x
, fixed_y
)
492 local corner
, x
, y
= client
.corner(c
, corner
)
493 local g
= c
:geometry()
495 -- Warp mouse pointer
496 capi
.mouse
.coords({ x
= x
, y
= y
})
498 capi
.mousegrabber
.run(function (mouse
)
499 for k
, v
in ipairs(mouse
.buttons
) do
501 -- Ignore screen changes
502 if not aclient
.floating
.get(c
)
503 and capi
.mouse
.screen
~= c
.screen
then
508 if corner
== "bottom_right" then
509 ng
= { width
= mouse
.x
- g
.x
,
510 height
= mouse
.y
- g
.y
}
511 elseif corner
== "bottom_left" then
513 width
= (g
.x
+ g
.width
) - mouse
.x
,
514 height
= mouse
.y
- g
.y
}
515 elseif corner
== "top_left" then
517 width
= (g
.x
+ g
.width
) - mouse
.x
,
519 height
= (g
.y
+ g
.height
) - mouse
.y
}
521 ng
= { width
= mouse
.x
- g
.x
,
523 height
= (g
.y
+ g
.height
) - mouse
.y
}
525 if ng
.width
<= 0 then ng
.width
= nil end
526 if ng
.height
<= 0 then ng
.height
= nil end
527 if fixed_x
then ng
.width
= g
.width ng
.x
= g
.x
end
528 if fixed_y
then ng
.height
= g
.height ng
.y
= g
.y
end
530 -- Get real geometry that has been applied
531 -- in case we honor size hints
532 -- XXX: This should be rewritten when size
533 -- hints are available from Lua.
534 local rg
= c
:geometry()
536 if corner
== "bottom_right" then
538 elseif corner
== "bottom_left" then
539 ng
= { x
= (g
.x
+ g
.width
) - rg
.width
}
540 elseif corner
== "top_left" then
541 ng
= { x
= (g
.x
+ g
.width
) - rg
.width
,
542 y
= (g
.y
+ g
.height
) - rg
.height
}
544 ng
= { y
= (g
.y
+ g
.height
) - rg
.height
}
546 c
:geometry({ x
= ng
.x
, y
= ng
.y
})
551 end, corner
.. "_corner")
555 -- @param c The client to resize, or the focused one by default.
556 -- @param corner The corner to grab on resize. Auto detected by default.
557 function client
.resize(c
, corner
)
558 local c
= c
or capi
.client
.focus
560 if not c
then return end
563 or c
.type == "desktop"
564 or c
.type == "splash"
565 or c
.type == "dock" then
569 -- Do not allow maximized clients to be resized by mouse
570 local fixed_x
= c
.maximized_horizontal
571 local fixed_y
= c
.maximized_vertical
573 local lay
= layout
.get(c
.screen
)
575 if lay
== layout
.suit
.floating
or aclient
.floating
.get(c
) then
576 return client_resize_floating(c
, corner
, fixed_x
, fixed_y
)
577 elseif lay
== layout
.suit
.tile
578 or lay
== layout
.suit
.tile
.left
579 or lay
== layout
.suit
.tile
.top
580 or lay
== layout
.suit
.tile
.bottom
582 return client_resize_tiled(c
, lay
)
583 elseif lay
== layout
.suit
.magnifier
then
584 return client_resize_magnifier(c
, corner
)
588 -- Set the cursor at startup
589 capi
.root
.cursor("left_ptr")
591 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80