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 function urgent
.jumpto()
60 local c
= urgent
.get()
62 local s
= capi
.client
.focus
and capi
.client
.focus
.screen
or capi
.mouse
.screen
65 capi
.mouse
.screen
= c
.screen
67 -- focus the tag only if the client is not sticky
69 tag.viewonly(c
:tags()[1])
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
)
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
91 table.remove(data
.urgent
, k
)
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
102 table.remove(data
.focus
, k
)
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"
116 or c
.type == "splash" then
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.
138 function focus
.history
.get(screen
, idx
)
139 -- When this counter is equal to idx, we return the client
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
146 if counter
== idx
then
149 -- We found one, increment the counter only.
150 counter
= counter
+ 1
156 -- Argh nobody found in history, give the first one visible if there is one
157 -- that passes the filter.
159 for k
, v
in ipairs(vc
) do
160 if focus
.filter(v
) then
167 --- Focus the previous client in history.
168 function focus
.history
.previous()
169 local sel
= capi
.client
.focus
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
)
186 for k
, c
in pairs(cls
) do
187 if c
:isvisible() then
188 table.insert(vcls
, c
)
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
)
200 -- Remove floating clients
201 for k
, c
in pairs(clients
) do
202 if not floating
.get(c
) then
203 table.insert(tclients
, c
)
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.
215 -- Get currently focused client
216 local sel
= c
or capi
.client
.focus
218 -- Get all visible clients
219 local cls
= visible(sel
.screen
)
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
)
228 -- Loop upon each client
229 for idx
, c
in ipairs(cls
) do
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()
249 elseif dir
== "down" then
251 elseif dir
== "left" then
253 elseif dir
== "right" then
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()
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
291 local geometry
= sel
:geometry()
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
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
322 local target
= get_client_in_direction(dir
, sel
)
324 -- If we found a client to focus, then do it.
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
)
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
347 local target
= get_client_in_direction(dir
, sel
)
349 -- If we found a client to swap with, then go for it
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
)
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.
375 local c
= table.remove(cls
, 1)
377 for i
= #cls
, 1, -1 do
381 for _
, rc
in pairs(cls
) do
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
396 --- Set the client as slave: put it at the end of other windows.
397 -- @param c The window to set as slave.
399 local cls
= visible(c
.screen
)
400 for k
, v
in pairs(cls
) do
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
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()
442 for i
, v
in ipairs(tags
) do
449 -- If it's the only tag for the window, stop.
450 if #tags
== 1 then return end
453 tags
[#tags
+ 1] = target
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
465 local sc
= capi
.screen
.count()
469 if s
> sc
then s
= 1 elseif s
< 1 then s
= sc
end
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.
480 local cl
= c
or capi
.client
.focus
482 for k
, v
in pairs(data
.marked
) do
488 table.insert(data
.marked
, cl
)
491 cl
:emit_signal("marked")
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.
500 local cl
= c
or capi
.client
.focus
502 for k
, v
in pairs(data
.marked
) do
504 table.remove(data
.marked
, k
)
505 cl
:emit_signal("unmarked")
513 --- Check if a client is marked.
514 -- @param c The client to check, or the focused one otherwise.
516 local cl
= c
or capi
.client
.focus
518 for k
, v
in pairs(data
.marked
) do
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
537 --- Return the marked clients and empty the marked table.
538 -- @return A table with all marked clients.
540 for k
, v
in pairs(data
.marked
) do
541 v
:emit_signal("unmarked")
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
559 c
:geometry(property
.get(c
, "floating_geometry"))
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
)
580 capi
.client
.add_signal("manage", function(c
)
581 c
:add_signal("property::geometry", store_floating_geometry
)
584 --- Return if a client has a fixe size or not.
585 -- @param c The client.
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
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
606 function floating
.get(c
)
607 local c
= c
or capi
.client
.focus
609 local value
= property
.get(c
, "floating")
613 if c
.type ~= "normal"
615 or c
.maximized_vertical
616 or c
.maximized_horizontal
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
632 floating
.set(c
, true)
636 --- Remove the floating information on a client.
637 -- @param c The client.
638 function floating
.delete(c
)
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
650 total
= total
+ set
[i
]
653 set
[i
] = set
[i
] / total
656 for i
,v
in ipairs(set
) do
660 for i
,v
in ipairs(set
) do
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
)
678 for k
, cl
in ipairs(clients
) do
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
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
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
707 -- col = math.floor( (idx - (percol*regcol) - 1) / (percol + 1) ) + regcol + 1
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)
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
)
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
742 -- calculate the current denominator
746 total
= total
+ colfact
[i
]
750 -- normalize the windows
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
)
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"
788 function dockable
.get(c
)
789 local value
= property
.get(c
, "dockable")
791 -- Some sane defaults
793 if (c
.type == "utility" or c
.type == "toolbar" or c
.type == "dock") then
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