lib: add @release tag
[awesome.git] / lib / awful.lua.in
blob571f10330c48475646cbaa40f3ddd61d9480315b
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 type = type
9 local string = string
10 local assert = assert
11 local loadstring = loadstring
12 local ipairs = ipairs
13 local pairs = pairs
14 local os = os
15 local io = io
16 local math = math
17 local setmetatable = setmetatable
18 local table = table
19 local otable = otable
20 local capi =
22 awesome = awesome,
23 screen = screen,
24 client = client,
25 mouse = mouse,
26 titlebar = titlebar,
27 widget = widget,
28 hooks = hooks,
29 keygrabber = keygrabber
32 --- awful: AWesome Functions very UsefuL
33 module("awful")
35 -- Local variable handling theme
36 local theme = {}
38 -- Various public structures
39 beautiful = {}
40 hooks = {}
41 hooks.user = {}
42 prompt = {}
43 prompt.history = {}
44 prompt.history.data = {}
45 completion = {}
46 screen = {}
47 layout = {}
48 client = {}
49 client.data = {}
50 client.data.maximize = otable()
51 client.focus = {}
52 client.focus.history = {}
53 client.focus.history.data = {}
54 tag = {}
55 tag.history = {}
56 tag.history.data = {}
57 tag.history.data.past = {}
58 tag.history.data.current = {}
59 titlebar = {}
60 titlebar.data = otable()
61 widget = {}
62 widget.taglist = {}
63 widget.taglist.label = {}
64 widget.tasklist = {}
65 widget.tasklist.label = {}
66 client.urgent = {}
67 client.urgent.stack = {}
68 client.urgent.stack.data = {}
70 --- Make i cycle.
71 -- @param t A length.
72 -- @param i An absolute index to fit into #t.
73 -- @return The object at new index.
74 local function cycle(t, i)
75 while i > t do i = i - t end
76 while i < 1 do i = i + t end
77 return i
78 end
80 --- Get the first client that got the urgent hint.
81 -- @return The first urgent client.
82 function client.urgent.get()
83 if #client.urgent.stack.data > 0 then
84 return client.urgent.stack.data[1]
85 else
86 -- fallback behaviour: iterate through clients and get the first urgent
87 local clients = capi.client.get()
88 for k, cl in pairs(clients) do
89 if cl.urgent then
90 return cl
91 end
92 end
93 end
94 end
96 --- Jump to the client that received the urgent hint first.
97 function client.urgent.jumpto()
98 local c = client.urgent.get()
99 if c then
100 local s = capi.client.focus and capi.client.focus.screen or capi.mouse.screen
101 -- focus the screen
102 if s ~= c.screen then
103 capi.mouse.screen = c.screen
105 -- focus the tag
106 tag.viewonly(c:tags()[1])
107 -- focus the client
108 capi.client.focus = c
109 c:raise()
113 --- Adds client to urgent stack.
114 -- @param The client object.
115 function client.urgent.stack.add(c)
116 table.insert(client.urgent.stack.data, c)
119 --- Remove client from urgent stack.
120 -- @param The client object.
121 function client.urgent.stack.delete(c)
122 for k, cl in ipairs(client.urgent.stack.data) do
123 if c == cl then
124 table.remove(client.urgent.stack.data, k)
125 break
130 --- Remove a client from the focus history
131 -- @param c The client that must be removed.
132 function client.focus.history.delete(c)
133 for k, v in ipairs(client.focus.history.data) do
134 if v == c then
135 table.remove(client.focus.history.data, k)
136 break
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.focus.history.data, 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.
154 -- @return A client.
155 function client.focus.history.get(screen, idx)
156 -- When this counter is equal to idx, we return the client
157 local counter = 0
158 local vc = capi.client.visible_get(screen)
159 for k, c in ipairs(client.focus.history.data) do
160 if c.screen == screen then
161 for j, vcc in ipairs(vc) do
162 if vcc == c then
163 if counter == idx then
164 return c
166 -- We found one, increment the counter only.
167 counter = counter + 1
168 break
173 -- Argh nobody found in history, give the first one visible if there is one
174 if counter == 0 then
175 return vc[1]
179 --- Focus the previous client in history.
180 function client.focus.history.previous()
181 local sel = capi.client.focus
182 local s
183 if sel then
184 s = sel.screen
185 else
186 s = capi.mouse.screen
188 local c = client.focus.history.get(s, 1)
189 if c then capi.client.focus = c end
192 --- Get a client by its relative index to the focused window.
193 -- @usage Set i to 1 to get next, -1 to get previous.
194 -- @param i The index.
195 -- @param c Optional client.
196 -- @return A client, or nil if no client is available.
197 function client.next(i, c)
198 -- Get currently focused client
199 local sel = c or capi.client.focus
200 if sel then
201 -- Get all visible clients
202 local cls = capi.client.visible_get(sel.screen)
203 -- Loop upon each client
204 for idx, c in ipairs(cls) do
205 if c == sel then
206 -- Cycle
207 return cls[cycle(#cls, idx +i)]
213 --- Return true whether client B is in the right direction
214 -- compared to client A.
215 -- @param dir The direction.
216 -- @param cA The first client.
217 -- @param cB The second client.
218 -- @return True if B is in the direction of A.
219 local function is_in_direction(dir, cA, cB)
220 if dir == "up" then
221 return cA['y'] > cB['y']
222 elseif dir == "down" then
223 return cA['y'] < cB['y']
224 elseif dir == "left" then
225 return cA['x'] > cB['x']
226 elseif dir == "right" then
227 return cA['x'] < cB['x']
229 return false
232 --- Calculate distance between two points.
233 -- i.e: if we want to move to the right, we will take the right border
234 -- of the currently focused client and the left side of the checked client.
235 -- This avoid the focus of an upper client when you move to the right in a
236 -- tilebottom layout with nmaster=2 and 5 clients open, for instance.
237 -- @param dir The direction.
238 -- @param cA The first client.
239 -- @param cB The second client.
240 -- @return The distance between the clients.
241 local function calculate_distance(dir, cA, cB)
242 local xA = cA['x']
243 local xB = cB['x']
244 local yA = cA['y']
245 local yB = cB['y']
247 if dir == "up" then
248 yB = yB + cB['height']
249 elseif dir == "down" then
250 yA = yA + cA['height']
251 elseif dir == "left" then
252 xB = xB + cB['width']
253 elseif dir == "right" then
254 xA = xA + cA['width']
257 return math.sqrt(math.pow(xB - xA, 2) + math.pow(yB - yA, 2))
260 --- Focus a client by the given direction.
261 -- @param dir The direction, can be either "up", "down", "left" or "right".
262 -- @param c Optional client.
263 function client.focusbydirection(dir, c)
264 local sel = c or capi.client.focus
265 if sel then
266 local coords = sel.coords
267 local dist, dist_min
268 local target = nil
269 local cls = capi.client.visible_get(sel.screen)
271 -- We check each client.
272 for i, c in ipairs(cls) do
273 -- Check coords to see if client is located in the right direction.
274 if is_in_direction(dir, coords, c.coords) then
276 -- Calculate distance between focused client and checked client.
277 dist = calculate_distance(dir, coords, c.coords)
279 -- If distance is shorter then keep the client.
280 if not target or dist < dist_min then
281 target = c
282 dist_min = dist
287 -- If we found a client to focus, then do it.
288 if target then
289 capi.client.focus = target
294 --- Focus a client by its relative index.
295 -- @param i The index.
296 -- @param c Optional client.
297 function client.focusbyidx(i, c)
298 local target = client.next(i, c)
299 if target then
300 capi.client.focus = target
304 --- Swap a client by its relative index.
305 -- @param i The index.
306 -- @param c Optional client, otherwise focused one is used.
307 function client.swap(i, c)
308 local sel = c or capi.client.focus
309 local target = client.next(i, sel)
310 if target then
311 target:swap(sel)
315 --- Get the master window.
316 -- @param screen Optional screen number, otherwise screen mouse is used.
317 -- @return The master window.
318 function client.master(screen)
319 local s = screen or capi.mouse.screen
320 return capi.client.visible_get(s)[1]
323 --- Move/resize a client relative to current coordinates.
324 -- @param x The relative x coordinate.
325 -- @param y The relative y coordinate.
326 -- @param w The relative width.
327 -- @param h The relative height.
328 -- @param c The optional client, otherwise focused one is used.
329 function client.moveresize(x, y, w, h, c)
330 local sel = c or capi.client.focus
331 local coords = sel.coords
332 coords['x'] = coords['x'] + x
333 coords['y'] = coords['y'] + y
334 coords['width'] = coords['width'] + w
335 coords['height'] = coords['height'] + h
336 sel.coords = coords
339 --- Maximize a client to use the full workarea.
340 -- @param c A client, or the focused one if nil.
341 function client.maximize(c)
342 local sel = c or capi.client.focus
343 if sel then
344 local ws = capi.screen[sel.screen].workarea
345 ws.width = ws.width - 2 * sel.border_width
346 ws.height = ws.height - 2 * sel.border_width
347 if sel.floating and client.data.maximize[sel] then
348 sel.floating = client.data.maximize[sel].floating
349 if sel.floating then
350 sel.coords = client.data.maximize[sel].coords
352 client.data.maximize[sel] = nil
353 else
354 client.data.maximize[sel] = { coords = sel.coords, floating = sel.floating }
355 sel.floating = true
356 sel.coords = ws
362 --- Erase eventual client data in maximize.
363 -- @param c The client.
364 local function client_maximize_clean(c)
365 client.data.maximize[c] = nil
368 --- Give the focus to a screen, and move pointer.
369 -- @param Screen number.
370 function screen.focus(i)
371 local s = cycle(capi.screen.count(), capi.mouse.screen + i)
372 local c = client.focus.history.get(s, 0)
373 if c then capi.client.focus = c end
374 -- Move the mouse on the screen
375 capi.mouse.screen = s
378 --- Compare 2 tables of tags.
379 -- @param a The first table.
380 -- @param b The second table of tags.
381 -- @return True if the tables are identical, false otherwise.
382 local function tag_compare_select(a, b)
383 if not a or not b then
384 return false
386 -- Quick size comparison
387 if #a ~= #b then
388 return false
390 for ka, va in pairs(a) do
391 if b[ka] ~= va.selected then
392 return false
395 for kb, vb in pairs(b) do
396 if a[kb].selected ~= vb then
397 return false
400 return true
403 --- Update the tag history.
404 -- @param screen The screen number.
405 function tag.history.update(screen)
406 local curtags = capi.screen[screen]:tags()
407 if not tag_compare_select(curtags, tag.history.data.current[screen]) then
408 tag.history.data.past[screen] = tag.history.data.current[screen]
409 tag.history.data.current[screen] = {}
410 for k, v in ipairs(curtags) do
411 tag.history.data.current[screen][k] = v.selected
416 -- Revert tag history.
417 -- @param screen The screen number.
418 function tag.history.restore(screen)
419 local s = screen or capi.mouse.screen
420 local tags = capi.screen[s]:tags()
421 for k, t in pairs(tags) do
422 t.selected = tag.history.data.past[s][k]
426 --- Return a table with all visible tags
427 -- @param s Screen number.
428 -- @return A table with all selected tags.
429 function tag.selectedlist(s)
430 local screen = s or capi.mouse.screen
431 local tags = capi.screen[screen]:tags()
432 local vtags = {}
433 for i, t in pairs(tags) do
434 if t.selected then
435 vtags[#vtags + 1] = t
438 return vtags
441 --- Return only the first visible tag.
442 -- @param s Screen number.
443 function tag.selected(s)
444 return tag.selectedlist(s)[1]
447 --- Set master width factor.
448 -- @param mwfact Master width factor.
449 function tag.setmwfact(mwfact)
450 local t = tag.selected()
451 if t then
452 t.mwfact = mwfact
456 --- Increase master width factor.
457 -- @param add Value to add to master width factor.
458 function tag.incmwfact(add)
459 local t = tag.selected()
460 if t then
461 t.mwfact = t.mwfact + add
465 --- Set the number of master windows.
466 -- @param nmaster The number of master windows.
467 function tag.setnmaster(nmaster)
468 local t = tag.selected()
469 if t then
470 t.nmaster = nmaster
474 --- Increase the number of master windows.
475 -- @param add Value to add to number of master windows.
476 function tag.incnmaster(add)
477 local t = tag.selected()
478 if t then
479 t.nmaster = t.nmaster + add
483 --- Set number of column windows.
484 -- @param ncol The number of column.
485 function tag.setncol(ncol)
486 local t = tag.selected()
487 if t then
488 t.ncol = ncol
492 --- Increase number of column windows.
493 -- @param add Value to add to number of column windows.
494 function tag.incncol(add)
495 local t = tag.selected()
496 if t then
497 t.ncol = t.ncol + add
501 --- View no tag.
502 -- @param Optional screen number.
503 function tag.viewnone(screen)
504 local tags = capi.screen[screen or capi.mouse.screen]:tags()
505 for i, t in pairs(tags) do
506 t.selected = false
510 --- View a tag by its index.
511 -- @param i The relative index to see.
512 -- @param screen Optional screen number.
513 function tag.viewidx(i, screen)
514 local tags = capi.screen[screen or capi.mouse.screen]:tags()
515 local sel = tag.selected()
516 tag.viewnone()
517 for k, t in ipairs(tags) do
518 if t == sel then
519 tags[cycle(#tags, k + i)].selected = true
524 --- View next tag. This is the same as tag.viewidx(1).
525 function tag.viewnext()
526 return tag.viewidx(1)
529 --- View previous tag. This is the same a tag.viewidx(-1).
530 function tag.viewprev()
531 return tag.viewidx(-1)
534 --- View only a tag.
535 -- @param t The tag object.
536 function tag.viewonly(t)
537 tag.viewnone(t.screen)
538 t.selected = true
541 --- View only a set of tags.
542 -- @param tags A table with tags to view only.
543 -- @param screen Optional screen number of the tags.
544 function tag.viewmore(tags, screen)
545 tag.viewnone(screen)
546 for i, t in pairs(tags) do
547 t.selected = true
551 --- Move a client to a tag.
552 -- @param target The tag to move the client to.
553 -- @para c Optional client to move, otherwise the focused one is used.
554 function client.movetotag(target, c)
555 local sel = c or capi.client.focus
556 if sel then
557 -- Check that tag and client screen are identical
558 if sel.screen ~= target.screen then return end
559 sel:tags({ target })
563 --- Toggle a tag on a client.
564 -- @param target The tag to toggle.
565 -- @param c Optional client to toggle, otherwise the focused one is used.
566 function client.toggletag(target, c)
567 local sel = c or capi.client.focus
568 -- Check that tag and client screen are identical
569 if sel and sel.screen == target.screen then
570 local tags = sel:tags()
571 if tags[target] then
572 tags[target] = nil
573 else
574 tags[target] = target
576 sel:tags(tags)
580 --- Toggle the floating status of a client.
581 -- @param c Optional client, the focused on if not set.
582 function client.togglefloating(c)
583 local sel = c or capi.client.focus
584 if sel then
585 sel.floating = not sel.floating
589 --- Move a client to a screen. Default is next screen, cycling.
590 -- @param c The client to move.
591 -- @param s The screen number, default to current + 1.
592 function client.movetoscreen(c, s)
593 local sel = c or capi.client.focus
594 if sel then
595 local sc = capi.screen.count()
596 if not s then
597 s = sel.screen + 1
599 if s > sc then s = 1 elseif s < 1 then s = sc end
600 sel.screen = s
601 capi.mouse.coords = capi.screen[s].coords
602 capi.client.focus = sel
606 --- Get the current layout name.
607 -- @param screen The screen number.
608 function layout.get(screen)
609 local t = tag.selected(screen)
610 if t then
611 return t.layout
615 --- Create a new userhook (for external libs).
616 -- @param name Hook name.
617 function hooks.user.create(name)
618 hooks[name] = {}
619 hooks[name].callbacks = {}
620 hooks[name].register = function (f)
621 table.insert(hooks[name].callbacks, f)
623 hooks[name].unregister = function (f)
624 for k, h in ipairs(hooks[name].callbacks) do
625 if h == f then
626 table.remove(hooks[name].callbacks, k)
627 break
633 --- Call a created userhook (for external libs).
634 -- @param name Hook name.
635 function hooks.user.call(name, ...)
636 for name, callback in pairs(hooks[name].callbacks) do
637 callback(...)
641 -- Just set an awful mark to a client to move it later.
642 local awfulmarked = {}
643 hooks.user.create('marked')
644 hooks.user.create('unmarked')
646 --- Mark a client, and then call 'marked' hook.
647 -- @param c The client to mark, the focused one if not specified.
648 -- @return True if the client has been marked. False if the client was already marked.
649 function client.mark (c)
650 local cl = c or capi.client.focus
651 if cl then
652 for k, v in pairs(awfulmarked) do
653 if cl == v then
654 return false
658 table.insert(awfulmarked, cl)
660 -- Call callback
661 hooks.user.call('marked', cl)
662 return true
666 --- Unmark a client and then call 'unmarked' hook.
667 -- @param c The client to unmark, or the focused one if not specified.
668 -- @return True if the client has been unmarked. False if the client was not marked.
669 function client.unmark(c)
670 local cl = c or capi.client.focus
672 for k, v in pairs(awfulmarked) do
673 if cl == v then
674 table.remove(awfulmarked, k)
675 hooks.user.call('unmarked', cl)
676 return true
680 return false
683 --- Check if a client is marked.
684 -- @param c The client to check, or the focused one otherwise.
685 function client.ismarked(c)
686 local cl = c or capi.client.focus
687 if cl then
688 for k, v in pairs(awfulmarked) do
689 if cl == v then
690 return true
694 return false
697 --- Toggle a client as marked.
698 -- @param c The client to toggle mark.
699 function client.togglemarked(c)
700 local cl = c or capi.client.focus
702 if not client.mark(c) then
703 client.unmark(c)
707 --- Return the marked clients and empty the marked table.
708 -- @return A table with all marked clients.
709 function client.getmarked()
710 for k, v in pairs(awfulmarked) do
711 hooks.user.call('unmarked', v)
714 t = awfulmarked
715 awfulmarked = {}
716 return t
719 --- Change the layout of the current tag.
720 -- @param layouts A table of layouts.
721 -- @param i Relative index.
722 function layout.inc(layouts, i)
723 local t = tag.selected()
724 local number_of_layouts = 0
725 local rev_layouts = {}
726 for i, v in ipairs(layouts) do
727 rev_layouts[v] = i
728 number_of_layouts = number_of_layouts + 1
730 if t then
731 local cur_layout = layout.get()
732 local new_layout_index = (rev_layouts[cur_layout] + i) % number_of_layouts
733 if new_layout_index == 0 then
734 new_layout_index = number_of_layouts
736 t.layout = layouts[new_layout_index]
740 --- Set the layout of the current tag by name.
741 -- @param layout Layout name.
742 function layout.set(layout)
743 local t = tag.selected()
744 if t then
745 t.layout = layout
749 -- Autodeclare awful.hooks.* functions
750 -- mapped to awesome hooks.* functions
751 for name, hook in pairs(capi.hooks) do
752 if name ~= 'timer' then
753 hooks[name] = {}
754 hooks[name].register = function (f)
755 if not hooks[name].callbacks then
756 hooks[name].callbacks = {}
757 hook(function (...)
758 for i, callback in ipairs(hooks[name].callbacks) do
759 callback(...)
761 end)
764 table.insert(hooks[name].callbacks, f)
766 hooks[name].unregister = function (f)
767 if hooks[name].callbacks then
768 for k, h in ipairs(hooks[name].callbacks) do
769 if h == f then
770 table.remove(hooks[name].callbacks, k)
771 break
776 else
777 hooks[name] = {}
778 hooks[name].register = function (time, f, runnow)
779 if type(time) ~= 'number' or type(f) ~= 'function' or time <= 0 then
780 return
782 local new_timer
783 if hooks[name].timer then
784 -- Take the smallest between current and new
785 new_timer = math.min(time, hooks[name].timer)
786 else
787 new_timer = time
789 if not hooks[name].callbacks then
790 hooks[name].callbacks = {}
792 if hooks[name].timer ~= new_timer then
793 hooks[name].timer = new_timer
794 hook(hooks[name].timer, function (...)
795 for i, callback in ipairs(hooks[name].callbacks) do
796 callback['counter'] = callback['counter'] + hooks[name].timer
797 if callback['counter'] >= callback['timer'] then
798 callback['callback'](...)
799 callback['counter'] = 0
802 end)
805 if runnow then
806 table.insert(hooks[name].callbacks, { callback = f, timer = time, counter = time })
807 else
808 table.insert(hooks[name].callbacks, { callback = f, timer = time, counter = 0 })
811 hooks[name].unregister = function (f)
812 if hooks[name].callbacks then
813 for k, h in ipairs(hooks[name].callbacks) do
814 if h.callback == f then
815 table.remove(hooks[name].callbacks, k)
816 break
824 --- Spawn a program.
825 -- @param cmd The command.
826 -- @return The awesome.spawn return value.
827 function spawn(cmd)
828 if cmd and cmd ~= "" then
829 return capi.awesome.spawn(cmd .. "&", capi.mouse.screen)
833 --- Eval Lua code.
834 -- @return The return value of Lua code.
835 function eval(s)
836 return assert(loadstring("return " .. s))()
839 --- Load history file in history table
840 -- @param id The prompt history identifier which is the path to the filename
841 -- @param max Optional parameter, the maximum number of entries in file
842 local function prompt_history_check_load(id, max)
843 if id and id ~= ""
844 and not prompt.history[id] then
845 prompt.history[id] = { max = 50, table = {} }
847 if max then
848 prompt.history[id].max = max
851 local f = io.open(id, "r")
853 -- Read history file
854 if f then
855 for line in f:lines() do
856 table.insert(prompt.history[id].table, line)
857 if #prompt.history[id].table >= prompt.history[id].max then
858 break
865 --- Save history table in history file
866 -- @param id The prompt history identifier
867 local function prompt_history_save(id)
868 if prompt.history[id] then
869 local f = assert(io.open(id, "w"))
870 for i = 1, math.min(#prompt.history[id].table, prompt.history[id].max) do
871 f:write(prompt.history[id].table[i] .. "\n")
873 f:close()
877 --- Return the number of items in history table regarding the id
878 -- @param id The prompt history identifier
879 -- @return the number of items in history table, -1 if history is disabled
880 local function prompt_history_items(id)
881 if prompt.history[id] then
882 return #prompt.history[id].table
883 else
884 return -1
888 --- Add an entry to the history file
889 -- @param id The prompt history identifier
890 -- @param command The command to add
891 local function prompt_history_add(id, command)
892 if prompt.history[id] then
893 if command ~= ""
894 and command ~= prompt.history[id].table[#prompt.history[id].table] then
895 table.insert(prompt.history[id].table, command)
897 -- Do not exceed our max_cmd
898 if #prompt.history[id].table > prompt.history[id].max then
899 table.remove(prompt.history[id].table, 1)
902 prompt_history_save(id)
907 --- Use bash completion system to complete command and filename.
908 -- @param command The command line.
909 -- @param cur_pos The cursor position.
910 -- @paran ncomp The element number to complete.
911 -- @return The new command and the new cursor position.
912 function completion.bash(command, cur_pos, ncomp)
913 local wstart = 1
914 local wend = 1
915 local words = {}
916 local cword_index = 0
917 local cword_start = 0
918 local cword_end = 0
919 local i = 1
920 local comptype = "file"
922 -- do nothing if we are on a letter, i.e. not at len + 1 or on a space
923 if cur_pos ~= #command + 1 and command:sub(cur_pos, cur_pos) ~= " " then
924 return command, cur_pos
925 elseif #command == 0 then
926 return command, cur_pos
929 while wend <= #command do
930 wend = command:find(" ", wstart)
931 if not wend then wend = #command + 1 end
932 table.insert(words, command:sub(wstart, wend - 1))
933 if cur_pos >= wstart and cur_pos <= wend + 1 then
934 cword_start = wstart
935 cword_end = wend
936 cword_index = i
938 wstart = wend + 1
939 i = i + 1
942 if cword_index == 1 then
943 comptype = "command"
946 local c = io.popen("/usr/bin/env bash -c 'compgen -A " .. comptype .. " " .. words[cword_index] .. "'")
947 local output = {}
948 i = 0
949 while true do
950 local line = c:read("*line")
951 if not line then break end
952 table.insert(output, line)
955 c:close()
957 -- no completion, return
958 if #output == 0 then
959 return command, cur_pos
962 -- cycle
963 while ncomp > #output do
964 ncomp = ncomp - #output
967 local str = command:sub(1, cword_start - 1) .. output[ncomp] .. command:sub(cword_end)
968 cur_pos = cword_end + #output[ncomp] + 1
970 return str, cur_pos
973 --- Draw the prompt text with a cursor.
974 -- @param text The text.
975 -- @param text_color The text color.
976 -- @param cursor_color The cursor color.
977 -- @param cursor_pos The cursor position.
978 local function prompt_text_with_cursor(text, text_color, cursor_color, cursor_pos)
979 local char
980 if not text then text = "" end
981 if #text < cursor_pos then
982 char = " "
983 else
984 char = escape(text:sub(cursor_pos, cursor_pos))
986 local text_start = escape(text:sub(1, cursor_pos - 1))
987 local text_end = escape(text:sub(cursor_pos + 1))
988 return text_start .. "<span background=\"" .. cursor_color .. "\" foreground=\"" .. text_color .. "\">" .. char .. "</span>" .. text_end
991 --- Run a prompt in a box.
992 -- @param args A table with optional arguments: fg_cursor, bg_cursor, prompt.
993 -- @param textbox The textbox to use for the prompt.
994 -- @param exe_callback The callback function to call with command as argument when finished.
995 -- @param completion_callback The callback function to call to get completion.
996 -- @param history_path Optional parameter: file path where the history should be saved, set nil to disable history
997 -- @param history_max Optional parameter: set the maximum entries in history file, 50 by default
998 -- @param done_callback Optional parameter: the callback function to always call without arguments, regardless of whether the prompt was cancelled.
999 function prompt.run(args, textbox, exe_callback, completion_callback, history_path, history_max, done_callback)
1000 if not args then args = {} end
1001 local command = ""
1002 local command_before_comp
1003 local cur_pos_before_comp
1004 local prettyprompt = args.prompt or ""
1005 local inv_col = args.fg_cursor or theme.fg_focus or "black"
1006 local cur_col = args.bg_cursor or theme.bg_focus or "white"
1008 prompt_history_check_load(history_path, history_max)
1009 local history_index = prompt_history_items(history_path) + 1
1010 -- The cursor position
1011 local cur_pos = 1
1012 -- The completion element to use on completion request.
1013 local ncomp = 1
1014 if not textbox or not exe_callback then
1015 return
1017 textbox.text = prettyprompt .. prompt_text_with_cursor(text, inv_col, cur_col, cur_pos)
1018 capi.keygrabber.run(
1019 function (mod, key)
1020 local has_ctrl = false
1022 -- Compute modifiers keys
1023 for k, v in ipairs(mod) do
1024 if v == "Control" then has_ctrl = true end
1027 -- Get out cases
1028 if has_ctrl then
1029 if key == "c" or key == "g" then
1030 textbox.text = ""
1031 if done_callback then done_callback() end
1032 return false
1033 elseif key == "j" or key == "m" then
1034 textbox.text = ""
1035 exec_callback(command)
1036 if done_callback then done_callback() end
1037 return false
1039 else
1040 if key == "Return" then
1041 textbox.text = ""
1042 prompt_history_add(history_path, command)
1043 exe_callback(command)
1044 if done_callback then done_callback() end
1045 return false
1046 elseif key == "Escape" then
1047 textbox.text = ""
1048 if done_callback then done_callback() end
1049 return false
1053 -- Control cases
1054 if has_ctrl then
1055 if key == "a" then
1056 cur_pos = 1
1057 elseif key == "b" then
1058 if cur_pos > 1 then
1059 cur_pos = cur_pos - 1
1061 elseif key == "d" then
1062 if cur_pos <= #command then
1063 command = command:sub(1, cur_pos - 1) .. command:sub(cur_pos + 1)
1065 elseif key == "e" then
1066 cur_pos = #command + 1
1067 elseif key == "f" then
1068 if cur_pos <= #command then
1069 cur_pos = cur_pos + 1
1071 elseif key == "h" then
1072 if cur_pos > 1 then
1073 command = command:sub(1, cur_pos - 2) .. command:sub(cur_pos)
1074 cur_pos = cur_pos - 1
1076 elseif key == "k" then
1077 command = command:sub(1, cur_pos - 1)
1078 elseif key == "u" then
1079 command = command:sub(cur_pos, #command)
1080 cur_pos = 1
1081 elseif key == "w" then
1082 local wstart = 1
1083 local wend = 1
1084 local cword_start = 1
1085 local cword_end = 1
1086 while wend < cur_pos do
1087 wend = command:find(" ", wstart)
1088 if not wend then wend = #command + 1 end
1089 if cur_pos >= wstart and cur_pos <= wend + 1 then
1090 cword_start = wstart
1091 cword_end = cur_pos - 1
1092 break
1094 wstart = wend + 1
1096 command = command:sub(1, cword_start - 1) .. command:sub(cword_end + 1)
1097 cur_pos = cword_start
1099 else
1100 if completion_callback then
1101 -- That's tab
1102 if key:byte() == 9 then
1103 if ncomp == 1 then
1104 command_before_comp = command
1105 cur_pos_before_comp = cur_pos
1107 command, cur_pos = completion_callback(command_before_comp, cur_pos_before_comp, ncomp)
1108 ncomp = ncomp + 1
1109 key = ""
1110 else
1111 ncomp = 1
1115 -- Typin cases
1116 if key == "Home" then
1117 cur_pos = 1
1118 elseif key == "End" then
1119 cur_pos = #command + 1
1120 elseif key == "BackSpace" then
1121 if cur_pos > 1 then
1122 command = command:sub(1, cur_pos - 2) .. command:sub(cur_pos)
1123 cur_pos = cur_pos - 1
1125 -- That's DEL
1126 elseif key:byte() == 127 then
1127 command = command:sub(1, cur_pos - 1) .. command:sub(cur_pos + 1)
1128 elseif key == "Left" then
1129 cur_pos = cur_pos - 1
1130 elseif key == "Right" then
1131 cur_pos = cur_pos + 1
1132 elseif key == "Up" then
1133 if history_index > 1 then
1134 history_index = history_index - 1
1136 command = prompt.history[history_path].table[history_index]
1137 cur_pos = #command + 2
1139 elseif key == "Down" then
1140 if history_index < prompt_history_items(history_path) then
1141 history_index = history_index + 1
1143 command = prompt.history[history_path].table[history_index]
1144 cur_pos = #command + 2
1145 elseif history_index == prompt_history_items(history_path) then
1146 history_index = history_index + 1
1148 command = ""
1149 cur_pos = 1
1151 else
1152 -- len() is UTF-8 aware but #key is not,
1153 -- so check that we have one UTF-8 char but advance the cursor of # position
1154 if key:len() == 1 then
1155 command = command:sub(1, cur_pos - 1) .. key .. command:sub(cur_pos)
1156 cur_pos = cur_pos + #key
1159 if cur_pos < 1 then
1160 cur_pos = 1
1161 elseif cur_pos > #command + 1 then
1162 cur_pos = #command + 1
1166 -- Update textbox
1167 textbox.text = prettyprompt .. prompt_text_with_cursor(command, inv_col, cur_col, cur_pos)
1169 return true
1170 end)
1173 --- Escape a string from XML char.
1174 -- Useful to set raw text in textbox.
1175 -- @param text Text to escape.
1176 -- @return Escape text.
1177 function escape(text)
1178 if text then
1179 text = text:gsub("&", "&amp;")
1180 text = text:gsub("<", "&lt;")
1181 text = text:gsub(">", "&gt;")
1182 text = text:gsub("'", "&apos;")
1183 text = text:gsub("\"", "&quot;")
1185 return text
1188 --- Unescape a string from entities.
1189 -- @param text Text to unescape.
1190 -- @return Unescaped text.
1191 function unescape(text)
1192 if text then
1193 text = text:gsub("&amp;", "&")
1194 text = text:gsub("&lt;", "<")
1195 text = text:gsub("&gt;", ">")
1196 text = text:gsub("&apos;", "'")
1197 text = text:gsub("&quot;", "\"")
1199 return text
1202 --- Return labels for a taglist widget with all tag from screen.
1203 -- It returns the tag name and set a special
1204 -- foreground and background color for selected tags.
1205 -- @param t The tag.
1206 -- @param args The arguments table.
1207 -- bg_focus The background color for selected tag.
1208 -- fg_focus The foreground color for selected tag.
1209 -- bg_urgent The background color for urgent tags.
1210 -- fg_urgent The foreground color for urgent tags.
1211 -- taglist_squares Optional: set "true" or nil to display the taglist squares.
1212 -- @return A string to print.
1213 function widget.taglist.label.all(t, args)
1214 if not args then args = {} end
1215 local fg_focus = args.fg_focus or theme.taglist_fg_focus or theme.fg_focus
1216 local bg_focus = args.bg_focus or theme.taglist_bg_focus or theme.bg_focus
1217 local fg_urgent = args.fg_urgent or theme.taglist_fg_urgent or theme.fg_urgent
1218 local bg_urgent = args.bg_urgent or theme.taglist_bg_urgent or theme.bg_urgent
1219 local taglist_squares = args.taglist_squares or theme.taglist_squares
1220 local text
1221 local background = ""
1222 local sel = capi.client.focus
1223 local bg_color = nil
1224 local fg_color = nil
1225 if t.selected then
1226 bg_color = bg_focus
1227 fg_color = fg_focus
1229 if sel and sel:tags()[t] then
1230 if not taglist_squares or taglist_squares == "true" then
1231 background = "resize=\"true\" image=\"@AWESOME_ICON_PATH@/taglist/squarefw.png\""
1233 elseif bg_urgent and fg_urgent then
1234 for k, c in pairs(t:clients()) do
1235 if not taglist_squares or taglist_squares == "true" then
1236 background = "resize=\"true\" image=\"@AWESOME_ICON_PATH@/taglist/squarew.png\""
1238 if c.urgent then
1239 bg_color = bg_urgent
1240 fg_color = fg_urgent
1241 break
1245 if bg_color and fg_color then
1246 text = "<bg "..background.." color='"..bg_color.."'/> <span color='"..fg_color.."'>"..escape(t.name).."</span> "
1247 else
1248 text = " <bg "..background.." />"..escape(t.name).." "
1250 return text
1253 --- Return labels for a taglist widget with all *non empty* tags from screen.
1254 -- It returns the tag name and set a special
1255 -- foreground and background color for selected tags.
1256 -- @param t The tag.
1257 -- @param args The arguments table.
1258 -- bg_focus The background color for selected tag.
1259 -- fg_focus The foreground color for selected tag.
1260 -- bg_urgent The background color for urgent tags.
1261 -- fg_urgent The foreground color for urgent tags.
1262 -- @return A string to print.
1263 function widget.taglist.label.noempty(t, args)
1264 if #t:clients() > 0 or t.selected then
1265 if not args then args = {} end
1266 local fg_focus = args.fg_focus or theme.taglist_fg_focus or theme.fg_focus
1267 local bg_focus = args.bg_focus or theme.taglist_bg_focus or theme.bg_focus
1268 local fg_urgent = args.fg_urgent or theme.taglist_fg_urgent or theme.fg_urgent
1269 local bg_urgent = args.bg_urgent or theme.taglist_bg_urgent or theme.bg_urgent
1270 local bg_color = nil
1271 local fg_color = nil
1272 local text
1274 if t.selected then
1275 bg_color = bg_focus
1276 fg_color = fg_focus
1278 if bg_urgent and fg_urgent then
1279 for k, c in pairs(t:clients()) do
1280 if c.urgent then
1281 bg_color = bg_urgent
1282 fg_color = fg_urgent
1283 break
1287 if fg_color and bg_color then
1288 text = "<bg color='" .. bg_color .. "'/> <span color='" .. fg_color .. "'>" .. escape(t.name) .. "</span> "
1289 else
1290 text = " " .. escape(t.name) .. " "
1292 return text
1296 local function widget_tasklist_label_common(c, args)
1297 if not args then args = {} end
1298 local fg_focus = args.fg_focus or theme.tasklist_fg_focus or theme.fg_focus
1299 local bg_focus = args.bg_focus or theme.tasklist_bg_focus or theme.bg_focus
1300 local fg_urgent = args.fg_urgent or theme.tasklist_fg_urgent or theme.fg_urgent
1301 local bg_urgent = args.bg_urgent or theme.tasklist_bg_urgent or theme.bg_urgent
1302 local text = ""
1303 local name
1304 if c.floating then
1305 text = "<bg image=\"@AWESOME_ICON_PATH@/tasklist/floatingw.png\" align=\"right\"/>"
1307 if c.hidden then
1308 name = escape(c.icon_name) or ""
1309 else
1310 name = escape(c.name) or ""
1312 if capi.client.focus == c then
1313 text = text .. " <bg color='"..bg_focus.."'/><span color='"..fg_focus.."'>"..name.."</span> "
1314 elseif c.urgent and bg_urgent and fg_urgent then
1315 text = text .. " <bg color='"..bg_urgent.."'/><span color='"..fg_urgent.."'>"..name.."</span> "
1316 else
1317 text = text .. " "..name.." "
1319 return text
1322 --- Return labels for a tasklist widget with clients from all tags and screen.
1323 -- It returns the client name and set a special
1324 -- foreground and background color for focused client.
1325 -- It also puts a special icon for floating windows.
1326 -- @param c The client.
1327 -- @param screen The screen we are drawing on.
1328 -- @param args The arguments table.
1329 -- bg_focus The background color for focused client.
1330 -- fg_focus The foreground color for focused client.
1331 -- bg_urgent The background color for urgent clients.
1332 -- fg_urgent The foreground color for urgent clients.
1333 -- @return A string to print.
1334 function widget.tasklist.label.allscreen(c, screen, args)
1335 return widget_tasklist_label_common(c, args)
1338 --- Return labels for a tasklist widget with clients from all tags.
1339 -- It returns the client name and set a special
1340 -- foreground and background color for focused client.
1341 -- It also puts a special icon for floating windows.
1342 -- @param c The client.
1343 -- @param screen The screen we are drawing on.
1344 -- @param args The arguments table.
1345 -- bg_focus The background color for focused client.
1346 -- fg_focus The foreground color for focused client.
1347 -- bg_urgent The background color for urgent clients.
1348 -- fg_urgent The foreground color for urgent clients.
1349 -- @return A string to print.
1350 function widget.tasklist.label.alltags(c, screen, args)
1351 -- Only print client on the same screen as this widget
1352 if c.screen ~= screen then return end
1353 return widget_tasklist_label_common(c, args)
1356 --- Return labels for a tasklist widget with clients from currently selected tags.
1357 -- It returns the client name and set a special
1358 -- foreground and background color for focused client.
1359 -- It also puts a special icon for floating windows.
1360 -- @param c The client.
1361 -- @param screen The screen we are drawing on.
1362 -- @param args The arguments table.
1363 -- bg_focus The background color for focused client.
1364 -- fg_focus The foreground color for focused client.
1365 -- bg_urgent The background color for urgent clients.
1366 -- fg_urgent The foreground color for urgent clients.
1367 -- @return A string to print.
1368 function widget.tasklist.label.currenttags(c, screen, args)
1369 -- Only print client on the same screen as this widget
1370 if c.screen ~= screen then return end
1371 for k, t in ipairs(capi.screen[screen]:tags()) do
1372 if t.selected and c:tags()[t] then
1373 return widget_tasklist_label_common(c, args)
1378 --- Create a standard titlebar.
1379 -- @param c The client.
1380 -- @param args Arguments.
1381 -- fg: the foreground color.
1382 -- bg: the background color.
1383 -- fg_focus: the foreground color for focused window.
1384 -- fg_focus: the background color for focused window.
1385 function titlebar.add(c, args)
1386 if not args then args = {} end
1387 -- Store colors
1388 titlebar.data[c] = {}
1389 titlebar.data[c].fg = args.fg or theme.titlebar_fg_normal or theme.fg_normal
1390 titlebar.data[c].bg = args.bg or theme.titlebar_bg_normal or theme.bg_normal
1391 titlebar.data[c].fg_focus = args.fg_focus or theme.titlebar_fg_focus or theme.fg_focus
1392 titlebar.data[c].bg_focus = args.bg_focus or theme.titlebar_bg_focus or theme.bg_focus
1394 -- Built args
1395 local targs = {}
1396 if args.fg then targs.fg = args.fg end
1397 if args.bg then targs.bg = args.bg end
1398 local tb = capi.titlebar(targs)
1400 local title = capi.widget({ type = "textbox", name = "title", align = "flex" })
1401 title:mouse_add(capi.mouse({ }, 1, function (t) t.client:mouse_move() end))
1402 title:mouse_add(capi.mouse({ args.modkey }, 3, function (t) t.client:mouse_resize() end))
1404 local close_button= capi.widget({ type = "textbox", name = "close", align = "right" })
1405 close_button:mouse_add(capi.mouse({ }, 1, function (t) t.client:kill() end))
1407 tb.widgets =
1409 capi.widget({ type = "appicon", name = "appicon", align = "left" }),
1410 title,
1411 close_button
1414 titlebar.update(c)
1416 c.titlebar = tb
1419 --- Update a titlebar. This should be called in some hooks.
1420 -- @param c The client to update.
1421 function titlebar.update(c)
1422 if c.titlebar and titlebar.data[c] then
1423 local widgets = c.titlebar.widgets
1424 local title, close
1425 for k, v in ipairs(widgets) do
1426 if v.name == "title" then title = v end
1427 if v.name == "close" then close = v end
1428 if title and close then break end
1430 if title then
1431 title.text = " " .. escape(c.name)
1433 if capi.client.focus == c then
1434 c.titlebar.fg = titlebar.data[c].fg_focus
1435 c.titlebar.bg = titlebar.data[c].bg_focus
1436 if close then
1437 close.text = "<bg image=\"@AWESOME_ICON_PATH@/titlebar/closer.png\" resize=\"true\"/>"
1439 else
1440 c.titlebar.fg = titlebar.data[c].fg
1441 c.titlebar.bg = titlebar.data[c].bg
1442 if close then
1443 close.text = "<bg image=\"@AWESOME_ICON_PATH@/titlebar/close.png\" resize=\"true\"/>"
1449 --- Remove a titlebar from a client.
1450 -- @param c The client.
1451 function titlebar.remove(c)
1452 c.titlebar = nil
1453 titlebar.data[c] = nil
1456 --- Set the beautiful theme if any.
1457 -- @param The beautiful theme.
1458 function beautiful.register(btheme)
1459 if btheme then
1460 theme = btheme
1461 else
1462 theme = {}
1466 -- Register standards hooks
1467 hooks.arrange.register(tag.history.update)
1469 hooks.focus.register(client.focus.history.add)
1470 hooks.unmanage.register(client.focus.history.delete)
1471 hooks.unmanage.register(client_maximize_clean)
1473 hooks.focus.register(titlebar.update)
1474 hooks.unfocus.register(titlebar.update)
1475 hooks.titleupdate.register(titlebar.update)
1476 hooks.unmanage.register(titlebar.remove)
1478 hooks.urgent.register(client.urgent.stack.add)
1479 hooks.focus.register(client.urgent.stack.delete)
1480 hooks.unmanage.register(client.urgent.stack.delete)
1482 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80