awful.client: fix iteration over removed elements
[awesome.git] / lib / awful / client.lua.in
blob155633a74eebfe2261cdaaf1d53be7c12ac96d8b
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")
12 local pairs = pairs
13 local ipairs = ipairs
14 local table = table
15 local math = math
16 local otable = otable
17 local setmetatable = setmetatable
18 local capi =
20 client = client,
21 mouse = mouse,
22 screen = screen,
25 --- Client module for awful
26 module("awful.client")
28 -- Private data
29 local data = {}
30 data.maximize = otable()
31 data.focus = {}
32 data.urgent = {}
33 data.marked = {}
35 -- Urgent functions
36 urgent = {}
37 focus = {}
38 focus.history = {}
39 swap = {}
41 -- User hooks
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.
47 function urgent.get()
48 if #data.urgent > 0 then
49 return data.urgent[1]
50 else
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
54 if cl.urgent then
55 return cl
56 end
57 end
58 end
59 end
61 --- Jump to the client that received the urgent hint first.
62 function urgent.jumpto()
63 local c = urgent.get()
64 if c then
65 local s = capi.client.focus and capi.client.focus.screen or capi.mouse.screen
66 -- focus the screen
67 if s ~= c.screen then
68 capi.mouse.screen = c.screen
69 end
70 -- focus the tag
71 tag.viewonly(c:tags()[1])
72 -- focus the client
73 capi.client.focus = c
74 c:raise()
75 end
76 end
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)
84 end
85 end
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
91 if c == cl then
92 table.remove(data.urgent, k)
93 break
94 end
95 end
96 end
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
102 if v == c then
103 table.remove(data.focus, k)
104 break
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"
116 or c.type == "dock"
117 or c.type == "splash" then
118 return nil
120 return c
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.
138 -- @return A client.
139 function focus.history.get(screen, idx)
140 -- When this counter is equal to idx, we return the client
141 local counter = 0
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
146 if vcc == c then
147 if counter == idx then
148 return c
150 -- We found one, increment the counter only.
151 counter = counter + 1
152 break
157 -- Argh nobody found in history, give the first one visible if there is one
158 if counter == 0 then
159 return vc[1]
163 --- Focus the previous client in history.
164 function focus.history.previous()
165 local sel = capi.client.focus
166 local s
167 if sel then
168 s = sel.screen
169 else
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)
181 local vcls = {}
182 for k, c in pairs(cls) do
183 if c:isvisible() then
184 table.insert(vcls, c)
187 return vcls
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.
195 function next(i, c)
196 -- Get currently focused client
197 local sel = c or capi.client.focus
198 if sel then
199 -- Get all visible clients
200 local cls = visible(sel.screen)
201 local fcls = {}
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)
208 cls = fcls
209 -- Loop upon each client
210 for idx, c in ipairs(cls) do
211 if c == sel then
212 -- Cycle
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)
226 if dir == "up" then
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']
235 return false
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)
248 local xA = cA['x']
249 local xB = cB['x']
250 local yA = cA['y']
251 local yB = cB['y']
253 if dir == "up" then
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
271 if sel then
272 local geometry = sel:geometry()
273 local dist, dist_min
274 local target = nil
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
287 target = c
288 dist_min = dist
293 return target
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
302 if sel then
303 local target = get_client_in_direction(dir, sel)
305 -- If we found a client to focus, then do it.
306 if target then
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)
327 if target then
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
337 if sel then
338 local target = get_client_in_direction(dir, sel)
340 -- If we found a client to swap with, then go for it
341 if target then
342 target:swap(sel)
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)
353 if target then
354 target:swap(sel)
358 -- Compatibility
359 local function __swap(self, i, c)
360 util.deprecate("swap.byidx()")
361 swap.byidx(i, c)
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
370 return visible(s)[1]
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.
381 function setslave(c)
382 local cls = visible(c.screen)
383 for k, v in pairs(cls) do
384 c:swap(v)
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.
406 function maximize(c)
407 local sel = c or capi.client.focus
408 if sel then
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
417 else
418 data.maximize[sel] = { geometry = sel:geometry(), floating = sel.floating }
419 if curlay ~= "floating" then
420 sel.floating = true
422 sel:geometry(ws)
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
439 if sel then
440 -- Check that tag and client screen are identical
441 if sel.screen ~= target.screen then return end
442 sel:tags({ target })
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()
454 if tags[target] then
455 -- If it's the only tag for the window, stop.
456 if #tags == 1 then return end
457 tags[tags[target]] = nil
458 else
459 tags[target] = target
461 sel:tags(tags)
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
469 if sel then
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
479 if sel then
480 local sc = capi.screen.count()
481 if not s then
482 s = sel.screen + 1
484 if s > sc then s = 1 elseif s < 1 then s = sc end
485 sel.screen = s
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.
494 function mark(c)
495 local cl = c or capi.client.focus
496 if cl then
497 for k, v in pairs(data.marked) do
498 if cl == v then
499 return false
503 table.insert(data.marked, cl)
505 -- Call callback
506 hooks.user.call('marked', cl)
507 return true
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.
514 function unmark(c)
515 local cl = c or capi.client.focus
517 for k, v in pairs(data.marked) do
518 if cl == v then
519 table.remove(data.marked, k)
520 hooks.user.call('unmarked', cl)
521 return true
525 return false
528 --- Check if a client is marked.
529 -- @param c The client to check, or the focused one otherwise.
530 function ismarked(c)
531 local cl = c or capi.client.focus
532 if cl then
533 for k, v in pairs(data.marked) do
534 if cl == v then
535 return true
539 return false
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
547 if not mark(c) then
548 unmark(c)
552 --- Return the marked clients and empty the marked table.
553 -- @return A table with all marked clients.
554 function getmarked()
555 for k, v in pairs(data.marked) do
556 hooks.user.call('unmarked', v)
559 t = data.marked
560 data.marked = {}
561 return t
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