awful.client.jumpto: View urgent clients smarter
[awesome.git] / lib / awful / client.lua.in
blob1f18963f129f0ea92d125d23306247a22a3e8f79
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 util = require("awful.util")
9 local tag = require("awful.tag")
10 local pairs = pairs
11 local type = type
12 local ipairs = ipairs
13 local table = table
14 local math = math
15 local setmetatable = setmetatable
16 local capi =
18 client = client,
19 mouse = mouse,
20 screen = screen,
23 --- Useful client manipulation functions.
24 module("awful.client")
26 -- Private data
27 data = {}
28 data.focus = {}
29 data.urgent = {}
30 data.marked = {}
31 data.properties = setmetatable({}, { __mode = 'k' })
33 -- Functions
34 urgent = {}
35 focus = {}
36 focus.history = {}
37 swap = {}
38 floating = {}
39 dockable = {}
40 property = {}
42 --- Get the first client that got the urgent hint.
43 -- @return The first urgent client.
44 function urgent.get()
45 if #data.urgent > 0 then
46 return data.urgent[1]
47 else
48 -- fallback behaviour: iterate through clients and get the first urgent
49 local clients = capi.client.get()
50 for k, cl in pairs(clients) do
51 if cl.urgent then
52 return cl
53 end
54 end
55 end
56 end
58 --- Jump to the client that received the urgent hint first.
59 -- @param merge If true then merge tags when clients are not visible.
60 function urgent.jumpto(merge)
61 local c = urgent.get()
62 if c then
63 local s = capi.client.focus and capi.client.focus.screen or capi.mouse.screen
64 -- focus the screen
65 if s ~= c.screen then
66 capi.mouse.screen = c.screen
67 end
69 -- Try to make client visible, this also covers e.g. sticky
70 local t = c:tags()[1]
71 if t and not c:isvisible() then
72 if merge then
73 t.selected = true
74 else
75 tag.viewonly(t)
76 end
77 end
79 -- focus the client
80 capi.client.focus = c
81 c:raise()
82 end
83 end
85 --- Adds client to urgent stack.
86 -- @param c The client object.
87 -- @param prop The property which is updated.
88 function urgent.add(c, prop)
89 if type(c) == "client" and prop == "urgent" and c.urgent then
90 table.insert(data.urgent, c)
91 end
92 end
94 --- Remove client from urgent stack.
95 -- @param c The client object.
96 function urgent.delete(c)
97 for k, cl in ipairs(data.urgent) do
98 if c == cl then
99 table.remove(data.urgent, k)
100 break
105 --- Remove a client from the focus history
106 -- @param c The client that must be removed.
107 function focus.history.delete(c)
108 for k, v in ipairs(data.focus) do
109 if v == c then
110 table.remove(data.focus, k)
111 break
116 --- Filter out window that we do not want handled by focus.
117 -- This usually means that desktop, dock and splash windows are
118 -- not registered and cannot get focus.
119 -- @param c A client.
120 -- @return The same client if it's ok, nil otherwise.
121 function focus.filter(c)
122 if c.type == "desktop"
123 or c.type == "dock"
124 or c.type == "splash"
125 or not c.focusable then
126 return nil
128 return c
131 --- Update client focus history.
132 -- @param c The client that has been focused.
133 function focus.history.add(c)
134 if focus.filter(c) then
135 -- Remove the client if its in stack
136 focus.history.delete(c)
137 -- Record the client has latest focused
138 table.insert(data.focus, 1, c)
142 --- Get the latest focused client for a screen in history.
143 -- @param screen The screen number to look for.
144 -- @param idx The index: 0 will return first candidate,
145 -- 1 will return second, etc.
146 -- @return A client.
147 function focus.history.get(screen, idx)
148 -- When this counter is equal to idx, we return the client
149 local counter = 0
150 local vc = visible(screen)
151 for k, c in ipairs(data.focus) do
152 if c.screen == screen then
153 for j, vcc in ipairs(vc) do
154 if vcc == c then
155 if counter == idx then
156 return c
158 -- We found one, increment the counter only.
159 counter = counter + 1
160 break
165 -- Argh nobody found in history, give the first one visible if there is one
166 -- that passes the filter.
167 if counter == 0 then
168 for k, v in ipairs(vc) do
169 if focus.filter(v) then
170 return v
176 --- Focus the previous client in history.
177 function focus.history.previous()
178 local sel = capi.client.focus
179 local s
180 if sel then
181 s = sel.screen
182 else
183 s = capi.mouse.screen
185 local c = focus.history.get(s, 1)
186 if c then capi.client.focus = c end
189 --- Get visible clients from a screen.
190 -- @param screen The screen number, or nil for all screens.
191 -- @return A table with all visible clients.
192 function visible(screen)
193 local cls = capi.client.get(screen)
194 local vcls = {}
195 for k, c in pairs(cls) do
196 if c:isvisible() then
197 table.insert(vcls, c)
200 return vcls
203 --- Get visible and tiled clients
204 -- @param screen The screen number, or nil for all screens.
205 -- @return A tabl with all visible and tiled clients.
206 function tiled(screen)
207 local clients = visible(screen)
208 local tclients = {}
209 -- Remove floating clients
210 for k, c in pairs(clients) do
211 if not floating.get(c) then
212 table.insert(tclients, c)
215 return tclients
218 --- Get a client by its relative index to the focused window.
219 -- @usage Set i to 1 to get next, -1 to get previous.
220 -- @param i The index.
221 -- @param c Optional client.
222 -- @return A client, or nil if no client is available.
223 function next(i, c)
224 -- Get currently focused client
225 local sel = c or capi.client.focus
226 if sel then
227 -- Get all visible clients
228 local cls = visible(sel.screen)
229 local fcls = {}
230 -- Remove all non-normal clients
231 for idx, c in ipairs(cls) do
232 if focus.filter(c) or c == sel then
233 table.insert(fcls, c)
236 cls = fcls
237 -- Loop upon each client
238 for idx, c in ipairs(cls) do
239 if c == sel then
240 -- Cycle
241 return cls[util.cycle(#cls, idx + i)]
247 -- Return true whether client B is in the right direction
248 -- compared to client A.
249 -- @param dir The direction.
250 -- @param cA The first client.
251 -- @param cB The second client.
252 -- @return True if B is in the direction of A.
253 local function is_in_direction(dir, cA, cB)
254 local gA = cA:geometry()
255 local gB = cB:geometry()
256 if dir == "up" then
257 return gA.y > gB.y
258 elseif dir == "down" then
259 return gA.y < gB.y
260 elseif dir == "left" then
261 return gA.x > gB.x
262 elseif dir == "right" then
263 return gA.x < gB.x
265 return false
268 -- Calculate distance between two points.
269 -- i.e: if we want to move to the right, we will take the right border
270 -- of the currently focused client and the left side of the checked client.
271 -- This avoid the focus of an upper client when you move to the right in a
272 -- tilebottom layout with nmaster=2 and 5 clients open, for instance.
273 -- @param dir The direction.
274 -- @param cA The first client.
275 -- @param cB The second client.
276 -- @return The distance between the clients.
277 local function calculate_distance(dir, cA, cB)
278 local gA = cA:geometry()
279 local gB = cB:geometry()
281 if dir == "up" then
282 gB.y = gB.y + gB.height
283 elseif dir == "down" then
284 gA.y = gA.y + gA.height
285 elseif dir == "left" then
286 gB.x = gB.x + gB.width
287 elseif dir == "right" then
288 gA.x = gA.x + gA.width
291 return math.sqrt(math.pow(gB.x - gA.x, 2) + math.pow(gB.y - gA.y, 2))
294 -- Get the nearest client in the given direction.
295 -- @param dir The direction, can be either "up", "down", "left" or "right".
296 -- @param c Optional client to get a client relative to. Else focussed is used.
297 local function get_client_in_direction(dir, c)
298 local sel = c or capi.client.focus
299 if sel then
300 local geometry = sel:geometry()
301 local dist, dist_min
302 local target = nil
303 local cls = visible(sel.screen)
305 -- We check each client.
306 for i, c in ipairs(cls) do
307 -- Check geometry to see if client is located in the right direction.
308 if is_in_direction(dir, sel, c) then
310 -- Calculate distance between focused client and checked client.
311 dist = calculate_distance(dir, sel, c)
313 -- If distance is shorter then keep the client.
314 if not target or dist < dist_min then
315 target = c
316 dist_min = dist
321 return target
325 --- Focus a client by the given direction.
326 -- @param dir The direction, can be either "up", "down", "left" or "right".
327 -- @param c Optional client.
328 function focus.bydirection(dir, c)
329 local sel = c or capi.client.focus
330 if sel then
331 local target = get_client_in_direction(dir, sel)
333 -- If we found a client to focus, then do it.
334 if target then
335 capi.client.focus = target
340 --- Focus a client by its relative index.
341 -- @param i The index.
342 -- @param c Optional client.
343 function focus.byidx(i, c)
344 local target = next(i, c)
345 if target then
346 capi.client.focus = target
350 --- Swap a client with another client in the given direction
351 -- @param dir The direction, can be either "up", "down", "left" or "right".
352 -- @param c Optional client.
353 function swap.bydirection(dir, c)
354 local sel = c or capi.client.focus
355 if sel then
356 local target = get_client_in_direction(dir, sel)
358 -- If we found a client to swap with, then go for it
359 if target then
360 target:swap(sel)
365 --- Swap a client by its relative index.
366 -- @param i The index.
367 -- @param c Optional client, otherwise focused one is used.
368 function swap.byidx(i, c)
369 local sel = c or capi.client.focus
370 local target = next(i, sel)
371 if target then
372 target:swap(sel)
376 --- Cycle clients.
377 -- @param clockwise True to cycle clients clockwise.
378 -- @param screen Optional screen where to cycle clients.
379 function cycle(clockwise, screen)
380 local screen = screen or capi.mouse.screen
381 local cls = visible(screen)
382 -- We can't rotate without at least 2 clients, buddy.
383 if #cls >= 2 then
384 local c = table.remove(cls, 1)
385 if clockwise then
386 for i = #cls, 1, -1 do
387 c:swap(cls[i])
389 else
390 for _, rc in pairs(cls) do
391 c:swap(rc)
397 --- Get the master window.
398 -- @param screen Optional screen number, otherwise screen mouse is used.
399 -- @return The master window.
400 function getmaster(screen)
401 local s = screen or capi.mouse.screen
402 return visible(s)[1]
405 --- Set the client as slave: put it at the end of other windows.
406 -- @param c The window to set as slave.
407 function setslave(c)
408 local cls = visible(c.screen)
409 for k, v in pairs(cls) do
410 c:swap(v)
414 --- Move/resize a client relative to current coordinates.
415 -- @param x The relative x coordinate.
416 -- @param y The relative y coordinate.
417 -- @param w The relative width.
418 -- @param h The relative height.
419 -- @param c The optional client, otherwise focused one is used.
420 function moveresize(x, y, w, h, c)
421 local sel = c or capi.client.focus
422 local geometry = sel:geometry()
423 geometry['x'] = geometry['x'] + x
424 geometry['y'] = geometry['y'] + y
425 geometry['width'] = geometry['width'] + w
426 geometry['height'] = geometry['height'] + h
427 sel:geometry(geometry)
430 --- Move a client to a tag.
431 -- @param target The tag to move the client to.
432 -- @param c Optional client to move, otherwise the focused one is used.
433 function movetotag(target, c)
434 local sel = c or capi.client.focus
435 if sel and target.screen then
436 -- Set client on the same screen as the tag.
437 sel.screen = target.screen
438 sel:tags({ target })
442 --- Toggle a tag on a client.
443 -- @param target The tag to toggle.
444 -- @param c Optional client to toggle, otherwise the focused one is used.
445 function toggletag(target, c)
446 local sel = c or capi.client.focus
447 -- Check that tag and client screen are identical
448 if sel and sel.screen == target.screen then
449 local tags = sel:tags()
450 local index = nil;
451 for i, v in ipairs(tags) do
452 if v == target then
453 index = i
454 break
457 if index then
458 -- If it's the only tag for the window, stop.
459 if #tags == 1 then return end
460 tags[index] = nil
461 else
462 tags[#tags + 1] = target
464 sel:tags(tags)
468 --- Move a client to a screen. Default is next screen, cycling.
469 -- @param c The client to move.
470 -- @param s The screen number, default to current + 1.
471 function movetoscreen(c, s)
472 local sel = c or capi.client.focus
473 if sel then
474 local sc = capi.screen.count()
475 if not s then
476 s = sel.screen + 1
478 if s > sc then s = 1 elseif s < 1 then s = sc end
479 sel.screen = s
480 capi.mouse.coords(capi.screen[s].geometry)
481 capi.client.focus = sel
485 --- Mark a client, and then call 'marked' hook.
486 -- @param c The client to mark, the focused one if not specified.
487 -- @return True if the client has been marked. False if the client was already marked.
488 function mark(c)
489 local cl = c or capi.client.focus
490 if cl then
491 for k, v in pairs(data.marked) do
492 if cl == v then
493 return false
497 table.insert(data.marked, cl)
499 -- Call callback
500 cl:emit_signal("marked")
501 return true
505 --- Unmark a client and then call 'unmarked' hook.
506 -- @param c The client to unmark, or the focused one if not specified.
507 -- @return True if the client has been unmarked. False if the client was not marked.
508 function unmark(c)
509 local cl = c or capi.client.focus
511 for k, v in pairs(data.marked) do
512 if cl == v then
513 table.remove(data.marked, k)
514 cl:emit_signal("unmarked")
515 return true
519 return false
522 --- Check if a client is marked.
523 -- @param c The client to check, or the focused one otherwise.
524 function ismarked(c)
525 local cl = c or capi.client.focus
526 if cl then
527 for k, v in pairs(data.marked) do
528 if cl == v then
529 return true
533 return false
536 --- Toggle a client as marked.
537 -- @param c The client to toggle mark.
538 function togglemarked(c)
539 local cl = c or capi.client.focus
541 if not mark(c) then
542 unmark(c)
546 --- Return the marked clients and empty the marked table.
547 -- @return A table with all marked clients.
548 function getmarked()
549 for k, v in pairs(data.marked) do
550 v:emit_signal("unmarked")
553 t = data.marked
554 data.marked = {}
555 return t
558 --- Set a client floating state, overriding auto-detection.
559 -- Floating client are not handled by tiling layouts.
560 -- @param c A client.
561 -- @param s True or false.
562 function floating.set(c, s)
563 local c = c or capi.client.focus
564 if c and property.get(c, "floating") ~= s then
565 property.set(c, "floating", s)
566 local screen = c.screen
567 if s == true then
568 c:geometry(property.get(c, "floating_geometry"))
570 c.screen = screen
574 local function store_floating_geometry(c)
575 if floating.get(c) then
576 property.set(c, "floating_geometry", c:geometry())
580 -- Store the initial client geometry.
581 capi.client.add_signal("new", function(c)
582 local function store_init_geometry(c)
583 property.set(c, "floating_geometry", c:geometry())
584 c:remove_signal("property::geometry", store_init_geometry)
586 c:add_signal("property::geometry", store_init_geometry)
587 end)
589 capi.client.add_signal("manage", function(c)
590 c:add_signal("property::geometry", store_floating_geometry)
591 end)
593 --- Return if a client has a fixe size or not.
594 -- @param c The client.
595 function isfixed(c)
596 local c = c or capi.client.focus
597 if not c then return end
598 local h = c.size_hints
599 if h.min_width and h.max_width
600 and h.max_height and h.min_height
601 and h.min_width > 0 and h.max_width > 0
602 and h.max_height > 0 and h.min_height > 0
603 and h.min_width == h.max_width
604 and h.min_height == h.max_height then
605 return true
607 return false
610 --- Get a client floating state.
611 -- @param c A client.
612 -- @return True or false. Note that some windows might be floating even if you
613 -- did not set them manually. For example, windows with a type different than
614 -- normal.
615 function floating.get(c)
616 local c = c or capi.client.focus
617 if c then
618 local value = property.get(c, "floating")
619 if value ~= nil then
620 return value
622 if c.type ~= "normal"
623 or c.fullscreen
624 or c.maximized_vertical
625 or c.maximized_horizontal
626 or isfixed(c) then
627 return true
629 return false
633 --- Toggle the floating state of a client between 'auto' and 'true'.
634 -- @param c A client.
635 function floating.toggle(c)
636 local c = c or capi.client.focus
637 -- If it has been set to floating
638 if floating.get(c) then
639 floating.set(c, false)
640 else
641 floating.set(c, true)
645 --- Remove the floating information on a client.
646 -- @param c The client.
647 function floating.delete(c)
648 floating.set(c, nil)
651 --- Restore (=unminimize) a random client.
652 -- @param s The screen to use.
653 -- @return True if some client was restored.
654 function restore(s)
655 local s = s or (capi.client.focus and capi.client.focus.screen) or capi.mouse.screen
656 local cls = capi.client.get(s)
657 local tags = tag.selectedlist(s)
658 local mcls = {}
659 for k, c in pairs(cls) do
660 local ctags = c:tags()
661 if c.minimized then
662 for k, t in ipairs(tags) do
663 if util.table.hasitem(ctags, t) then
664 c.minimized = false
665 return true
670 return false
673 -- Normalize a set of numbers to 1
674 -- @param set the set of numbers to normalize
675 -- @param num the number of numbers to normalize
676 local function normalize(set, num)
677 local num = num or #set
678 local total = 0
679 if num then
680 for i = 1,num do
681 total = total + set[i]
683 for i = 1,num do
684 set[i] = set[i] / total
686 else
687 for i,v in ipairs(set) do
688 total = total + v
691 for i,v in ipairs(set) do
692 set[i] = v / total
697 --- Calculate a client's column number, index in that column, and
698 -- number of visible clients in this column.
699 -- @param c the client
700 -- @return col the column number
701 -- @return idx index of the client in the column
702 -- @return num the number of visible clients in the column
703 function idx(c)
704 local c = c or capi.client.focus
705 if not c then return end
707 local clients = tiled(c.screen)
708 local idx = nil
709 for k, cl in ipairs(clients) do
710 if cl == c then
711 idx = k
712 break
716 local t = tag.selected(c.screen)
717 local nmaster = tag.getnmaster(t)
718 if idx <= nmaster then
719 return {idx = idx, col=0, num=nmaster}
721 local nother = #clients - nmaster
722 idx = idx - nmaster
724 -- rather than regenerate the column number we can calculate it
725 -- based on the how the tiling algorithm places clients we calculate
726 -- the column, we could easily use the for loop in the program but we can
727 -- calculate it.
728 local ncol = tag.getncol(t)
729 -- minimum number of clients per column
730 local percol = math.floor(nother / ncol)
731 -- number of columns with an extra client
732 local overcol = math.mod(nother, ncol)
733 -- number of columns filled with [percol] clients
734 local regcol = ncol - overcol
736 local col = math.floor( (idx - 1) / percol) + 1
737 if col > regcol then
738 -- col = math.floor( (idx - (percol*regcol) - 1) / (percol + 1) ) + regcol + 1
739 -- simplified
740 col = math.floor( (idx + regcol + percol) / (percol+1) )
741 -- calculate the index in the column
742 idx = idx - percol*regcol - (col - regcol - 1) * (percol+1)
743 percol = percol+1
744 else
745 idx = idx - percol*(col-1)
748 return {idx = idx, col=col, num=percol}
752 --- Set the window factor of a client
753 -- @param wfact the window factor value
754 -- @param c the client
755 function setwfact(wfact, c)
756 -- get the currently selected window
757 local c = c or capi.client.focus
758 if not c or not c:isvisible() then return end
760 local t = tag.selected(c.screen)
761 local w = idx(c)
763 local cls = tiled(t.screen)
764 local nmaster = tag.getnmaster(t)
766 -- n is the number of windows currently visible for which we have to be concerned with the properties
767 local data = tag.getproperty(t, "windowfact") or {}
768 local colfact = data[w.col]
770 colfact[w.idx] = wfact
771 rest = 1-wfact
773 -- calculate the current denominator
774 local total = 0
775 for i = 1,w.num do
776 if i ~= w.idx then
777 total = total + colfact[i]
781 -- normalize the windows
782 for i = 1,w.num do
783 if i ~= w.idx then
784 colfact[i] = (colfact[i] * rest) / total
788 t:emit_signal("property::windowfact")
791 --- Increment a client's window factor
792 -- @param add amount to increase the client's window
793 -- @param c the client
794 function incwfact(add, c)
795 local c = c or capi.client.focus
796 if not c then return end
798 local t = tag.selected(c.screen)
800 local w = idx(c)
802 local nmaster = tag.getnmaster(t)
803 local data = tag.getproperty(t, "windowfact") or {}
804 local colfact = data[w.col]
805 curr = colfact[w.idx] or 1
806 colfact[w.idx] = curr + add
808 -- keep our ratios normalized
809 normalize(colfact, w.num)
811 t:emit_signal("property::windowfact")
814 --- Get a client dockable state.
815 -- @param c A client.
816 -- @return True or false. Note that some windows might be dockable even if you
817 -- did not set them manually. For example, windows with a type "utility", "toolbar"
818 -- or "dock"
819 function dockable.get(c)
820 local value = property.get(c, "dockable")
822 -- Some sane defaults
823 if value == nil then
824 if (c.type == "utility" or c.type == "toolbar" or c.type == "dock") then
825 value = true
826 else
827 value = false
831 return value
834 --- Set a client dockable state, overriding auto-detection.
835 -- With this enabled you can dock windows by moving them from the center
836 -- to the edge of the workarea.
837 -- @param c A client.
838 -- @param value True or false.
839 function dockable.set(c, value)
840 property.set(c, "dockable", value)
843 --- Get a client property.
844 -- @param c The client.
845 -- @param prop The property name.
846 -- @return The property.
847 function property.get(c, prop)
848 if data.properties[c] then
849 return data.properties[c][prop]
853 --- Set a client property.
854 -- This properties are internal to awful. Some are used to move clients, etc.
855 -- @param c The client.
856 -- @param prop The property name.
857 -- @param value The value.
858 function property.set(c, prop, value)
859 if not data.properties[c] then
860 data.properties[c] = {}
862 data.properties[c][prop] = value
863 c:emit_signal("property::" .. prop)
866 -- Register standards signals
867 capi.client.add_signal("focus", focus.history.add)
868 capi.client.add_signal("unmanage", focus.history.delete)
870 capi.client.add_signal("manage", function(c) c:add_signal("property::urgent", urgent.add) end)
871 capi.client.add_signal("focus", urgent.delete)
872 capi.client.add_signal("unmanage", urgent.delete)
874 capi.client.add_signal("unmanage", floating.delete)
876 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80