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")
15 local setmetatable
= setmetatable
23 --- Useful client manipulation functions.
29 client
.data
.focus
= {}
30 client
.data
.urgent
= {}
31 client
.data
.marked
= {}
32 client
.data
.properties
= setmetatable({}, { __mode
= 'k' })
37 client
.focus
.history
= {}
44 -- Jump to the given client. Takes care of focussing the screen, the right tag,
46 -- @param c the client to jump to
47 -- @param merge If true then merge tags when clients are not visible.
48 function client
.jumpto(c
, merge
)
49 local s
= capi
.client
.focus
and capi
.client
.focus
.screen
or capi
.mouse
.screen
52 capi
.mouse
.screen
= c
.screen
55 -- Try to make client visible, this also covers e.g. sticky
57 if t
and not c
:isvisible() then
70 --- Get the first client that got the urgent hint.
71 -- @return The first urgent client.
72 function client
.urgent
.get()
73 if #client
.data
.urgent
> 0 then
74 return client
.data
.urgent
[1]
76 -- fallback behaviour: iterate through clients and get the first urgent
77 local clients
= capi
.client
.get()
78 for k
, cl
in pairs(clients
) do
86 --- Jump to the client that received the urgent hint first.
87 -- @param merge If true then merge tags when clients are not visible.
88 function client
.urgent
.jumpto(merge
)
89 local c
= client
.urgent
.get()
91 client
.jumpto(c
, merge
)
95 --- Adds client to urgent stack.
96 -- @param c The client object.
97 -- @param prop The property which is updated.
98 function client
.urgent
.add(c
, prop
)
99 if type(c
) == "client" and prop
== "urgent" and c
.urgent
then
100 table.insert(client
.data
.urgent
, c
)
104 --- Remove client from urgent stack.
105 -- @param c The client object.
106 function client
.urgent
.delete(c
)
107 for k
, cl
in ipairs(client
.data
.urgent
) do
109 table.remove(client
.data
.urgent
, k
)
115 --- Remove a client from the focus history
116 -- @param c The client that must be removed.
117 function client
.focus
.history
.delete(c
)
118 for k
, v
in ipairs(client
.data
.focus
) do
120 table.remove(client
.data
.focus
, k
)
126 --- Filter out window that we do not want handled by focus.
127 -- This usually means that desktop, dock and splash windows are
128 -- not registered and cannot get focus.
129 -- @param c A client.
130 -- @return The same client if it's ok, nil otherwise.
131 function client
.focus
.filter(c
)
132 if c
.type == "desktop"
134 or c
.type == "splash"
135 or not c
.focusable
then
141 --- Update client focus history.
142 -- @param c The client that has been focused.
143 function client
.focus
.history
.add(c
)
144 -- Remove the client if its in stack
145 client
.focus
.history
.delete(c
)
146 -- Record the client has latest focused
147 table.insert(client
.data
.focus
, 1, c
)
150 --- Get the latest focused client for a screen in history.
151 -- @param screen The screen number to look for.
152 -- @param idx The index: 0 will return first candidate,
153 -- 1 will return second, etc.
155 function client
.focus
.history
.get(screen
, idx
)
156 -- When this counter is equal to idx, we return the client
158 local vc
= client
.visible(screen
)
159 for k
, c
in ipairs(client
.data
.focus
) do
160 if c
.screen
== screen
then
161 for j
, vcc
in ipairs(vc
) do
163 if counter
== idx
then
166 -- We found one, increment the counter only.
167 counter
= counter
+ 1
173 -- Argh nobody found in history, give the first one visible if there is one
174 -- that passes the filter.
176 for k
, v
in ipairs(vc
) do
177 if client
.focus
.filter(v
) then
184 --- Focus the previous client in history.
185 function client
.focus
.history
.previous()
186 local sel
= capi
.client
.focus
191 s
= capi
.mouse
.screen
193 local c
= client
.focus
.history
.get(s
, 1)
194 if c
then capi
.client
.focus
= c
end
197 --- Get visible clients from a screen.
198 -- @param screen The screen number, or nil for all screens.
199 -- @return A table with all visible clients.
200 function client
.visible(screen
)
201 local cls
= capi
.client
.get(screen
)
203 for k
, c
in pairs(cls
) do
204 if c
:isvisible() then
205 table.insert(vcls
, c
)
211 --- Get visible and tiled clients
212 -- @param screen The screen number, or nil for all screens.
213 -- @return A table with all visible and tiled clients.
214 function client
.tiled(screen
)
215 local clients
= client
.visible(screen
)
217 -- Remove floating clients
218 for k
, c
in pairs(clients
) do
219 if not client
.floating
.get(c
) then
220 table.insert(tclients
, c
)
226 --- Get a client by its relative index to the focused window.
227 -- @usage Set i to 1 to get next, -1 to get previous.
228 -- @param i The index.
229 -- @param c Optional client.
230 -- @return A client, or nil if no client is available.
231 function client
.next(i
, c
)
232 -- Get currently focused client
233 local sel
= c
or capi
.client
.focus
235 -- Get all visible clients
236 local cls
= client
.visible(sel
.screen
)
238 -- Remove all non-normal clients
239 for idx
, c
in ipairs(cls
) do
240 if client
.focus
.filter(c
) or c
== sel
then
241 table.insert(fcls
, c
)
245 -- Loop upon each client
246 for idx
, c
in ipairs(cls
) do
249 return cls
[util
.cycle(#cls
, idx
+ i
)]
255 -- Return true whether client B is in the right direction
256 -- compared to client A.
257 -- @param dir The direction.
258 -- @param cA The first client.
259 -- @param cB The second client.
260 -- @return True if B is in the direction of A.
261 local function is_in_direction(dir
, cA
, cB
)
262 local gA
= cA
:geometry()
263 local gB
= cB
:geometry()
266 elseif dir
== "down" then
268 elseif dir
== "left" then
270 elseif dir
== "right" then
276 -- Calculate distance between two points.
277 -- i.e: if we want to move to the right, we will take the right border
278 -- of the currently focused client and the left side of the checked client.
279 -- This avoid the focus of an upper client when you move to the right in a
280 -- tilebottom layout with nmaster=2 and 5 clients open, for instance.
281 -- @param dir The direction.
282 -- @param cA The first client.
283 -- @param cB The second client.
284 -- @return The distance between the clients.
285 local function calculate_distance(dir
, cA
, cB
)
286 local gA
= cA
:geometry()
287 local gB
= cB
:geometry()
290 gB
.y
= gB
.y
+ gB
.height
291 elseif dir
== "down" then
292 gA
.y
= gA
.y
+ gA
.height
293 elseif dir
== "left" then
294 gB
.x
= gB
.x
+ gB
.width
295 elseif dir
== "right" then
296 gA
.x
= gA
.x
+ gA
.width
299 return math
.sqrt(math
.pow(gB
.x
- gA
.x
, 2) + math
.pow(gB
.y
- gA
.y
, 2))
302 -- Get the nearest client in the given direction.
303 -- @param dir The direction, can be either "up", "down", "left" or "right".
304 -- @param c Optional client to get a client relative to. Else focussed is used.
305 local function get_client_in_direction(dir
, c
)
306 local sel
= c
or capi
.client
.focus
308 local geometry
= sel
:geometry()
311 local cls
= client
.visible(sel
.screen
)
313 -- We check each client.
314 for i
, c
in ipairs(cls
) do
315 -- Check geometry to see if client is located in the right direction.
316 if is_in_direction(dir
, sel
, c
) then
318 -- Calculate distance between focused client and checked client.
319 dist
= calculate_distance(dir
, sel
, c
)
321 -- If distance is shorter then keep the client.
322 if not target
or dist
< dist_min
then
333 --- Focus a client by the given direction.
334 -- @param dir The direction, can be either "up", "down", "left" or "right".
335 -- @param c Optional client.
336 function client
.focus
.bydirection(dir
, c
)
337 local sel
= c
or capi
.client
.focus
339 local target
= get_client_in_direction(dir
, sel
)
341 -- If we found a client to focus, then do it.
343 capi
.client
.focus
= target
348 --- Focus a client by its relative index.
349 -- @param i The index.
350 -- @param c Optional client.
351 function client
.focus
.byidx(i
, c
)
352 local target
= client
.next(i
, c
)
354 capi
.client
.focus
= target
358 --- Swap a client with another client in the given direction
359 -- @param dir The direction, can be either "up", "down", "left" or "right".
360 -- @param c Optional client.
361 function client
.swap
.bydirection(dir
, c
)
362 local sel
= c
or capi
.client
.focus
364 local target
= get_client_in_direction(dir
, sel
)
366 -- If we found a client to swap with, then go for it
373 --- Swap a client by its relative index.
374 -- @param i The index.
375 -- @param c Optional client, otherwise focused one is used.
376 function client
.swap
.byidx(i
, c
)
377 local sel
= c
or capi
.client
.focus
378 local target
= client
.next(i
, sel
)
385 -- @param clockwise True to cycle clients clockwise.
386 -- @param screen Optional screen where to cycle clients.
387 function client
.cycle(clockwise
, screen
)
388 local screen
= screen
or capi
.mouse
.screen
389 local cls
= client
.visible(screen
)
390 -- We can't rotate without at least 2 clients, buddy.
392 local c
= table.remove(cls
, 1)
394 for i
= #cls
, 1, -1 do
398 for _
, rc
in pairs(cls
) do
405 --- Get the master window.
406 -- @param screen Optional screen number, otherwise screen mouse is used.
407 -- @return The master window.
408 function client
.getmaster(screen
)
409 local s
= screen
or capi
.mouse
.screen
410 return client
.visible(s
)[1]
413 --- Set the client as slave: put it at the end of other windows.
414 -- @param c The window to set as slave.
415 function client
.setslave(c
)
416 local cls
= capi
.client
.get(c
.screen
)
417 for k
, v
in pairs(cls
) do
422 --- Move/resize a client relative to current coordinates.
423 -- @param x The relative x coordinate.
424 -- @param y The relative y coordinate.
425 -- @param w The relative width.
426 -- @param h The relative height.
427 -- @param c The optional client, otherwise focused one is used.
428 function client
.moveresize(x
, y
, w
, h
, c
)
429 local sel
= c
or capi
.client
.focus
430 local geometry
= sel
:geometry()
431 geometry
['x'] = geometry
['x'] + x
432 geometry
['y'] = geometry
['y'] + y
433 geometry
['width'] = geometry
['width'] + w
434 geometry
['height'] = geometry
['height'] + h
435 sel
:geometry(geometry
)
438 --- Move a client to a tag.
439 -- @param target The tag to move the client to.
440 -- @param c Optional client to move, otherwise the focused one is used.
441 function client
.movetotag(target
, c
)
442 local sel
= c
or capi
.client
.focus
443 if sel
and target
.screen
then
444 -- Set client on the same screen as the tag.
445 sel
.screen
= target
.screen
450 --- Toggle a tag on a client.
451 -- @param target The tag to toggle.
452 -- @param c Optional client to toggle, otherwise the focused one is used.
453 function client
.toggletag(target
, c
)
454 local sel
= c
or capi
.client
.focus
455 -- Check that tag and client screen are identical
456 if sel
and sel
.screen
== target
.screen
then
457 local tags
= sel
:tags()
459 for i
, v
in ipairs(tags
) do
466 -- If it's the only tag for the window, stop.
467 if #tags
== 1 then return end
470 tags
[#tags
+ 1] = target
476 --- Move a client to a screen. Default is next screen, cycling.
477 -- @param c The client to move.
478 -- @param s The screen number, default to current + 1.
479 function client
.movetoscreen(c
, s
)
480 local sel
= c
or capi
.client
.focus
482 local sc
= capi
.screen
.count()
486 if s
> sc
then s
= 1 elseif s
< 1 then s
= sc
end
488 capi
.mouse
.coords(capi
.screen
[s
].geometry
)
492 --- Mark a client, and then call 'marked' hook.
493 -- @param c The client to mark, the focused one if not specified.
494 -- @return True if the client has been marked. False if the client was already marked.
495 function client
.mark(c
)
496 local cl
= c
or capi
.client
.focus
498 for k
, v
in pairs(client
.data
.marked
) do
504 table.insert(client
.data
.marked
, cl
)
507 cl
:emit_signal("marked")
512 --- Unmark a client and then call 'unmarked' hook.
513 -- @param c The client to unmark, or the focused one if not specified.
514 -- @return True if the client has been unmarked. False if the client was not marked.
515 function client
.unmark(c
)
516 local cl
= c
or capi
.client
.focus
518 for k
, v
in pairs(client
.data
.marked
) do
520 table.remove(client
.data
.marked
, k
)
521 cl
:emit_signal("unmarked")
529 --- Check if a client is marked.
530 -- @param c The client to check, or the focused one otherwise.
531 function client
.ismarked(c
)
532 local cl
= c
or capi
.client
.focus
534 for k
, v
in pairs(client
.data
.marked
) do
543 --- Toggle a client as marked.
544 -- @param c The client to toggle mark.
545 function client
.togglemarked(c
)
546 local cl
= c
or capi
.client
.focus
548 if not client
.mark(c
) then
553 --- Return the marked clients and empty the marked table.
554 -- @return A table with all marked clients.
555 function client
.getmarked()
556 for k
, v
in pairs(client
.data
.marked
) do
557 v
:emit_signal("unmarked")
560 t
= client
.data
.marked
561 client
.data
.marked
= {}
565 --- Set a client floating state, overriding auto-detection.
566 -- Floating client are not handled by tiling layouts.
567 -- @param c A client.
568 -- @param s True or false.
569 function client
.floating
.set(c
, s
)
570 local c
= c
or capi
.client
.focus
571 if c
and client
.property
.get(c
, "floating") ~= s
then
572 client
.property
.set(c
, "floating", s
)
573 local screen
= c
.screen
575 c
:geometry(client
.property
.get(c
, "floating_geometry"))
581 local function store_floating_geometry(c
)
582 if client
.floating
.get(c
) then
583 client
.property
.set(c
, "floating_geometry", c
:geometry())
587 -- Store the initial client geometry.
588 capi
.client
.connect_signal("new", function(c
)
589 local function store_init_geometry(c
)
590 client
.property
.set(c
, "floating_geometry", c
:geometry())
591 c
:disconnect_signal("property::border_width", store_init_geometry
)
593 c
:connect_signal("property::border_width", store_init_geometry
)
596 capi
.client
.connect_signal("manage", function(c
)
597 c
:connect_signal("property::geometry", store_floating_geometry
)
600 --- Return if a client has a fixe size or not.
601 -- @param c The client.
602 function client
.isfixed(c
)
603 local c
= c
or capi
.client
.focus
604 if not c
then return end
605 local h
= c
.size_hints
606 if h
.min_width
and h
.max_width
607 and h
.max_height
and h
.min_height
608 and h
.min_width
> 0 and h
.max_width
> 0
609 and h
.max_height
> 0 and h
.min_height
> 0
610 and h
.min_width
== h
.max_width
611 and h
.min_height
== h
.max_height
then
617 --- Get a client floating state.
618 -- @param c A client.
619 -- @return True or false. Note that some windows might be floating even if you
620 -- did not set them manually. For example, windows with a type different than
622 function client
.floating
.get(c
)
623 local c
= c
or capi
.client
.focus
625 local value
= client
.property
.get(c
, "floating")
629 if c
.type ~= "normal"
631 or c
.maximized_vertical
632 or c
.maximized_horizontal
633 or client
.isfixed(c
) then
640 --- Toggle the floating state of a client between 'auto' and 'true'.
641 -- @param c A client.
642 function client
.floating
.toggle(c
)
643 local c
= c
or capi
.client
.focus
644 -- If it has been set to floating
645 if client
.floating
.get(c
) then
646 client
.floating
.set(c
, false)
648 client
.floating
.set(c
, true)
652 --- Remove the floating information on a client.
653 -- @param c The client.
654 function client
.floating
.delete(c
)
655 client
.floating
.set(c
, nil)
658 --- Restore (=unminimize) a random client.
659 -- @param s The screen to use.
660 -- @return True if some client was restored.
661 function client
.restore(s
)
662 local s
= s
or (capi
.client
.focus
and capi
.client
.focus
.screen
) or capi
.mouse
.screen
663 local cls
= capi
.client
.get(s
)
664 local tags
= tag.selectedlist(s
)
666 for k
, c
in pairs(cls
) do
667 local ctags
= c
:tags()
669 for k
, t
in ipairs(tags
) do
670 if util
.table.hasitem(ctags
, t
) then
680 -- Normalize a set of numbers to 1
681 -- @param set the set of numbers to normalize
682 -- @param num the number of numbers to normalize
683 local function normalize(set
, num
)
684 local num
= num
or #set
688 total
= total
+ set
[i
]
691 set
[i
] = set
[i
] / total
694 for i
,v
in ipairs(set
) do
698 for i
,v
in ipairs(set
) do
704 --- Calculate a client's column number, index in that column, and
705 -- number of visible clients in this column.
706 -- @param c the client
707 -- @return col the column number
708 -- @return idx index of the client in the column
709 -- @return num the number of visible clients in the column
710 function client
.idx(c
)
711 local c
= c
or capi
.client
.focus
712 if not c
then return end
714 local clients
= client
.tiled(c
.screen
)
716 for k
, cl
in ipairs(clients
) do
723 local t
= tag.selected(c
.screen
)
724 local nmaster
= tag.getnmaster(t
)
725 if idx
<= nmaster
then
726 return {idx
= idx
, col
=0, num
=nmaster
}
728 local nother
= #clients
- nmaster
731 -- rather than regenerate the column number we can calculate it
732 -- based on the how the tiling algorithm places clients we calculate
733 -- the column, we could easily use the for loop in the program but we can
735 local ncol
= tag.getncol(t
)
736 -- minimum number of clients per column
737 local percol
= math
.floor(nother
/ ncol
)
738 -- number of columns with an extra client
739 local overcol
= math
.fmod(nother
, ncol
)
740 -- number of columns filled with [percol] clients
741 local regcol
= ncol
- overcol
743 local col
= math
.floor( (idx
- 1) / percol
) + 1
745 -- col = math.floor( (idx - (percol*regcol) - 1) / (percol + 1) ) + regcol + 1
747 col
= math
.floor( (idx
+ regcol
+ percol
) / (percol
+1) )
748 -- calculate the index in the column
749 idx
= idx
- percol
*regcol
- (col
- regcol
- 1) * (percol
+1)
752 idx
= idx
- percol
*(col
-1)
755 return {idx
= idx
, col
=col
, num
=percol
}
759 --- Set the window factor of a client
760 -- @param wfact the window factor value
761 -- @param c the client
762 function client
.setwfact(wfact
, c
)
763 -- get the currently selected window
764 local c
= c
or capi
.client
.focus
765 if not c
or not c
:isvisible() then return end
767 local t
= tag.selected(c
.screen
)
768 local w
= client
.idx(c
)
770 local cls
= client
.tiled(t
.screen
)
771 local nmaster
= tag.getnmaster(t
)
773 -- n is the number of windows currently visible for which we have to be concerned with the properties
774 local data
= tag.getproperty(t
, "windowfact") or {}
775 local colfact
= data
[w
.col
]
777 colfact
[w
.idx
] = wfact
780 -- calculate the current denominator
784 total
= total
+ colfact
[i
]
788 -- normalize the windows
791 colfact
[i
] = (colfact
[i
] * rest
) / total
795 t
:emit_signal("property::windowfact")
798 --- Increment a client's window factor
799 -- @param add amount to increase the client's window
800 -- @param c the client
801 function client
.incwfact(add
, c
)
802 local c
= c
or capi
.client
.focus
803 if not c
then return end
805 local t
= tag.selected(c
.screen
)
807 local w
= client
.idx(c
)
809 local nmaster
= tag.getnmaster(t
)
810 local data
= tag.getproperty(t
, "windowfact") or {}
811 local colfact
= data
[w
.col
]
812 curr
= colfact
[w
.idx
] or 1
813 colfact
[w
.idx
] = curr
+ add
815 -- keep our ratios normalized
816 normalize(colfact
, w
.num
)
818 t
:emit_signal("property::windowfact")
821 --- Get a client dockable state.
822 -- @param c A client.
823 -- @return True or false. Note that some windows might be dockable even if you
824 -- did not set them manually. For example, windows with a type "utility", "toolbar"
826 function client
.dockable
.get(c
)
827 local value
= client
.property
.get(c
, "dockable")
829 -- Some sane defaults
831 if (c
.type == "utility" or c
.type == "toolbar" or c
.type == "dock") then
841 --- Set a client dockable state, overriding auto-detection.
842 -- With this enabled you can dock windows by moving them from the center
843 -- to the edge of the workarea.
844 -- @param c A client.
845 -- @param value True or false.
846 function client
.dockable
.set(c
, value
)
847 client
.property
.set(c
, "dockable", value
)
850 --- Get a client property.
851 -- @param c The client.
852 -- @param prop The property name.
853 -- @return The property.
854 function client
.property
.get(c
, prop
)
855 if client
.data
.properties
[c
] then
856 return client
.data
.properties
[c
][prop
]
860 --- Set a client property.
861 -- This properties are internal to awful. Some are used to move clients, etc.
862 -- @param c The client.
863 -- @param prop The property name.
864 -- @param value The value.
865 function client
.property
.set(c
, prop
, value
)
866 if not client
.data
.properties
[c
] then
867 client
.data
.properties
[c
] = {}
869 client
.data
.properties
[c
][prop
] = value
870 c
:emit_signal("property::" .. prop
)
874 -- Returns an iterator to cycle through, starting from the client in focus or
875 -- the given index, all clients that match a given criteria.
877 -- @param filter a function that returns true to indicate a positive match
878 -- @param start what index to start iterating from. Defaults to using the
879 -- index of the currently focused client.
880 -- @param s which screen to use. nil means all screens.
882 -- @usage e.g.: un-minimize all urxvt instances
884 -- local urxvt = function (c) <br/>
885 -- return awful.rules.match(c, {class = "URxvt"}) <br/>
888 -- for c in awful.client.iterate(urxvt) do <br/>
889 -- c.minimized = false <br/>
892 function client
.iterate(filter
, start
, s
)
893 local clients
= capi
.client
.get(s
)
894 local focused
= capi
.client
.focus
895 local start
= start
or util
.table.hasitem(clients
, focused
)
896 return util
.table.iterate(clients
, filter
, start
)
900 -- <p>Switch to a client matching the given condition if running, else spawn it.
901 -- If multiple clients match the given condition then the next one is
904 -- @param cmd the command to execute
905 -- @param matcher a function that returns true to indicate a matching client
906 -- @param merge if true then merge tags when clients are not visible
908 -- @usage run or raise urxvt (perhaps, with tabs) on modkey + semicolon
910 -- awful.key({ modkey, }, 'semicolon', function () <br/>
911 -- local matcher = function (c) <br/>
912 -- return awful.rules.match(c, {class = 'URxvt'}) <br/>
914 -- awful.client.run_or_raise('urxvt', matcher)
917 function client
.run_or_raise(cmd
, matcher
, merge
)
918 local clients
= capi
.client
.get()
919 local findex
= util
.table.hasitem(clients
, capi
.client
.focus
) or 1
920 local start
= util
.cycle(#clients
, findex
+ 1)
922 for c
in iterate(matcher
, start
) do
923 client
.jumpto(c
, merge
)
927 -- client not found, spawn it
931 -- Register standards signals
932 capi
.client
.add_signal("property::floating_geometry")
933 capi
.client
.add_signal("property::floating")
934 capi
.client
.add_signal("property::dockable")
936 capi
.client
.connect_signal("focus", client
.focus
.history
.add
)
937 capi
.client
.connect_signal("unmanage", client
.focus
.history
.delete
)
939 capi
.client
.connect_signal("manage", function(c
) c
:connect_signal("property::urgent", client
.urgent
.add
) end)
940 capi
.client
.connect_signal("focus", client
.urgent
.delete
)
941 capi
.client
.connect_signal("unmanage", client
.urgent
.delete
)
943 capi
.client
.connect_signal("unmanage", client
.floating
.delete
)
947 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80