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 hooks
= require("awful.hooks")
9 local util
= require("awful.util")
10 local layout
= require("awful.layout")
11 local tag = require("awful.tag")
17 local setmetatable
= setmetatable
25 --- Client module for awful
26 module("awful.client")
30 data
.maximize
= otable()
42 hooks
.user
.create('marked')
43 hooks
.user
.create('unmarked')
45 --- Get the first client that got the urgent hint.
46 -- @return The first urgent client.
48 if #data
.urgent
> 0 then
51 -- fallback behaviour: iterate through clients and get the first urgent
52 local clients
= capi
.client
.get()
53 for k
, cl
in pairs(clients
) do
61 --- Jump to the client that received the urgent hint first.
62 function urgent
.jumpto()
63 local c
= urgent
.get()
65 local s
= capi
.client
.focus
and capi
.client
.focus
.screen
or capi
.mouse
.screen
68 capi
.mouse
.screen
= c
.screen
71 tag.viewonly(c
:tags()[1])
78 --- Adds client to urgent stack.
79 -- @param c The client object.
80 -- @param prop The property which is updated.
81 function urgent
.add(c
, prop
)
82 if prop
== "urgent" and c
.urgent
then
83 table.insert(data
.urgent
, c
)
87 --- Remove client from urgent stack.
88 -- @param c The client object.
89 function urgent
.delete(c
)
90 for k
, cl
in ipairs(data
.urgent
) do
92 table.remove(data
.urgent
, k
)
98 --- Remove a client from the focus history
99 -- @param c The client that must be removed.
100 function focus
.history
.delete(c
)
101 for k
, v
in ipairs(data
.focus
) do
103 table.remove(data
.focus
, k
)
109 --- Filter out window that we do not want handled by focus.
110 -- This usually means that desktop, dock and splash windows are
111 -- not registered and cannot get focus.
112 -- @param c A client.
113 -- @return The same client if it's ok, nil otherwise.
114 function focus
.filter(c
)
115 if c
.type == "desktop"
117 or c
.type == "splash" then
123 --- Update client focus history.
124 -- @param c The client that has been focused.
125 function focus
.history
.add(c
)
126 if focus
.filter(c
) then
127 -- Remove the client if its in stack
128 focus
.history
.delete(c
)
129 -- Record the client has latest focused
130 table.insert(data
.focus
, 1, c
)
134 --- Get the latest focused client for a screen in history.
135 -- @param screen The screen number to look for.
136 -- @param idx The index: 0 will return first candidate,
137 -- 1 will return second, etc.
139 function focus
.history
.get(screen
, idx
)
140 -- When this counter is equal to idx, we return the client
142 local vc
= visible(screen
)
143 for k
, c
in ipairs(data
.focus
) do
144 if c
.screen
== screen
then
145 for j
, vcc
in ipairs(vc
) do
147 if counter
== idx
then
150 -- We found one, increment the counter only.
151 counter
= counter
+ 1
157 -- Argh nobody found in history, give the first one visible if there is one
163 --- Focus the previous client in history.
164 function focus
.history
.previous()
165 local sel
= capi
.client
.focus
170 s
= capi
.mouse
.screen
172 local c
= focus
.history
.get(s
, 1)
173 if c
then capi
.client
.focus
= c
end
176 --- Get visible clients from a screen.
177 -- @param screen The screen number, or nil for all screens.
178 -- @return A table with all visible clients.
179 function visible(screen
)
180 local cls
= capi
.client
.get(screen
)
182 for k
, c
in pairs(cls
) do
183 if c
:isvisible() then
184 table.insert(vcls
, c
)
190 --- Get a client by its relative index to the focused window.
191 -- @usage Set i to 1 to get next, -1 to get previous.
192 -- @param i The index.
193 -- @param c Optional client.
194 -- @return A client, or nil if no client is available.
196 -- Get currently focused client
197 local sel
= c
or capi
.client
.focus
199 -- Get all visible clients
200 local cls
= visible(sel
.screen
)
202 -- Remove all non-normal clients
203 for idx
, c
in ipairs(cls
) do
204 if focus
.filter(c
) then
205 table.insert(fcls
, c
)
209 -- Loop upon each client
210 for idx
, c
in ipairs(cls
) do
213 return cls
[util
.cycle(#cls
, idx
+ i
)]
219 --- Return true whether client B is in the right direction
220 -- compared to client A.
221 -- @param dir The direction.
222 -- @param cA The first client.
223 -- @param cB The second client.
224 -- @return True if B is in the direction of A.
225 local function is_in_direction(dir
, cA
, cB
)
227 return cA
['y'] > cB
['y']
228 elseif dir
== "down" then
229 return cA
['y'] < cB
['y']
230 elseif dir
== "left" then
231 return cA
['x'] > cB
['x']
232 elseif dir
== "right" then
233 return cA
['x'] < cB
['x']
238 --- Calculate distance between two points.
239 -- i.e: if we want to move to the right, we will take the right border
240 -- of the currently focused client and the left side of the checked client.
241 -- This avoid the focus of an upper client when you move to the right in a
242 -- tilebottom layout with nmaster=2 and 5 clients open, for instance.
243 -- @param dir The direction.
244 -- @param cA The first client.
245 -- @param cB The second client.
246 -- @return The distance between the clients.
247 local function calculate_distance(dir
, cA
, cB
)
254 yB
= yB
+ cB
['height']
255 elseif dir
== "down" then
256 yA
= yA
+ cA
['height']
257 elseif dir
== "left" then
258 xB
= xB
+ cB
['width']
259 elseif dir
== "right" then
260 xA
= xA
+ cA
['width']
263 return math
.sqrt(math
.pow(xB
- xA
, 2) + math
.pow(yB
- yA
, 2))
266 --- Get the nearest client in the given direction
267 -- @param dir The direction, can be either "up", "down", "left" or "right".
268 -- @param c Optional client to get a client relative to. Else focussed is used.
269 local function get_client_in_direction(dir
, c
)
270 local sel
= c
or capi
.client
.focus
272 local geometry
= sel
:geometry()
275 local cls
= visible(sel
.screen
)
277 -- We check each client.
278 for i
, c
in ipairs(cls
) do
279 -- Check geometry to see if client is located in the right direction.
280 if is_in_direction(dir
, geometry
, c
:geometry()) then
282 -- Calculate distance between focused client and checked client.
283 dist
= calculate_distance(dir
, geometry
, c
:geometry())
285 -- If distance is shorter then keep the client.
286 if not target
or dist
< dist_min
then
297 --- Focus a client by the given direction.
298 -- @param dir The direction, can be either "up", "down", "left" or "right".
299 -- @param c Optional client.
300 function focus
.bydirection(dir
, c
)
301 local sel
= c
or capi
.client
.focus
303 local target
= get_client_in_direction(dir
, sel
)
305 -- If we found a client to focus, then do it.
307 capi
.client
.focus
= target
312 function focusbydirection(dir
, c
)
313 util
.deprecate("focus.bydirection()")
314 return focus
.bydirection(dir
, c
)
317 function focusbyidx(i
, c
)
318 util
.deprecate("focus.byidx()")
319 return focus
.byidx(i
, c
)
322 --- Focus a client by its relative index.
323 -- @param i The index.
324 -- @param c Optional client.
325 function focus
.byidx(i
, c
)
326 local target
= next(i
, c
)
328 capi
.client
.focus
= target
332 --- Swap a client with another client in the given direction
333 -- @param dir The direction, can be either "up", "down", "left" or "right".
334 -- @param c Optional client.
335 function swap
.bydirection(dir
, c
)
336 local sel
= c
or capi
.client
.focus
338 local target
= get_client_in_direction(dir
, sel
)
340 -- If we found a client to swap with, then go for it
347 --- Swap a client by its relative index.
348 -- @param i The index.
349 -- @param c Optional client, otherwise focused one is used.
350 function swap
.byidx(i
, c
)
351 local sel
= c
or capi
.client
.focus
352 local target
= next(i
, sel
)
359 local function __swap(self
, i
, c
)
360 util
.deprecate("swap.byidx()")
363 setmetatable(swap
, { __call
= __swap
})
365 --- Get the master window.
366 -- @param screen Optional screen number, otherwise screen mouse is used.
367 -- @return The master window.
368 function getmaster(screen
)
369 local s
= screen
or capi
.mouse
.screen
373 --- Get the master window, deprecated, see getmaster().
374 function master(screen
)
375 util
.deprecate("getmaster()")
376 return getmaster(screen
)
379 --- Set the client as slave: put it at the end of other windows.
380 -- @param c The window to set as slave.
382 local cls
= visible(c
.screen
)
383 for k
, v
in pairs(cls
) do
388 --- Move/resize a client relative to current coordinates.
389 -- @param x The relative x coordinate.
390 -- @param y The relative y coordinate.
391 -- @param w The relative width.
392 -- @param h The relative height.
393 -- @param c The optional client, otherwise focused one is used.
394 function moveresize(x
, y
, w
, h
, c
)
395 local sel
= c
or capi
.client
.focus
396 local geometry
= sel
:geometry()
397 geometry
['x'] = geometry
['x'] + x
398 geometry
['y'] = geometry
['y'] + y
399 geometry
['width'] = geometry
['width'] + w
400 geometry
['height'] = geometry
['height'] + h
401 sel
:geometry(geometry
)
404 --- Maximize a client to use the full workarea.
405 -- @param c A client, or the focused one if nil.
407 local sel
= c
or capi
.client
.focus
409 local curlay
= layout
.get()
410 local ws
= capi
.screen
[sel
.screen
].workarea
411 ws
.width
= ws
.width
- 2 * sel
.border_width
412 ws
.height
= ws
.height
- 2 * sel
.border_width
413 if (sel
.floating
or curlay
== "floating") and data
.maximize
[sel
] then
414 sel
:geometry(data
.maximize
[sel
].geometry
)
415 sel
.floating
= data
.maximize
[sel
].floating
416 data
.maximize
[sel
] = nil
418 data
.maximize
[sel
] = { geometry
= sel
:geometry(), floating
= sel
.floating
}
419 if curlay
~= "floating" then
428 --- Erase eventual client data in maximize.
429 -- @param c The client.
430 local function maximize_clean(c
)
431 data
.maximize
[c
] = nil
434 --- Move a client to a tag.
435 -- @param target The tag to move the client to.
436 -- @param c Optional client to move, otherwise the focused one is used.
437 function movetotag(target
, c
)
438 local sel
= c
or capi
.client
.focus
440 -- Check that tag and client screen are identical
441 if sel
.screen
~= target
.screen
then return end
446 --- Toggle a tag on a client.
447 -- @param target The tag to toggle.
448 -- @param c Optional client to toggle, otherwise the focused one is used.
449 function toggletag(target
, c
)
450 local sel
= c
or capi
.client
.focus
451 -- Check that tag and client screen are identical
452 if sel
and sel
.screen
== target
.screen
then
453 local tags
= sel
:tags()
455 -- If it's the only tag for the window, stop.
456 if #tags
== 1 then return end
457 tags
[tags
[target]]
= nil
459 tags
[target
] = target
465 --- Toggle the floating status of a client.
466 -- @param c Optional client, the focused one if not set.
467 function togglefloating(c
)
468 local sel
= c
or capi
.client
.focus
470 sel
.floating
= not sel
.floating
474 --- Move a client to a screen. Default is next screen, cycling.
475 -- @param c The client to move.
476 -- @param s The screen number, default to current + 1.
477 function movetoscreen(c
, s
)
478 local sel
= c
or capi
.client
.focus
480 local sc
= capi
.screen
.count()
484 if s
> sc
then s
= 1 elseif s
< 1 then s
= sc
end
486 capi
.mouse
.coords(capi
.screen
[s
].geometry
)
487 capi
.client
.focus
= sel
491 --- Mark a client, and then call 'marked' hook.
492 -- @param c The client to mark, the focused one if not specified.
493 -- @return True if the client has been marked. False if the client was already marked.
495 local cl
= c
or capi
.client
.focus
497 for k
, v
in pairs(data
.marked
) do
503 table.insert(data
.marked
, cl
)
506 hooks
.user
.call('marked', cl
)
511 --- Unmark a client and then call 'unmarked' hook.
512 -- @param c The client to unmark, or the focused one if not specified.
513 -- @return True if the client has been unmarked. False if the client was not marked.
515 local cl
= c
or capi
.client
.focus
517 for k
, v
in pairs(data
.marked
) do
519 table.remove(data
.marked
, k
)
520 hooks
.user
.call('unmarked', cl
)
528 --- Check if a client is marked.
529 -- @param c The client to check, or the focused one otherwise.
531 local cl
= c
or capi
.client
.focus
533 for k
, v
in pairs(data
.marked
) do
542 --- Toggle a client as marked.
543 -- @param c The client to toggle mark.
544 function togglemarked(c
)
545 local cl
= c
or capi
.client
.focus
552 --- Return the marked clients and empty the marked table.
553 -- @return A table with all marked clients.
555 for k
, v
in pairs(data
.marked
) do
556 hooks
.user
.call('unmarked', v
)
564 -- Register standards hooks
565 hooks
.focus
.register(focus
.history
.add
)
566 hooks
.unmanage
.register(focus
.history
.delete
)
567 hooks
.unmanage
.register(maximize_clean
)
569 hooks
.property
.register(urgent
.add
)
570 hooks
.focus
.register(urgent
.delete
)
571 hooks
.unmanage
.register(urgent
.delete
)
573 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80