awful.client: fix initial geometry storage (FS#608)
[awesome.git] / lib / awful / client.lua.in
blob2c6bf433da7e073407c757c0090debf2452cd521
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 function urgent.jumpto()
60 local c = urgent.get()
61 if c then
62 local s = capi.client.focus and capi.client.focus.screen or capi.mouse.screen
63 -- focus the screen
64 if s ~= c.screen then
65 capi.mouse.screen = c.screen
66 end
67 -- focus the tag only if the client is not sticky
68 if not c.sticky then
69 tag.viewonly(c:tags()[1])
70 end
71 -- focus the client
72 capi.client.focus = c
73 c:raise()
74 end
75 end
77 --- Adds client to urgent stack.
78 -- @param c The client object.
79 -- @param prop The property which is updated.
80 function urgent.add(c, prop)
81 if type(c) == "client" and prop == "urgent" and c.urgent then
82 table.insert(data.urgent, c)
83 end
84 end
86 --- Remove client from urgent stack.
87 -- @param c The client object.
88 function urgent.delete(c)
89 for k, cl in ipairs(data.urgent) do
90 if c == cl then
91 table.remove(data.urgent, k)
92 break
93 end
94 end
95 end
97 --- Remove a client from the focus history
98 -- @param c The client that must be removed.
99 function focus.history.delete(c)
100 for k, v in ipairs(data.focus) do
101 if v == c then
102 table.remove(data.focus, k)
103 break
108 --- Filter out window that we do not want handled by focus.
109 -- This usually means that desktop, dock and splash windows are
110 -- not registered and cannot get focus.
111 -- @param c A client.
112 -- @return The same client if it's ok, nil otherwise.
113 function focus.filter(c)
114 if c.type == "desktop"
115 or c.type == "dock"
116 or c.type == "splash" then
117 return nil
119 return c
122 --- Update client focus history.
123 -- @param c The client that has been focused.
124 function focus.history.add(c)
125 if focus.filter(c) then
126 -- Remove the client if its in stack
127 focus.history.delete(c)
128 -- Record the client has latest focused
129 table.insert(data.focus, 1, c)
133 --- Get the latest focused client for a screen in history.
134 -- @param screen The screen number to look for.
135 -- @param idx The index: 0 will return first candidate,
136 -- 1 will return second, etc.
137 -- @return A client.
138 function focus.history.get(screen, idx)
139 -- When this counter is equal to idx, we return the client
140 local counter = 0
141 local vc = visible(screen)
142 for k, c in ipairs(data.focus) do
143 if c.screen == screen then
144 for j, vcc in ipairs(vc) do
145 if vcc == c then
146 if counter == idx then
147 return c
149 -- We found one, increment the counter only.
150 counter = counter + 1
151 break
156 -- Argh nobody found in history, give the first one visible if there is one
157 -- that passes the filter.
158 if counter == 0 then
159 for k, v in ipairs(vc) do
160 if focus.filter(v) then
161 return v
167 --- Focus the previous client in history.
168 function focus.history.previous()
169 local sel = capi.client.focus
170 local s
171 if sel then
172 s = sel.screen
173 else
174 s = capi.mouse.screen
176 local c = focus.history.get(s, 1)
177 if c then capi.client.focus = c end
180 --- Get visible clients from a screen.
181 -- @param screen The screen number, or nil for all screens.
182 -- @return A table with all visible clients.
183 function visible(screen)
184 local cls = capi.client.get(screen)
185 local vcls = {}
186 for k, c in pairs(cls) do
187 if c:isvisible() then
188 table.insert(vcls, c)
191 return vcls
194 --- Get visible and tiled clients
195 -- @param screen The screen number, or nil for all screens.
196 -- @return A tabl with all visible and tiled clients.
197 function tiled(screen)
198 local clients = visible(screen)
199 local tclients = {}
200 -- Remove floating clients
201 for k, c in pairs(clients) do
202 if not floating.get(c) then
203 table.insert(tclients, c)
206 return tclients
209 --- Get a client by its relative index to the focused window.
210 -- @usage Set i to 1 to get next, -1 to get previous.
211 -- @param i The index.
212 -- @param c Optional client.
213 -- @return A client, or nil if no client is available.
214 function next(i, c)
215 -- Get currently focused client
216 local sel = c or capi.client.focus
217 if sel then
218 -- Get all visible clients
219 local cls = visible(sel.screen)
220 local fcls = {}
221 -- Remove all non-normal clients
222 for idx, c in ipairs(cls) do
223 if focus.filter(c) or c == sel then
224 table.insert(fcls, c)
227 cls = fcls
228 -- Loop upon each client
229 for idx, c in ipairs(cls) do
230 if c == sel then
231 -- Cycle
232 return cls[util.cycle(#cls, idx + i)]
238 -- Return true whether client B is in the right direction
239 -- compared to client A.
240 -- @param dir The direction.
241 -- @param cA The first client.
242 -- @param cB The second client.
243 -- @return True if B is in the direction of A.
244 local function is_in_direction(dir, cA, cB)
245 local gA = cA:geometry()
246 local gB = cB:geometry()
247 if dir == "up" then
248 return gA.y > gB.y
249 elseif dir == "down" then
250 return gA.y < gB.y
251 elseif dir == "left" then
252 return gA.x > gB.x
253 elseif dir == "right" then
254 return gA.x < gB.x
256 return false
259 -- Calculate distance between two points.
260 -- i.e: if we want to move to the right, we will take the right border
261 -- of the currently focused client and the left side of the checked client.
262 -- This avoid the focus of an upper client when you move to the right in a
263 -- tilebottom layout with nmaster=2 and 5 clients open, for instance.
264 -- @param dir The direction.
265 -- @param cA The first client.
266 -- @param cB The second client.
267 -- @return The distance between the clients.
268 local function calculate_distance(dir, cA, cB)
269 local gA = cA:geometry()
270 local gB = cB:geometry()
272 if dir == "up" then
273 gB.y = gB.y + gB.height
274 elseif dir == "down" then
275 gA.y = gA.y + gA.height
276 elseif dir == "left" then
277 gB.x = gB.x + gB.width
278 elseif dir == "right" then
279 gA.x = gA.x + gA.width
282 return math.sqrt(math.pow(gB.x - gA.x, 2) + math.pow(gB.y - gA.y, 2))
285 -- Get the nearest client in the given direction.
286 -- @param dir The direction, can be either "up", "down", "left" or "right".
287 -- @param c Optional client to get a client relative to. Else focussed is used.
288 local function get_client_in_direction(dir, c)
289 local sel = c or capi.client.focus
290 if sel then
291 local geometry = sel:geometry()
292 local dist, dist_min
293 local target = nil
294 local cls = visible(sel.screen)
296 -- We check each client.
297 for i, c in ipairs(cls) do
298 -- Check geometry to see if client is located in the right direction.
299 if is_in_direction(dir, sel, c) then
301 -- Calculate distance between focused client and checked client.
302 dist = calculate_distance(dir, sel, c)
304 -- If distance is shorter then keep the client.
305 if not target or dist < dist_min then
306 target = c
307 dist_min = dist
312 return target
316 --- Focus a client by the given direction.
317 -- @param dir The direction, can be either "up", "down", "left" or "right".
318 -- @param c Optional client.
319 function focus.bydirection(dir, c)
320 local sel = c or capi.client.focus
321 if sel then
322 local target = get_client_in_direction(dir, sel)
324 -- If we found a client to focus, then do it.
325 if target then
326 capi.client.focus = target
331 --- Focus a client by its relative index.
332 -- @param i The index.
333 -- @param c Optional client.
334 function focus.byidx(i, c)
335 local target = next(i, c)
336 if target then
337 capi.client.focus = target
341 --- Swap a client with another client in the given direction
342 -- @param dir The direction, can be either "up", "down", "left" or "right".
343 -- @param c Optional client.
344 function swap.bydirection(dir, c)
345 local sel = c or capi.client.focus
346 if sel then
347 local target = get_client_in_direction(dir, sel)
349 -- If we found a client to swap with, then go for it
350 if target then
351 target:swap(sel)
356 --- Swap a client by its relative index.
357 -- @param i The index.
358 -- @param c Optional client, otherwise focused one is used.
359 function swap.byidx(i, c)
360 local sel = c or capi.client.focus
361 local target = next(i, sel)
362 if target then
363 target:swap(sel)
367 --- Cycle clients.
368 -- @param clockwise True to cycle clients clockwise.
369 -- @param screen Optional screen where to cycle clients.
370 function cycle(clockwise, screen)
371 local screen = screen or capi.mouse.screen
372 local cls = visible(screen)
373 -- We can't rotate without at least 2 clients, buddy.
374 if #cls >= 2 then
375 local c = table.remove(cls, 1)
376 if clockwise then
377 for i = #cls, 1, -1 do
378 c:swap(cls[i])
380 else
381 for _, rc in pairs(cls) do
382 c:swap(rc)
388 --- Get the master window.
389 -- @param screen Optional screen number, otherwise screen mouse is used.
390 -- @return The master window.
391 function getmaster(screen)
392 local s = screen or capi.mouse.screen
393 return visible(s)[1]
396 --- Set the client as slave: put it at the end of other windows.
397 -- @param c The window to set as slave.
398 function setslave(c)
399 local cls = visible(c.screen)
400 for k, v in pairs(cls) do
401 c:swap(v)
405 --- Move/resize a client relative to current coordinates.
406 -- @param x The relative x coordinate.
407 -- @param y The relative y coordinate.
408 -- @param w The relative width.
409 -- @param h The relative height.
410 -- @param c The optional client, otherwise focused one is used.
411 function moveresize(x, y, w, h, c)
412 local sel = c or capi.client.focus
413 local geometry = sel:geometry()
414 geometry['x'] = geometry['x'] + x
415 geometry['y'] = geometry['y'] + y
416 geometry['width'] = geometry['width'] + w
417 geometry['height'] = geometry['height'] + h
418 sel:geometry(geometry)
421 --- Move a client to a tag.
422 -- @param target The tag to move the client to.
423 -- @param c Optional client to move, otherwise the focused one is used.
424 function movetotag(target, c)
425 local sel = c or capi.client.focus
426 if sel and target.screen then
427 -- Set client on the same screen as the tag.
428 sel.screen = target.screen
429 sel:tags({ target })
433 --- Toggle a tag on a client.
434 -- @param target The tag to toggle.
435 -- @param c Optional client to toggle, otherwise the focused one is used.
436 function toggletag(target, c)
437 local sel = c or capi.client.focus
438 -- Check that tag and client screen are identical
439 if sel and sel.screen == target.screen then
440 local tags = sel:tags()
441 local index = nil;
442 for i, v in ipairs(tags) do
443 if v == target then
444 index = i
445 break
448 if index then
449 -- If it's the only tag for the window, stop.
450 if #tags == 1 then return end
451 tags[index] = nil
452 else
453 tags[#tags + 1] = target
455 sel:tags(tags)
459 --- Move a client to a screen. Default is next screen, cycling.
460 -- @param c The client to move.
461 -- @param s The screen number, default to current + 1.
462 function movetoscreen(c, s)
463 local sel = c or capi.client.focus
464 if sel then
465 local sc = capi.screen.count()
466 if not s then
467 s = sel.screen + 1
469 if s > sc then s = 1 elseif s < 1 then s = sc end
470 sel.screen = s
471 capi.mouse.coords(capi.screen[s].geometry)
472 capi.client.focus = sel
476 --- Mark a client, and then call 'marked' hook.
477 -- @param c The client to mark, the focused one if not specified.
478 -- @return True if the client has been marked. False if the client was already marked.
479 function mark(c)
480 local cl = c or capi.client.focus
481 if cl then
482 for k, v in pairs(data.marked) do
483 if cl == v then
484 return false
488 table.insert(data.marked, cl)
490 -- Call callback
491 cl:emit_signal("marked")
492 return true
496 --- Unmark a client and then call 'unmarked' hook.
497 -- @param c The client to unmark, or the focused one if not specified.
498 -- @return True if the client has been unmarked. False if the client was not marked.
499 function unmark(c)
500 local cl = c or capi.client.focus
502 for k, v in pairs(data.marked) do
503 if cl == v then
504 table.remove(data.marked, k)
505 cl:emit_signal("unmarked")
506 return true
510 return false
513 --- Check if a client is marked.
514 -- @param c The client to check, or the focused one otherwise.
515 function ismarked(c)
516 local cl = c or capi.client.focus
517 if cl then
518 for k, v in pairs(data.marked) do
519 if cl == v then
520 return true
524 return false
527 --- Toggle a client as marked.
528 -- @param c The client to toggle mark.
529 function togglemarked(c)
530 local cl = c or capi.client.focus
532 if not mark(c) then
533 unmark(c)
537 --- Return the marked clients and empty the marked table.
538 -- @return A table with all marked clients.
539 function getmarked()
540 for k, v in pairs(data.marked) do
541 v:emit_signal("unmarked")
544 t = data.marked
545 data.marked = {}
546 return t
549 --- Set a client floating state, overriding auto-detection.
550 -- Floating client are not handled by tiling layouts.
551 -- @param c A client.
552 -- @param s True or false.
553 function floating.set(c, s)
554 local c = c or capi.client.focus
555 if c and property.get(c, "floating") ~= s then
556 property.set(c, "floating", s)
557 local screen = c.screen
558 if s == true then
559 c:geometry(property.get(c, "floating_geometry"))
561 c.screen = screen
565 local function store_floating_geometry(c)
566 if floating.get(c) then
567 property.set(c, "floating_geometry", c:geometry())
571 -- Store the initial client geometry.
572 capi.client.add_signal("new", function(c)
573 local function store_init_geometry(c)
574 property.set(c, "floating_geometry", c:geometry())
575 c:remove_signal("property::geometry", store_init_geometry)
577 c:add_signal("property::geometry", store_init_geometry)
578 end)
580 capi.client.add_signal("manage", function(c)
581 c:add_signal("property::geometry", store_floating_geometry)
582 end)
584 --- Return if a client has a fixe size or not.
585 -- @param c The client.
586 function isfixed(c)
587 local c = c or capi.client.focus
588 if not c then return end
589 local h = c.size_hints
590 if h.min_width and h.max_width
591 and h.max_height and h.min_height
592 and h.min_width > 0 and h.max_width > 0
593 and h.max_height > 0 and h.min_height > 0
594 and h.min_width == h.max_width
595 and h.min_height == h.max_height then
596 return true
598 return false
601 --- Get a client floating state.
602 -- @param c A client.
603 -- @return True or false. Note that some windows might be floating even if you
604 -- did not set them manually. For example, windows with a type different than
605 -- normal.
606 function floating.get(c)
607 local c = c or capi.client.focus
608 if c then
609 local value = property.get(c, "floating")
610 if value ~= nil then
611 return value
613 if c.type ~= "normal"
614 or c.fullscreen
615 or c.maximized_vertical
616 or c.maximized_horizontal
617 or isfixed(c) then
618 return true
620 return false
624 --- Toggle the floating state of a client between 'auto' and 'true'.
625 -- @param c A client.
626 function floating.toggle(c)
627 local c = c or capi.client.focus
628 -- If it has been set to floating
629 if property.get(c, "floating") then
630 floating.set(c, nil)
631 else
632 floating.set(c, true)
636 --- Remove the floating information on a client.
637 -- @param c The client.
638 function floating.delete(c)
639 floating.set(c, nil)
642 -- Normalize a set of numbers to 1
643 -- @param set the set of numbers to normalize
644 -- @param num the number of numbers to normalize
645 local function normalize(set, num)
646 local num = num or #set
647 local total = 0
648 if num then
649 for i = 1,num do
650 total = total + set[i]
652 for i = 1,num do
653 set[i] = set[i] / total
655 else
656 for i,v in ipairs(set) do
657 total = total + v
660 for i,v in ipairs(set) do
661 set[i] = v / total
666 --- Calculate a client's column number, index in that column, and
667 -- number of visible clients in this column.
668 -- @param c the client
669 -- @return col the column number
670 -- @return idx index of the client in the column
671 -- @return num the number of visible clients in the column
672 local function idx(c)
673 local c = c or capi.client.focus
674 if not c then return end
676 local clients = tiled(c.screen)
677 local idx = nil
678 for k, cl in ipairs(clients) do
679 if cl == c then
680 idx = k
681 break
685 local t = tag.selected(c.screen)
686 local nmaster = tag.getnmaster(t)
687 if idx <= nmaster then
688 return {idx = idx, col=0, num=nmaster}
690 local nother = #clients - nmaster
691 idx = idx - nmaster
693 -- rather than regenerate the column number we can calculate it
694 -- based on the how the tiling algorithm places clients we calculate
695 -- the column, we could easily use the for loop in the program but we can
696 -- calculate it.
697 local ncol = tag.getncol(t)
698 -- minimum number of clients per column
699 local percol = math.floor(nother / ncol)
700 -- number of columns with an extra client
701 local overcol = math.mod(nother, ncol)
702 -- number of columns filled with [percol] clients
703 local regcol = ncol - overcol
705 local col = math.floor( (idx - 1) / percol) + 1
706 if col > regcol then
707 -- col = math.floor( (idx - (percol*regcol) - 1) / (percol + 1) ) + regcol + 1
708 -- simplified
709 col = math.floor( (idx + regcol + percol) / (percol+1) )
710 -- calculate the index in the column
711 idx = idx - percol*regcol - (col - regcol - 1) * (percol+1)
712 percol = percol+1
713 else
714 idx = idx - percol*(col-1)
717 return {idx = idx, col=col, num=percol}
721 --- Set the window factor of a client
722 -- @param wfact the window factor value
723 -- @param c the client
724 function setwfact(wfact, c)
725 -- get the currently selected window
726 local c = c or capi.client.focus
727 if not c then return end
729 local t = tag.selected(c.screen)
730 local w = idx(c)
732 local cls = tiled(t.screen)
733 local nmaster = tag.getnmaster(t)
735 -- n is the number of windows currently visible for which we have to be concerned with the properties
736 local data = tag.getproperty(t, "windowfact") or {}
737 local colfact = data[w.col]
739 colfact[w.idx] = wfact
740 rest = 1-wfact
742 -- calculate the current denominator
743 local total = 0
744 for i = 1,w.num do
745 if i ~= w.idx then
746 total = total + colfact[i]
750 -- normalize the windows
751 for i = 1,w.num do
752 if i ~= w.idx then
753 colfact[i] = (colfact[i] * rest) / total
757 t:emit_signal("property::windowfact")
760 --- Increment a client's window factor
761 -- @param add amount to increase the client's window
762 -- @param c the client
763 function incwfact(add, c)
764 local c = c or capi.client.focus
765 if not c then return end
767 local t = tag.selected(c.screen)
769 local w = idx(c)
771 local nmaster = tag.getnmaster(t)
772 local data = tag.getproperty(t, "windowfact") or {}
773 local colfact = data[w.col]
774 curr = colfact[w.idx] or 1
775 colfact[w.idx] = curr + add
777 -- keep our ratios normalized
778 normalize(colfact, w.num)
780 t:emit_signal("property::windowfact")
783 --- Get a client dockable state.
784 -- @param c A client.
785 -- @return True or false. Note that some windows might be dockable even if you
786 -- did not set them manually. For example, windows with a type "utility", "toolbar"
787 -- or "dock"
788 function dockable.get(c)
789 local value = property.get(c, "dockable")
791 -- Some sane defaults
792 if value == nil then
793 if (c.type == "utility" or c.type == "toolbar" or c.type == "dock") then
794 value = true
795 else
796 value = false
800 return value
803 --- Set a client dockable state, overriding auto-detection.
804 -- With this enabled you can dock windows by moving them from the center
805 -- to the edge of the workarea.
806 -- @param c A client.
807 -- @param value True or false.
808 function dockable.set(c, value)
809 property.set(c, "dockable", value)
812 --- Get a client property.
813 -- @param c The client.
814 -- @param prop The property name.
815 -- @return The property.
816 function property.get(c, prop)
817 if data.properties[c] then
818 return data.properties[c][prop]
822 --- Set a client property.
823 -- This properties are internal to awful. Some are used to move clients, etc.
824 -- @param c The client.
825 -- @param prop The property name.
826 -- @param value The value.
827 function property.set(c, prop, value)
828 if not data.properties[c] then
829 data.properties[c] = {}
831 data.properties[c][prop] = value
832 c:emit_signal("property::" .. prop)
835 -- Register standards signals
836 capi.client.add_signal("focus", focus.history.add)
837 capi.client.add_signal("unmanage", focus.history.delete)
839 capi.client.add_signal("manage", function(c) c:add_signal("property::urgent", urgent.add) end)
840 capi.client.add_signal("focus", urgent.delete)
841 capi.client.add_signal("unmanage", urgent.delete)
843 capi.client.add_signal("unmanage", floating.delete)
845 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80