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 hooks
= require("awful.hooks")
11 local aclient
= require("awful.client")
12 local widget
= require("awful.widget")
13 local util
= require("awful.util")
23 mousegrabber
= mousegrabber
,
26 --- Mouse module for awful
32 --- Get the client object under the pointer.
33 -- @return The client object under the pointer, if one can be found.
34 function client_under_pointer()
35 local obj
= capi
.mouse
.object_under_pointer()
36 if type(obj
) == "client" then
41 --- Get the wibox object under the pointer.
42 -- @return The wibox object under the pointer, if one can be found.
43 function wibox_under_pointer()
44 local obj
= capi
.mouse
.object_under_pointer()
45 if type(obj
) == "wibox" then
50 --- Get the widget under the pointer.
51 -- @return The widget object under the pointer, if it can be found.
52 function widget_under_pointer()
53 local obj
, obj2
= capi
.mouse
.object_under_pointer()
54 if type(obj2
) == "widget" then
59 local function snap_outside(g
, sg
, snap
)
60 if g
.x
< snap
+ sg
.x
+ sg
.width
and g
.x
> sg
.x
+ sg
.width
then
62 elseif g
.x
+ g
.width
< sg
.x
and g
.x
+ g
.width
> sg
.x
- snap
then
65 if g
.y
< snap
+ sg
.y
+ sg
.height
and g
.y
> sg
.y
+ sg
.height
then
66 g
.y
= sg
.y
+ sg
.height
67 elseif g
.y
+ g
.height
< sg
.y
and g
.y
+ g
.height
> sg
.y
- snap
then
73 local function snap_inside(g
, sg
, snap
)
76 if math
.abs(g
.x
) < snap
+ sg
.x
and g
.x
>= sg
.x
then
79 elseif math
.abs((sg
.x
+ sg
.width
) - (g
.x
+ g
.width
)) < snap
then
81 g
.x
= sg
.x
+ sg
.width
- g
.width
83 if math
.abs(g
.y
) < snap
+ sg
.y
and g
.y
>= sg
.y
then
86 elseif math
.abs((sg
.y
+ sg
.height
) - (g
.y
+ g
.height
)) < snap
then
88 g
.y
= sg
.y
+ sg
.height
- g
.height
91 -- What is the dominant dimension?
92 if g
.width
> g
.height
then
99 --- Snap a client to the closest client or screen edge.
100 -- @param c The client to snap.
101 -- @param snap The pixel to snap clients.
102 -- @param x The client x coordinate.
103 -- @param y The client y coordinate.
104 -- @param fixed_x True if the client isn't allowed to move in the x direction.
105 -- @param fixed_y True if the client isn't allowed to move in the y direction.
106 function client
.snap(c
, snap
, x
, y
, fixed_x
, fixed_y
)
107 local snap
= snap
or 8
108 local c
= c
or client
.focus
109 local cur_geom
= c
:geometry()
110 local geom
= c
:geometry()
116 geom
, edge
= snap_inside(geom
, capi
.screen
[c
.screen
].geometry
, snap
)
117 geom
, edge2
= snap_inside(geom
, capi
.screen
[c
.screen
].workarea
, snap
)
119 -- Allow certain windows to snap to the edge of the workarea.
120 -- Only allow docking to workarea for consistency/to avoid problems.
121 if aclient
.dockable
.get(c
) then
122 local struts
= c
:struts()
127 if edge2
~= "none" and aclient
.floating
.get(c
) then
128 if edge2
== "left" or edge2
== "right" then
129 struts
[edge2
] = cur_geom
.width
130 elseif edge2
== "top" or edge2
== "bottom" then
131 struts
[edge2
] = cur_geom
.height
137 for k
, snapper
in ipairs(aclient
.visible(c
.screen
)) do
139 geom
= snap_outside(geom
, snapper
:geometry(), snap
)
143 -- It's easiest to undo changes afterwards if they're not allowed
144 if fixed_x
then geom
.x
= cur_geom
.x
end
145 if fixed_y
then geom
.y
= cur_geom
.y
end
151 -- @param c The client to move, or the focused one if nil.
152 -- @param snap The pixel to snap clients.
153 function client
.move(c
, snap
)
154 local c
= c
or capi
.client
.focus
158 or c
.type == "desktop"
159 or c
.type == "splash"
160 or c
.type == "dock" then
166 local orig
= c
:geometry()
167 local m_c
= capi
.mouse
.coords()
168 local dist_x
= m_c
.x
- orig
.x
169 local dist_y
= m_c
.y
- orig
.y
170 -- Only allow moving in the non-maximized directions
171 local fixed_x
= c
.maximized_horizontal
172 local fixed_y
= c
.maximized_vertical
174 capi
.mousegrabber
.run(function (mouse
)
175 for k
, v
in ipairs(mouse
.buttons
) do
177 local lay
= layout
.get(c
.screen
)
178 if lay
== layout
.suit
.floating
or aclient
.floating
.get(c
) then
179 local x
= mouse
.x
- dist_x
180 local y
= mouse
.y
- dist_y
181 c
:geometry(client
.snap(c
, snap
, x
, y
, fixed_x
, fixed_y
))
182 elseif lay
~= layout
.suit
.magnifier
then
183 -- Only move the client to the mouse
184 -- screen if the target screen is not
186 -- Otherwise, we move if via geometry.
187 if layout
.get(capi
.mouse
.screen
) == layout
.suit
.floating
then
188 local x
= mouse
.x
- dist_x
189 local y
= mouse
.y
- dist_y
190 c
:geometry(client
.snap(c
, snap
, x
, y
, fixed_x
, fixed_y
))
192 c
.screen
= capi
.mouse
.screen
194 if layout
.get(c
.screen
) ~= layout
.suit
.floating
then
195 local c_u_m
= client_under_pointer()
196 if c_u_m
and not aclient
.floating
.get(c_u_m
) then
210 client
.dragtotag
= { }
212 --- Move a client to a tag by drag'n'dropping it over a taglist widget
213 -- @param c The client to move
214 function client
.dragtotag
.widget(c
)
215 capi
.mousegrabber
.run(function (mouse
)
216 local button_down
= false
217 for _
, v
in ipairs(mouse
.buttons
) do
218 if v
then button_down
= true end
220 if not button_down
then
221 local w
= widget_under_pointer()
222 if w
and widget
.taglist
.gettag(w
) then
223 local t
= widget
.taglist
.gettag(w
)
224 if t
.screen
~= c
.screen
then
225 aclient
.movetoscreen(c
, t
.screen
)
227 aclient
.movetotag(t
, c
)
235 --- Move a client to a tag by dragging it onto the left / right side of the screen
236 -- @param c The client to move
237 function client
.dragtotag
.border(c
)
238 capi
.mousegrabber
.run(function (mouse
)
239 local button_down
= false
240 for _
, v
in ipairs(mouse
.buttons
) do
241 if v
then button_down
= true end
243 local wa
= capi
.screen
[c
.screen
].workarea
244 if mouse
.x
>= wa
.x
+ wa
.width
then
245 capi
.mouse
.coords({ x
= wa
.x
+ wa
.width
- 1 })
246 elseif mouse
.x
<= wa
.x
then
247 capi
.mouse
.coords({ x
= wa
.x
+ 1 })
249 if not button_down
then
250 local tags
= capi
.screen
[c
.screen
]:tags()
251 local t
= tag.selected()
253 for i
, v
in ipairs(tags
) do
258 if mouse
.x
> wa
.x
+ wa
.width
- 10 then
259 local newtag = tags
[util
.cycle(#tags
, idx
+ 1)]
260 aclient
.movetotag(newtag, c
)
262 elseif mouse
.x
< wa
.x
+ 10 then
263 local newtag = tags
[util
.cycle(#tags
, idx
- 1)]
264 aclient
.movetotag(newtag, c
)
273 --- Move the wibox under the cursor
274 --@param w The wibox to move, or none to use that under the pointer
275 function wibox
.move(w
)
276 if not w
then w
= wibox_under_pointer() end
277 if not w
then return end
280 x
= w
:geometry()["x"] - capi
.mouse
.coords()["x"],
281 y
= w
:geometry()["y"] - capi
.mouse
.coords()["y"]
284 capi
.mousegrabber
.run(function (mouse
)
285 local button_down
= false
286 if w
.position
== "floating" then
288 x
= capi
.mouse
.coords()["x"] + offset
["x"],
289 y
= capi
.mouse
.coords()["y"] + offset
["y"],
292 local wa
= capi
.screen
[capi
.mouse
.screen
].workarea
294 if capi
.mouse
.coords()["y"] > wa
.y
+ wa
.height
- 10 then
295 w
.position
= "bottom"
296 elseif capi
.mouse
.coords()["y"] < wa
.y
+ 10 then
298 elseif capi
.mouse
.coords()["x"] > wa
.x
+ wa
.width
- 10 then
300 elseif capi
.mouse
.coords()["x"] < wa
.x
+ 10 then
303 w
.screen
= capi
.mouse
.screen
305 for k
, v
in ipairs(mouse
.buttons
) do
306 if v
then button_down
= true end
308 if not button_down
then
315 --- Get a client corner coordinates.
316 -- @param c The client to get corner from, focused one by default.
317 -- @param corner The corner to use: auto, top_left, top_right, bottom_left,
318 -- bottom_right. Default is auto, and auto find the nearest corner.
319 -- @return Actual used corner and x and y coordinates.
320 function client
.corner(c
, corner
)
321 local c
= c
or capi
.client
.focus
322 if not c
then return end
324 local g
= c
:geometry()
326 if not corner
or corner
== "auto" then
327 local m_c
= capi
.mouse
.coords()
328 if math
.abs(g
.y
- m_c
.y
) < math
.abs(g
.y
+ g
.height
- m_c
.y
) then
329 if math
.abs(g
.x
- m_c
.x
) < math
.abs(g
.x
+ g
.width
- m_c
.x
) then
335 if math
.abs(g
.x
- m_c
.x
) < math
.abs(g
.x
+ g
.width
- m_c
.x
) then
336 corner
= "bottom_left"
338 corner
= "bottom_right"
344 if corner
== "top_right" then
347 elseif corner
== "top_left" then
350 elseif corner
== "bottom_left" then
361 local function client_resize_magnifier(c
, corner
)
362 local corner
, x
, y
= client
.corner(c
, corner
)
363 capi
.mouse
.coords({ x
= x
, y
= y
})
365 local wa
= capi
.screen
[c
.screen
].workarea
366 local center_x
= wa
.x
+ wa
.width
/ 2
367 local center_y
= wa
.y
+ wa
.height
/ 2
368 local maxdist_pow
= (wa
.width^
2 + wa
.height^
2) / 4
370 capi
.mousegrabber
.run(function (mouse
)
371 for k
, v
in ipairs(mouse
.buttons
) do
373 local dx
= center_x
- mouse
.x
374 local dy
= center_y
- mouse
.y
375 local dist
= dx^
2 + dy^
2
377 -- New master width factor
378 local mwfact
= dist
/ maxdist_pow
379 tag.setmwfact(math
.min(math
.max(0.01, mwfact
), 0.99), tag.selected(c
.screen
))
384 end, corner
.. "_corner")
387 local function client_resize_tiled(c
, lay
)
388 local wa
= capi
.screen
[c
.screen
].workarea
389 local mwfact
= tag.getmwfact()
391 local g
= c
:geometry()
394 if lay
== layout
.suit
.tile
then
396 if g
.height
+15 > wa
.height
then
397 offset
= g
.height
* .5
398 cursor
= "sb_h_double_arrow"
399 elseif not (g
.y
+g
.height
+15 > wa
.y
+wa
.height
) then
402 capi
.mouse
.coords({ x
= wa
.x
+ wa
.width
* mwfact
, y
= g
.y
+ offset
})
403 elseif lay
== layout
.suit
.tile
.left
then
405 if g
.height
+15 >= wa
.height
then
406 offset
= g
.height
* .5
407 cursor
= "sb_h_double_arrow"
408 elseif not (g
.y
+g
.height
+15 > wa
.y
+wa
.height
) then
411 capi
.mouse
.coords({ x
= wa
.x
+ wa
.width
* (1 - mwfact
), y
= g
.y
+ offset
})
412 elseif lay
== layout
.suit
.tile
.bottom
then
414 if g
.width
+15 >= wa
.width
then
415 offset
= g
.width
* .5
416 cursor
= "sb_v_double_arrow"
417 elseif not (g
.x
+g
.width
+15 > wa
.x
+wa
.width
) then
420 capi
.mouse
.coords({ y
= wa
.y
+ wa
.height
* mwfact
, x
= g
.x
+ offset
})
423 if g
.width
+15 >= wa
.width
then
424 offset
= g
.width
* .5
425 cursor
= "sb_v_double_arrow"
426 elseif not (g
.x
+g
.width
+15 > wa
.x
+wa
.width
) then
429 capi
.mouse
.coords({ y
= wa
.y
+ wa
.height
* (1 - mwfact
), x
= g
.x
+ offset
})
432 capi
.mousegrabber
.run(function (mouse
)
433 for k
, v
in ipairs(mouse
.buttons
) do
435 local fact_x
= (mouse
.x
- wa
.x
) / wa
.width
436 local fact_y
= (mouse
.y
- wa
.y
) / wa
.height
439 local g
= c
:geometry()
442 -- we have to make sure we're not on the last visible client where we have to use different settings.
444 local wfact_x
, wfact_y
445 if (g
.y
+g
.height
+15) > (wa
.y
+wa
.height
) then
446 wfact_y
= (g
.y
+ g
.height
- mouse
.y
) / wa
.height
448 wfact_y
= (mouse
.y
- g
.y
) / wa
.height
451 if (g
.x
+g
.width
+15) > (wa
.x
+wa
.width
) then
452 wfact_x
= (g
.x
+ g
.width
- mouse
.x
) / wa
.width
454 wfact_x
= (mouse
.x
- g
.x
) / wa
.width
458 if lay
== layout
.suit
.tile
then
461 elseif lay
== layout
.suit
.tile
.left
then
464 elseif lay
== layout
.suit
.tile
.bottom
then
472 tag.setmwfact(math
.min(math
.max(mwfact
, 0.01), 0.99), tag.selected(c
.screen
))
473 aclient
.setwfact(math
.min(math
.max(wfact
,0.01), 0.99), c
)
481 local function client_resize_floating(c
, corner
, fixed_x
, fixed_y
)
482 local corner
, x
, y
= client
.corner(c
, corner
)
483 local g
= c
:geometry()
485 -- Warp mouse pointer
486 capi
.mouse
.coords({ x
= x
, y
= y
})
488 capi
.mousegrabber
.run(function (mouse
)
489 for k
, v
in ipairs(mouse
.buttons
) do
491 -- Ignore screen changes
492 if not aclient
.floating
.get(c
)
493 and capi
.mouse
.screen
~= c
.screen
then
498 if corner
== "bottom_right" then
499 ng
= { width
= mouse
.x
- g
.x
,
500 height
= mouse
.y
- g
.y
}
501 elseif corner
== "bottom_left" then
503 width
= (g
.x
+ g
.width
) - mouse
.x
,
504 height
= mouse
.y
- g
.y
}
505 elseif corner
== "top_left" then
507 width
= (g
.x
+ g
.width
) - mouse
.x
,
509 height
= (g
.y
+ g
.height
) - mouse
.y
}
511 ng
= { width
= mouse
.x
- g
.x
,
513 height
= (g
.y
+ g
.height
) - mouse
.y
}
515 if ng
.width
<= 0 then ng
.width
= nil end
516 if ng
.height
<= 0 then ng
.height
= nil end
517 if fixed_x
then ng
.width
= g
.width ng
.x
= g
.x
end
518 if fixed_y
then ng
.height
= g
.height ng
.y
= g
.y
end
520 -- Get real geometry that has been applied
521 -- in case we honor size hints
522 -- XXX: This should be rewritten when size
523 -- hints are available from Lua.
524 local rg
= c
:geometry()
526 if corner
== "bottom_right" then
528 elseif corner
== "bottom_left" then
529 ng
= { x
= (g
.x
+ g
.width
) - rg
.width
}
530 elseif corner
== "top_left" then
531 ng
= { x
= (g
.x
+ g
.width
) - rg
.width
,
532 y
= (g
.y
+ g
.height
) - rg
.height
}
534 ng
= { y
= (g
.y
+ g
.height
) - rg
.height
}
536 c
:geometry({ x
= ng
.x
, y
= ng
.y
})
541 end, corner
.. "_corner")
545 -- @param c The client to resize, or the focused one by default.
546 -- @param corner The corner to grab on resize. Auto detected by default.
547 function client
.resize(c
, corner
)
548 local c
= c
or capi
.client
.focus
550 if not c
then return end
553 or c
.type == "desktop"
554 or c
.type == "splash"
555 or c
.type == "dock" then
559 -- Do not allow maximized clients to be resized by mouse
560 local fixed_x
= c
.maximized_horizontal
561 local fixed_y
= c
.maximized_vertical
563 local lay
= layout
.get(c
.screen
)
565 if lay
== layout
.suit
.floating
or aclient
.floating
.get(c
) then
566 return client_resize_floating(c
, corner
, fixed_x
, fixed_y
)
567 elseif lay
== layout
.suit
.tile
568 or lay
== layout
.suit
.tile
.left
569 or lay
== layout
.suit
.tile
.top
570 or lay
== layout
.suit
.tile
.bottom
572 return client_resize_tiled(c
, lay
)
573 elseif lay
== layout
.suit
.magnifier
then
574 return client_resize_magnifier(c
, corner
)
578 -- Set the cursor at startup
579 capi
.root
.cursor("left_ptr")
581 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80