awful.key: fix modifier comparison in match()
[awesome.git] / lib / awful / mouse.lua.in
blob17e207aac6c1b921a4865f0a64b6579312fb6c72
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")
14 local type = type
15 local math = math
16 local ipairs = ipairs
17 local capi =
19 root = root,
20 mouse = mouse,
21 screen = screen,
22 client = client,
23 mousegrabber = mousegrabber,
26 --- Mouse module for awful
27 module("awful.mouse")
29 client = {}
30 wibox = {}
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
37 return obj
38 end
39 end
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
46 return obj
47 end
48 end
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
55 return obj2
56 end
57 end
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
61 g.x = sg.x + sg.width
62 elseif g.x + g.width < sg.x and g.x + g.width > sg.x - snap then
63 g.x = sg.x - g.width
64 end
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
68 g.y = sg.y - g.height
69 end
70 return g
71 end
73 local function snap_inside(g, sg, snap)
74 local edgev = 'none'
75 local edgeh = 'none'
76 if math.abs(g.x) < snap + sg.x and g.x >= sg.x then
77 edgev = 'left'
78 g.x = sg.x
79 elseif math.abs((sg.x + sg.width) - (g.x + g.width)) < snap then
80 edgev = 'right'
81 g.x = sg.x + sg.width - g.width
82 end
83 if math.abs(g.y) < snap + sg.y and g.y >= sg.y then
84 edgeh = 'top'
85 g.y = sg.y
86 elseif math.abs((sg.y + sg.height) - (g.y + g.height)) < snap then
87 edgeh = 'bottom'
88 g.y = sg.y + sg.height - g.height
89 end
91 -- What is the dominant dimension?
92 if g.width > g.height then
93 return g, edgeh
94 else
95 return g, edgev
96 end
97 end
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()
111 local edge = "none"
112 local edge2 = "none"
113 geom.x = x or geom.x
114 geom.y = y or geom.y
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()
123 struts['left'] = 0
124 struts['right'] = 0
125 struts['top'] = 0
126 struts['bottom'] = 0
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
134 c:struts(struts)
137 for k, snapper in ipairs(aclient.visible(c.screen)) do
138 if snapper ~= c then
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
147 return geom
150 --- Move a client.
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
156 if not c
157 or c.fullscreen
158 or c.type == "desktop"
159 or c.type == "splash"
160 or c.type == "dock" then
161 return
164 c:raise()
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
176 if v then
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
185 -- floating.
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))
191 else
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
197 if c_u_m ~= c then
198 c:swap(c_u_m)
203 return true
206 return false
207 end, "fleur")
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)
229 return false
231 return true
232 end, "fleur")
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()
252 local idx
253 for i, v in ipairs(tags) do
254 if v == t then
255 idx = i
258 if mouse.x > wa.x + wa.width - 10 then
259 local newtag = tags[util.cycle(#tags, idx + 1)]
260 aclient.movetotag(newtag, c)
261 tag.viewnext()
262 elseif mouse.x < wa.x + 10 then
263 local newtag = tags[util.cycle(#tags, idx - 1)]
264 aclient.movetotag(newtag, c)
265 tag.viewprev()
267 return false
269 return true
270 end, "fleur")
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
279 local offset = {
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
287 w:geometry({
288 x = capi.mouse.coords()["x"] + offset["x"],
289 y = capi.mouse.coords()["y"] + offset["y"],
291 else
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
297 w.position = "top"
298 elseif capi.mouse.coords()["x"] > wa.x + wa.width - 10 then
299 w.position = "right"
300 elseif capi.mouse.coords()["x"] < wa.x + 10 then
301 w.position = "left"
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
309 return false
311 return true
312 end, "fleur")
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
330 corner = "top_left"
331 else
332 corner = "top_right"
334 else
335 if math.abs(g.x - m_c.x) < math.abs(g.x + g.width - m_c.x) then
336 corner = "bottom_left"
337 else
338 corner = "bottom_right"
343 local x, y
344 if corner == "top_right" then
345 x = g.x + g.width
346 y = g.y
347 elseif corner == "top_left" then
348 x = g.x
349 y = g.y
350 elseif corner == "bottom_left" then
351 x = g.x
352 y = g.y + g.height
353 else
354 x = g.x + g.width
355 y = g.y + g.height
358 return corner, x, y
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
372 if v then
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))
380 return true
383 return false
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()
390 local cursor
391 local g = c:geometry()
392 local offset = 0
393 local x,y
394 if lay == layout.suit.tile then
395 cursor = "cross"
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
400 offset = g.height
402 capi.mouse.coords({ x = wa.x + wa.width * mwfact, y = g.y + offset })
403 elseif lay == layout.suit.tile.left then
404 cursor = "cross"
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
409 offset = g.height
411 capi.mouse.coords({ x = wa.x + wa.width * (1 - mwfact), y = g.y + offset })
412 elseif lay == layout.suit.tile.bottom then
413 cursor = "cross"
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
418 offset = g.width
420 capi.mouse.coords({ y = wa.y + wa.height * mwfact, x = g.x + offset})
421 else
422 cursor = "cross"
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
427 offset = g.width
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
434 if v then
435 local fact_x = (mouse.x - wa.x) / wa.width
436 local fact_y = (mouse.y - wa.y) / wa.height
437 local mwfact
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.
443 local wfact
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
447 else
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
453 else
454 wfact_x = (mouse.x - g.x) / wa.width
458 if lay == layout.suit.tile then
459 mwfact = fact_x
460 wfact = wfact_y
461 elseif lay == layout.suit.tile.left then
462 mwfact = 1 - fact_x
463 wfact = wfact_y
464 elseif lay == layout.suit.tile.bottom then
465 mwfact = fact_y
466 wfact = wfact_x
467 else
468 mwfact = 1 - fact_y
469 wfact = wfact_x
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)
474 return true
477 return false
478 end, cursor)
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
490 if v then
491 -- Ignore screen changes
492 if not aclient.floating.get(c)
493 and capi.mouse.screen ~= c.screen then
494 return true
497 local ng
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
502 ng = { x = mouse.x,
503 width = (g.x + g.width) - mouse.x,
504 height = mouse.y - g.y }
505 elseif corner == "top_left" then
506 ng = { x = mouse.x,
507 width = (g.x + g.width) - mouse.x,
508 y = mouse.y,
509 height = (g.y + g.height) - mouse.y }
510 else
511 ng = { width = mouse.x - g.x,
512 y = mouse.y,
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
519 c:geometry(ng)
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
527 ng = {}
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 }
533 else
534 ng = { y = (g.y + g.height) - rg.height }
536 c:geometry({ x = ng.x, y = ng.y })
537 return true
540 return false
541 end, corner .. "_corner")
544 --- Resize a client.
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
552 if c.fullscreen
553 or c.type == "desktop"
554 or c.type == "splash"
555 or c.type == "dock" then
556 return
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
571 then
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