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.
24 module("awful.client")
31 data
.properties
= setmetatable({}, { __mode
= 'k' })
42 --- Get the first client that got the urgent hint.
43 -- @return The first urgent client.
45 if #data
.urgent
> 0 then
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
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()
63 local s
= capi
.client
.focus
and capi
.client
.focus
.screen
or capi
.mouse
.screen
66 capi
.mouse
.screen
= c
.screen
69 -- Try to make client visible, this also covers e.g. sticky
71 if t
and not c
:isvisible() then
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
)
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
99 table.remove(data
.urgent
, k
)
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
110 table.remove(data
.focus
, k
)
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"
124 or c
.type == "splash"
125 or not c
.focusable
then
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.
147 function focus
.history
.get(screen
, idx
)
148 -- When this counter is equal to idx, we return the client
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
155 if counter
== idx
then
158 -- We found one, increment the counter only.
159 counter
= counter
+ 1
165 -- Argh nobody found in history, give the first one visible if there is one
166 -- that passes the filter.
168 for k
, v
in ipairs(vc
) do
169 if focus
.filter(v
) then
176 --- Focus the previous client in history.
177 function focus
.history
.previous()
178 local sel
= capi
.client
.focus
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
)
195 for k
, c
in pairs(cls
) do
196 if c
:isvisible() then
197 table.insert(vcls
, c
)
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
)
209 -- Remove floating clients
210 for k
, c
in pairs(clients
) do
211 if not floating
.get(c
) then
212 table.insert(tclients
, c
)
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.
224 -- Get currently focused client
225 local sel
= c
or capi
.client
.focus
227 -- Get all visible clients
228 local cls
= visible(sel
.screen
)
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
)
237 -- Loop upon each client
238 for idx
, c
in ipairs(cls
) do
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()
258 elseif dir
== "down" then
260 elseif dir
== "left" then
262 elseif dir
== "right" then
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()
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
300 local geometry
= sel
:geometry()
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
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
331 local target
= get_client_in_direction(dir
, sel
)
333 -- If we found a client to focus, then do it.
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
)
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
356 local target
= get_client_in_direction(dir
, sel
)
358 -- If we found a client to swap with, then go for it
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
)
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.
384 local c
= table.remove(cls
, 1)
386 for i
= #cls
, 1, -1 do
390 for _
, rc
in pairs(cls
) do
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
405 --- Set the client as slave: put it at the end of other windows.
406 -- @param c The window to set as slave.
408 local cls
= visible(c
.screen
)
409 for k
, v
in pairs(cls
) do
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
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()
451 for i
, v
in ipairs(tags
) do
458 -- If it's the only tag for the window, stop.
459 if #tags
== 1 then return end
462 tags
[#tags
+ 1] = target
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
474 local sc
= capi
.screen
.count()
478 if s
> sc
then s
= 1 elseif s
< 1 then s
= sc
end
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.
489 local cl
= c
or capi
.client
.focus
491 for k
, v
in pairs(data
.marked
) do
497 table.insert(data
.marked
, cl
)
500 cl
:emit_signal("marked")
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.
509 local cl
= c
or capi
.client
.focus
511 for k
, v
in pairs(data
.marked
) do
513 table.remove(data
.marked
, k
)
514 cl
:emit_signal("unmarked")
522 --- Check if a client is marked.
523 -- @param c The client to check, or the focused one otherwise.
525 local cl
= c
or capi
.client
.focus
527 for k
, v
in pairs(data
.marked
) do
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
546 --- Return the marked clients and empty the marked table.
547 -- @return A table with all marked clients.
549 for k
, v
in pairs(data
.marked
) do
550 v
:emit_signal("unmarked")
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
568 c
:geometry(property
.get(c
, "floating_geometry"))
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
)
589 capi
.client
.add_signal("manage", function(c
)
590 c
:add_signal("property::geometry", store_floating_geometry
)
593 --- Return if a client has a fixe size or not.
594 -- @param c The client.
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
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
615 function floating
.get(c
)
616 local c
= c
or capi
.client
.focus
618 local value
= property
.get(c
, "floating")
622 if c
.type ~= "normal"
624 or c
.maximized_vertical
625 or c
.maximized_horizontal
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)
641 floating
.set(c
, true)
645 --- Remove the floating information on a client.
646 -- @param c The client.
647 function floating
.delete(c
)
651 --- Restore (=unminimize) a random client.
652 -- @param s The screen to use.
653 -- @return True if some client was restored.
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
)
659 for k
, c
in pairs(cls
) do
660 local ctags
= c
:tags()
662 for k
, t
in ipairs(tags
) do
663 if util
.table.hasitem(ctags
, t
) then
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
681 total
= total
+ set
[i
]
684 set
[i
] = set
[i
] / total
687 for i
,v
in ipairs(set
) do
691 for i
,v
in ipairs(set
) do
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
704 local c
= c
or capi
.client
.focus
705 if not c
then return end
707 local clients
= tiled(c
.screen
)
709 for k
, cl
in ipairs(clients
) do
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
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
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
738 -- col = math.floor( (idx - (percol*regcol) - 1) / (percol + 1) ) + regcol + 1
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)
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
)
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
773 -- calculate the current denominator
777 total
= total
+ colfact
[i
]
781 -- normalize the windows
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
)
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"
819 function dockable
.get(c
)
820 local value
= property
.get(c
, "dockable")
822 -- Some sane defaults
824 if (c
.type == "utility" or c
.type == "toolbar" or c
.type == "dock") then
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