Remove "encoding=utf-8" from Vim modelines
[awesome.git] / lib / awful / mouse / init.lua.in
blob4496b9c0a0fda8dd093dd574b9c8e1a63cdba13b
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")
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 require("awful.mouse.finder")
28 --- Mouse module for awful
29 module("awful.mouse")
31 client = {}
32 wibox = {}
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
39 return obj
40 end
41 end
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
48 return obj
49 end
50 end
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
57 return obj2
58 end
59 end
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
63 g.x = sg.x + sg.width
64 elseif g.x + g.width < sg.x and g.x + g.width > sg.x - snap then
65 g.x = sg.x - g.width
66 end
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
70 g.y = sg.y - g.height
71 end
72 return g
73 end
75 local function snap_inside(g, sg, snap)
76 local edgev = 'none'
77 local edgeh = 'none'
78 if math.abs(g.x) < snap + sg.x and g.x > sg.x then
79 edgev = 'left'
80 g.x = sg.x
81 elseif math.abs((sg.x + sg.width) - (g.x + g.width)) < snap then
82 edgev = 'right'
83 g.x = sg.x + sg.width - g.width
84 end
85 if math.abs(g.y) < snap + sg.y and g.y > sg.y then
86 edgeh = 'top'
87 g.y = sg.y
88 elseif math.abs((sg.y + sg.height) - (g.y + g.height)) < snap then
89 edgeh = 'bottom'
90 g.y = sg.y + sg.height - g.height
91 end
93 -- What is the dominant dimension?
94 if g.width > g.height then
95 return g, edgeh
96 else
97 return g, edgev
98 end
99 end
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)
115 local edge = "none"
116 local edge2 = "none"
117 geom.x = x or geom.x
118 geom.y = y or geom.y
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()
127 struts['left'] = 0
128 struts['right'] = 0
129 struts['top'] = 0
130 struts['bottom'] = 0
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
138 c:struts(struts)
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
145 if snapper ~= c then
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
159 return geom
162 --- Move a client.
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
168 if not c
169 or c.fullscreen
170 or c.type == "desktop"
171 or c.type == "splash"
172 or c.type == "dock" then
173 return
176 c:raise()
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
188 if v then
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
197 -- floating.
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))
203 else
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
209 if c_u_m ~= c then
210 c:swap(c_u_m)
215 return true
218 return false
219 end, "fleur")
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)
241 return false
243 return true
244 end, "fleur")
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()
264 local idx
265 for i, v in ipairs(tags) do
266 if v == t then
267 idx = i
270 if mouse.x > wa.x + wa.width - 10 then
271 local newtag = tags[util.cycle(#tags, idx + 1)]
272 aclient.movetotag(newtag, c)
273 tag.viewnext()
274 elseif mouse.x < wa.x + 10 then
275 local newtag = tags[util.cycle(#tags, idx - 1)]
276 aclient.movetotag(newtag, c)
277 tag.viewprev()
279 return false
281 return true
282 end, "fleur")
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
291 local offset = {
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
301 else
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
319 return false
321 return true
322 end, "fleur")
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
340 corner = "top_left"
341 else
342 corner = "top_right"
344 else
345 if math.abs(g.x - m_c.x) < math.abs(g.x + g.width - m_c.x) then
346 corner = "bottom_left"
347 else
348 corner = "bottom_right"
353 local x, y
354 if corner == "top_right" then
355 x = g.x + g.width
356 y = g.y
357 elseif corner == "top_left" then
358 x = g.x
359 y = g.y
360 elseif corner == "bottom_left" then
361 x = g.x
362 y = g.y + g.height
363 else
364 x = g.x + g.width
365 y = g.y + g.height
368 return corner, x, y
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
382 if v then
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))
390 return true
393 return false
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()
400 local cursor
401 local g = c:geometry()
402 local offset = 0
403 local x,y
404 if lay == layout.suit.tile then
405 cursor = "cross"
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
410 offset = g.height
412 capi.mouse.coords({ x = wa.x + wa.width * mwfact, y = g.y + offset })
413 elseif lay == layout.suit.tile.left then
414 cursor = "cross"
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
419 offset = g.height
421 capi.mouse.coords({ x = wa.x + wa.width * (1 - mwfact), y = g.y + offset })
422 elseif lay == layout.suit.tile.bottom then
423 cursor = "cross"
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
428 offset = g.width
430 capi.mouse.coords({ y = wa.y + wa.height * mwfact, x = g.x + offset})
431 else
432 cursor = "cross"
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
437 offset = g.width
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
444 if v then
445 local fact_x = (mouse.x - wa.x) / wa.width
446 local fact_y = (mouse.y - wa.y) / wa.height
447 local mwfact
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.
453 local wfact
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
457 else
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
463 else
464 wfact_x = (mouse.x - g.x) / wa.width
468 if lay == layout.suit.tile then
469 mwfact = fact_x
470 wfact = wfact_y
471 elseif lay == layout.suit.tile.left then
472 mwfact = 1 - fact_x
473 wfact = wfact_y
474 elseif lay == layout.suit.tile.bottom then
475 mwfact = fact_y
476 wfact = wfact_x
477 else
478 mwfact = 1 - fact_y
479 wfact = wfact_x
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)
484 return true
487 return false
488 end, cursor)
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
500 if v then
501 -- Ignore screen changes
502 if not aclient.floating.get(c)
503 and capi.mouse.screen ~= c.screen then
504 return true
507 local ng
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
512 ng = { x = mouse.x,
513 width = (g.x + g.width) - mouse.x,
514 height = mouse.y - g.y }
515 elseif corner == "top_left" then
516 ng = { x = mouse.x,
517 width = (g.x + g.width) - mouse.x,
518 y = mouse.y,
519 height = (g.y + g.height) - mouse.y }
520 else
521 ng = { width = mouse.x - g.x,
522 y = mouse.y,
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
529 c:geometry(ng)
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
537 ng = {}
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 }
543 else
544 ng = { y = (g.y + g.height) - rg.height }
546 c:geometry({ x = ng.x, y = ng.y })
547 return true
550 return false
551 end, corner .. "_corner")
554 --- Resize a client.
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
562 if c.fullscreen
563 or c.type == "desktop"
564 or c.type == "splash"
565 or c.type == "dock" then
566 return
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
581 then
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