2 -- Copyright (c) 2007, Bart Trojanowski <bart@jukie.net>
4 -- WMII event loop, in lua
6 -- http://www.jukie.net/~bart/blog/tag/wmiirc-lua
7 -- git://www.jukie.net/wmiirc-lua.git/
10 -- ========================================================================
12 -- ========================================================================
18 wmii.lua - WMII event-loop methods in lua
24 -- Write something to the wmii filesystem, in this case a key event.
25 wmii.write ("/event", "Key Mod1-j")
27 -- Set your wmii /ctl parameters
32 -- Configure wmii.lua parameters
34 xterm = 'x-terminal-emulator'
37 -- Now start the event loop
42 wmii.lua provides methods for replacing the stock sh-based wmiirc shipped with
43 wmii 3.6 and newer with a lua-based event loop.
45 It should be used by your wmiirc
54 -- ========================================================================
56 -- ========================================================================
58 local wmiidir
= ("%HOME_WMII%"):gsub("^~", os
.getenv("HOME"))
59 local wmiirc
= wmiidir
.. "/wmiirc"
61 package
.path
= wmiidir
.. "/core/?.lua;" ..
62 wmiidir
.. "/plugins/?.lua;" ..
64 package
.cpath
= wmiidir
.. "/core/?.so;" ..
65 wmiidir
.. "/plugins/?.so;" ..
68 local ixp
= require
"ixp"
69 local eventloop
= require
"eventloop"
70 local history
= require
"history"
72 local io
= require("io")
73 local os
= require("os")
74 local string = require("string")
75 local table = require("table")
76 local math
= require("math")
82 local package
= package
83 local require
= require
84 local tostring = tostring
85 local tonumber = tonumber
86 local setmetatable
= setmetatable
88 -- kinda silly, but there is no working liblua5.1-posix0 in ubuntu
89 -- so we make it optional
90 local have_posix
, posix
= pcall(require
,"posix")
97 -- but having posix is not enough as the API changes, so we try each one
98 if posix
.getprocessid
then
99 local stat
,rc
= pcall (posix
.getprocessid
, "pid")
104 if not myid
and posix
.getpid
then
105 local stat
,rc
= pcall (posix
.getpid
, "pid")
112 -- we were not able to get the PID, but we can create a random number
113 local now
= tonumber(os
.date("%s"))
115 myid
= math
.random(10000)
118 -- returns boolean indicating if path is a directory
119 local function is_directory(path
)
121 local stat
= posix
.stat(wmiidir
)
122 return stat
and stat
.type == "directory"
124 local path
= path
:gsub('([\\"])', '\\%1')
125 local cmd
= '[ -d "' .. path
.. '" ]'
126 local rc
= os
.execute (cmd
)
131 -- ========================================================================
133 -- ========================================================================
135 -- wmiir points to the wmiir executable
136 -- TODO: need to make sure that wmiir is in path, and if not find it
137 local wmiir
= "wmiir"
139 -- wimenu points to the wimenu
140 local wimenu
= "wimenu"
141 if 0 ~= os
.execute("which wimenu") then
142 if 0 ~= os
.execute("which dmenu") then
143 error ("could not find wimenu or dmenu in the PATH")
148 -- wmii_adr is the address we use when connecting using ixp
149 -- TODO: hide this magic in luaixp
150 local wmii_adr
= os
.getenv("WMII_ADDRESS") or ""
151 if wmii_adr
== "" then
152 local user
= os
.getenv("USER") or ""
154 local cmd
= "id -n -u"
155 local file
= io
.popen (cmd
)
157 user
= file
:read("*l") or ""
160 if not user
or user
== "" then
161 error ("no WMII_ADDRESS environment variable set, and "
162 .. "could not determine user name for socket location")
165 local disp
= os
.getenv("DISPLAY") or ":0.0"
166 wmii_adr
= "unix!/tmp/ns." .. user
.. "."
167 .. disp
:match("(:%d+)") .. "/wmii"
170 -- wmixp is the ixp context we use to talk to wmii
171 local wmixp
= ixp
.new(wmii_adr
)
173 -- history of previous views, view_hist[#view_hist] is the last one
174 local view_hist
= {} -- sorted with 1 being the oldest
175 local view_hist_max
= 50 -- max number to keep track of
177 -- allow for a client to be forced to a tag
178 local next_client_goes_to_tag
= nil
180 -- program and action histories
181 local prog_hist
= history
.new (20)
182 local action_hist
= history
.new(10)
184 -- where to find plugins
187 table.insert(plugin_paths
, wmiidir
.. "/plugins/?.so")
188 table.insert(plugin_paths
, wmiidir
.. "/plugins/?.lua")
190 for path
in string.gmatch(package
.path
, "[^;]+") do
191 local wmiidir
= path
:gsub("%?.*$", "wmii")
192 if is_directory(wmiidir
) then
193 local path
= path
:gsub("%?", "wmii/?")
194 table.insert(plugin_paths
, path
)
198 for path
in string.gmatch(package
.cpath
, "[^;]+") do
199 local wmiidir
= path
:gsub("%?.*$", "wmii")
200 if is_directory(wmiidir
) then
201 local path
= path
:gsub("%?", "wmii/?")
202 table.insert(plugin_paths
, path
)
206 -- where to find wmiirc (see find_wmiirc())
207 wmiirc_path
= wmiidir
.. "/wmiirc.lua;"
208 .. wmiidir
.. "/wmiirc;"
209 .. "%RC_DIR%/wmiirc.lua;"
212 -- ========================================================================
214 -- ========================================================================
221 log the message provided in c<str>
223 currently just writes to io.stderr
228 if get_conf("debug") then
229 io
.stderr
:write (str
.. "\n")
240 generate an error message, c<str>, but do not terminate lua
242 returns the string argument
256 =item execute ( cmd )
258 setsid wrapper for os.execute(c<cmd>)
262 local wmiir_has_setsid
= nil
263 function execute (cmd
)
264 log (" executing: " .. cmd
)
265 if wmiir_has_setsid
== nil then
266 -- test if wmiir has setsid support
267 local rc
= os
.execute (wmiir
.. " setsid true")
268 wmiir_has_setsid
= (rc
== 0)
269 log ("wmiir " .. (wmiir_has_setsid
and "has" or "does not have") .. " setsid support")
272 if wmiir_has_setsid
then
273 cmd
= wmiir
.. " setsid " .. cmd
277 local rc
= os
.execute (cmd
)
278 log (" ... rc=" .. tostring(rc
))
287 =item shell_execute ( cmd )
289 setsid wrapper for os.execute(c<cmd>)
290 like execute() but runs under a subshell
294 function shell_execute (cmd
)
295 cmd
= cmd
:gsub('([\\"])', '\\%1')
296 cmd
= 'sh -c "' .. cmd
.. '"'
306 =item find_wmiirc ( )
308 Locates the wmiirc script. It looks in %HOME_WMII% and %RC_DIR%
309 for the first lua script bearing the name wmiirc.lua or wmiirc. Returns
314 function find_wmiirc()
316 for fn
in string.gmatch(wmiirc_path
, "[^;]+") do
317 -- try to locate the files locally
318 local file
= io
.open(fn
, "r")
320 local txt
= file
:read("*line")
322 if type(txt
) == 'string' and txt
:match("lua") then
331 -- ========================================================================
332 -- MAIN ACCESS FUNCTIONS
333 -- ========================================================================
338 =item ls ( dir, fmt )
340 List the wmii filesystem directory provided in C<dir>, in the format specified
343 Returns an iterator of TODO
347 function ls (dir
, fmt
)
348 local verbose
= fmt
and fmt
:match("l")
350 local s
= wmixp
:stat(dir
)
352 return function () return nil end
354 if s
.modestr
:match("^[^d]") then
356 return stat2str(verbose
, s
)
360 local itr
= wmixp
:idir (dir
)
371 return stat2str(verbose
, s
)
377 local function stat2str(verbose
, stat
)
379 return string.format("%s %s %s %5d %s %s", stat
.modestr
, stat
.uid
, stat
.gid
, stat
.length
, stat
.timestr
, stat
.name
)
381 if stat
.modestr
:match("^d") then
382 return stat
.name
.. "/"
389 -- ------------------------------------------------------------------------
390 -- read all contents of a wmii virtual file
392 return wmixp
:read (file
)
395 -- ------------------------------------------------------------------------
396 -- return an iterator which walks all the lines in the file
399 -- for event in wmii.iread("/ctl")
403 -- NOTE: don't use iread for files that could block, as this will interfere
404 -- with timer processing and event delivery. Instead fork off a process to
405 -- execute wmiir and read back the responses via callback.
406 function iread (file
)
407 return wmixp
:iread(file
)
410 -- ------------------------------------------------------------------------
411 -- create a wmii file, optionally write data to it
412 function create (file
, data
)
413 wmixp
:create(file
, data
)
416 -- ------------------------------------------------------------------------
417 -- remove a wmii file
418 function remove (file
)
422 -- ------------------------------------------------------------------------
423 -- write a value to a wmii virtual file system
424 function write (file
, value
)
425 wmixp
:write (file
, value
)
428 -- ------------------------------------------------------------------------
429 -- setup a table describing the menu command
430 local function menu_cmd (prompt
)
431 local cmdt
= { wimenu
}
434 cmdt
[#cmdt
+1] = "'" .. prompt
.. "'"
437 if wimenu
== "dmenu" then
439 local normcolors
= get_ctl("normcolors")
441 local nf
, nb
= normcolors
:match("(#%x+)%s+(#%x+)%s#%x+")
443 cmdt
[#cmdt
+1] = "-nf"
444 cmdt
[#cmdt
+1] = "'" .. nf
.. "'"
447 cmdt
[#cmdt
+1] = "-nb"
448 cmdt
[#cmdt
+1] = "'" .. nb
.. "'"
451 local focuscolors
= get_ctl("focuscolors")
453 local sf
, sb
= focuscolors
:match("(#%x+)%s+(#%x+)%s#%x+")
455 cmdt
[#cmdt
+1] = "-sf"
456 cmdt
[#cmdt
+1] = "'" .. sf
.. "'"
459 cmdt
[#cmdt
+1] = "-sb"
460 cmdt
[#cmdt
+1] = "'" .. sb
.. "'"
468 -- ------------------------------------------------------------------------
469 -- displays the menu given an table of entires, returns selected text
470 function menu (tbl
, prompt
)
471 local menu
= menu_cmd(prompt
)
473 local infile
= os
.tmpname()
474 local fh
= io
.open (infile
, "w+")
477 for i
,v
in pairs(tbl
) do
478 if type(i
) == 'number' and type(v
) == 'string' then
487 local outfile
= os
.tmpname()
490 menu
[#menu
+1] = infile
492 menu
[#menu
+1] = outfile
494 local cmd
= table.concat(menu
," ")
497 fh
= io
.open (outfile
, "r")
500 local sel
= fh
:read("*l")
506 -- ------------------------------------------------------------------------
507 -- displays the a tag selection menu, returns selected tag
509 local tags
= get_tags()
511 return menu(tags
, "tag:")
514 -- ------------------------------------------------------------------------
515 -- displays the a program menu, returns selected program
516 function prog_menu ()
517 local menu
= menu_cmd("cmd:")
519 local outfile
= os
.tmpname()
522 menu
[#menu
+1] = outfile
525 for n
in prog_hist
:walk_reverse_unique() do
526 hstt
[#hstt
+1] = "echo '" .. n
.. "' ; "
529 local cmd
= "(" .. table.concat(hstt
)
531 .. table.concat(menu
," ")
534 local fh
= io
.open (outfile
, "rb")
537 local prog
= fh
:read("*l")
543 -- ------------------------------------------------------------------------
544 -- returns a table of sorted tags names
548 for s
in wmixp
:idir ("/tag") do
549 if s
.name
and not (s
.name
== "sel") then
557 -- ------------------------------------------------------------------------
558 -- returns a table of sorted screen names
559 function get_screens()
563 for s
in wmixp
:idir ("/screen") do
564 if s
.name
and not (s
.name
== "sel") then
576 -- ------------------------------------------------------------------------
577 -- returns current view, on current screen or specified screen
578 function get_view(screen
)
579 return get_screen_ctl(screen
, "view") or get_ctl("view")
582 -- ------------------------------------------------------------------------
583 -- changes the current view to the name given
584 function set_view(sel
)
585 local cur
= get_view()
586 local all
= get_tags()
588 if #all
< 2 or sel
== cur
then
589 -- nothing to do if we have less then 2 tags
593 if not (type(sel
) == "string") then
594 error ("string argument expected")
598 write ("/ctl", "view " .. sel
)
601 -- ------------------------------------------------------------------------
602 -- changes the current view to the index given
603 function set_view_index(sel
)
604 local cur
= get_view()
605 local all
= get_tags()
608 -- nothing to do if we have less then 2 tags
612 local num
= tonumber (sel
)
614 error ("number argument expected")
617 local name
= all
[sel
]
618 if not name
or name
== cur
then
623 write ("/ctl", "view " .. name
)
626 -- ------------------------------------------------------------------------
627 -- chnages to current view by offset given
628 function set_view_ofs(jump
)
629 local cur
= get_view()
630 local all
= get_tags()
633 -- nothing to do if we have less then 2 tags
638 if (jump
< - #all
) or (jump
> #all
) then
639 error ("view selector is out of range")
642 -- find the one that's selected index
645 for i
,v
in pairs (all
) do
646 if v
== cur
then curi
= i
end
650 local newi
= math
.fmod(#all
+ curi
+ jump
- 1, #all
) + 1
651 if (newi
< - #all
) or (newi
> #all
) then
652 error ("error computng new view")
655 write ("/ctl", "view " .. all
[newi
])
658 -- ------------------------------------------------------------------------
659 -- toggle between last view and current view
660 function toggle_view()
661 local last
= view_hist
[#view_hist
]
667 -- ========================================================================
669 -- ========================================================================
671 local action_handlers
= {
672 man
= function (act
, args
)
673 local xterm
= get_conf("xterm") or "xterm"
675 if (not page
) or (not page
:match("%S")) then
676 page
= wmiidir
.. "/wmii.3lua"
678 local cmd
= xterm
.. " -e man " .. page
.. " &"
683 write ("/ctl", "quit")
686 exec
= function (act
, args
)
687 local what
= args
or "wmii-lua"
688 log (" asking wmii to exec " .. tostring(what
))
690 write ("/ctl", "exec " .. what
)
693 xlock
= function (act
)
694 local cmd
= get_conf("xlock") or "xscreensaver-command --lock"
700 local wmiirc
= find_wmiirc()
702 log (" executing: lua " .. wmiirc
)
705 posix
.exec ("/bin/sh", "-c", "exec lua wmiirc")
706 posix
.exec ("%LUA_BIN%", wmiirc
)
707 posix
.exec ("/usr/bin/lua", wmiirc
)
710 log("sorry cannot restart; you don't have lua's posix library.")
715 wmixp
:write ("/client/sel/ctl", "Urgent toggle")
720 -- TODO: consider storing list of executables around, and
721 -- this will then reinitialize that list
722 log (" TODO: rehash")
726 -- TODO: this should eventually update something on the /rbar
727 log (" TODO: status")
735 =item add_action_handler (action, fn)
737 Add an Alt-a action handler callback function, I<fn>, for the given action string I<action>.
741 function add_action_handler (action
, fn
)
743 if type(action
) ~= "string" or type(fn
) ~= "function" then
744 error ("expecting a string and a function")
747 if action_handlers
[action
] then
748 error ("action handler already exists for '" .. action
.. "'")
751 action_handlers
[action
] = fn
757 =item remove_action_handler (action)
759 Remove an action handler callback function for the given action string I<action>.
763 function remove_action_handler (action
)
765 action_handlers
[action
] = nil
768 -- ========================================================================
770 -- ========================================================================
772 function ke_fullscreen_toggle()
773 wmixp
:write ("/client/sel/ctl", "Fullscreen toggle")
776 function ke_view_starting_with_letter (letter
)
779 -- find the view name in history in reverse order
780 for i
=#view_hist
,1,-1 do
782 if letter
== v
:sub(1,1) then
788 -- otherwise just pick the first view that matches
789 local all
= get_tags()
790 for i
,v
in pairs(all
) do
791 if letter
== v
:sub(1,1) then
800 function ke_handle_action()
805 for n
in action_hist
:walk_reverse() do
807 actions
[#actions
+1] = n
813 for n
,v
in pairs(action_handlers
) do
815 actions
[#actions
+1] = n
820 local text
= menu(actions
, "action:")
822 log ("Action: " .. text
)
825 local si
= text
:find("%s")
827 act
,args
= string.match(text
.. " ", "(%w+)%s(.+)")
830 local fn
= action_handlers
[act
]
832 action_hist
:add (act
)
833 local r
, err
= pcall (fn
, act
, args
)
835 log ("WARNING: " .. tostring(err
))
843 local key_handlers
= {
844 ["*"] = function (key
)
848 -- execution and actions
849 ["Mod1-Return"] = function (key
)
850 local xterm
= get_conf("xterm") or "xterm"
851 execute (xterm
.. " &")
853 ["Mod1-Shift-Return"] = function (key
)
854 local tag = tag_menu()
856 local xterm
= get_conf("xterm") or "xterm"
857 log (" executing on: " .. tag)
858 next_client_goes_to_tag
= tag
859 execute (xterm
.. " &")
862 ["Mod1-a"] = function (key
)
865 ["Mod1-p"] = function (key
)
866 local prog
= prog_menu()
868 prog_hist
:add(prog
:match("([^ ]+)"))
869 execute (prog
.. " &")
872 ["Mod1-Shift-p"] = function (key
)
873 local tag = tag_menu()
875 local prog
= prog_menu()
877 log (" executing on: " .. tag)
878 next_client_goes_to_tag
= tag
879 execute (prog
.. " &")
883 ["Mod1-Shift-c"] = function (key
)
884 write ("/client/sel/ctl", "kill")
887 -- HJKL active selection
888 ["Mod1-h"] = function (key
)
889 write ("/tag/sel/ctl", "select left")
891 ["Mod1-l"] = function (key
)
892 write ("/tag/sel/ctl", "select right")
894 ["Mod1-j"] = function (key
)
895 write ("/tag/sel/ctl", "select down")
897 ["Mod1-k"] = function (key
)
898 write ("/tag/sel/ctl", "select up")
902 ["Mod1-Shift-h"] = function (key
)
903 write ("/tag/sel/ctl", "send sel left")
905 ["Mod1-Shift-l"] = function (key
)
906 write ("/tag/sel/ctl", "send sel right")
908 ["Mod1-Shift-j"] = function (key
)
909 write ("/tag/sel/ctl", "send sel down")
911 ["Mod1-Shift-k"] = function (key
)
912 write ("/tag/sel/ctl", "send sel up")
916 ["Mod1-space"] = function (key
)
917 write ("/tag/sel/ctl", "select toggle")
919 ["Mod1-Shift-space"] = function (key
)
920 write ("/tag/sel/ctl", "send sel toggle")
923 -- work spaces (# and @ are wildcards for numbers and letters)
924 ["Mod4-#"] = function (key
, num
)
925 -- first attempt to find a view that starts with the number requested
926 local num_str
= tostring(num
)
927 if not ke_view_starting_with_letter (num_str
) then
928 -- if we fail, then set it to the index requested
932 ["Mod4-Shift-#"] = function (key
, num
)
933 write ("/client/sel/tags", tostring(num
))
935 ["Mod4-@"] = function (key
, letter
)
936 ke_view_starting_with_letter (letter
)
938 ["Mod4-Shift-@"] = function (key
, letter
)
939 local all
= get_tags()
941 for i
,v
in pairs(all
) do
942 if letter
== v
:sub(1,1) then
943 write ("/client/sel/tags", v
)
948 ["Mod1-comma"] = function (key
)
951 ["Mod1-period"] = function (key
)
954 ["Mod1-r"] = function (key
)
955 -- got to the last view
959 -- switching views and retagging
960 ["Mod1-t"] = function (key
)
962 local tag = tag_menu()
967 ["Mod1-Shift-t"] = function (key
)
968 -- move selected client to a tag
969 local tag = tag_menu()
971 write ("/client/sel/tags", tag)
974 ["Mod1-Shift-r"] = function (key
)
975 -- move selected client to a tag, and follow
976 local tag = tag_menu()
978 -- get the current window id
979 local xid
= wmixp
:read("/client/sel/ctl") or ""
982 write("/client/sel/tags", tag)
984 -- if the client is still in this tag, then
985 -- it might have been a regexp tag... check
986 local test
= wmixp
:read("/client/sel/ctl")
987 if not test
or test
~= xid
then
988 -- if the window moved, follow it
993 ["Mod1-Control-t"] = function (key
)
994 log (" TODO: Mod1-Control-t: " .. key
)
998 ["Mod1-d"] = function (key
)
999 write("/tag/sel/ctl", "colmode sel default-max")
1001 ["Mod1-s"] = function (key
)
1002 write("/tag/sel/ctl", "colmode sel stack-max")
1004 ["Mod1-m"] = function (key
)
1005 write("/tag/sel/ctl", "colmode sel stack+max")
1007 ["Mod1-f"] = function (key
)
1008 ke_fullscreen_toggle()
1011 -- changing client flags
1012 ["Shift-Mod1-f"] = function (key
)
1013 log ("setting flags")
1015 local cli
= get_client ()
1017 local flags
= { "suspend", "raw" }
1018 local current_flags
= cli
:flags_string()
1020 local what
= menu(flags
, "current flags: " .. current_flags
.. " toggle:")
1024 ["Mod4-space"] = function (key
)
1025 local cli
= get_client ()
1033 =item add_key_handler (key, fn)
1035 Add a keypress handler callback function, I<fn>, for the given key sequence I<key>.
1039 function add_key_handler (key
, fn
)
1041 if type(key
) ~= "string" or type(fn
) ~= "function" then
1042 error ("expecting a string and a function")
1045 local onlyKey
= key
:match("([^-]+)$")
1046 if 0 ~= os
.execute("xmodmap -pk | grep -q '(" .. onlyKey
.. ")'") then
1047 return warn ("xmodmap -pk doesn't know about '" .. onlyKey
.. "'")
1050 if key_handlers
[key
] then
1051 -- TODO: we may wish to allow multiple handlers for one keypress
1052 error ("key handler already exists for '" .. key
.. "'")
1055 key_handlers
[key
] = fn
1061 =item remove_key_handler (key)
1063 Remove an key handler callback function for the given key I<key>.
1065 Returns the handler callback function.
1069 function remove_key_handler (key
)
1071 local fn
= key_handlers
[key
]
1072 key_handlers
[key
] = nil
1079 =item remap_key_handler (old_key, new_key)
1081 Remove a key handler callback function from the given key I<old_key>,
1082 and assign it to a new key I<new_key>.
1086 function remap_key_handler (old_key
, new_key
)
1088 local fn
= remove_key_handler(old_key
)
1090 return add_key_handler (new_key
, fn
)
1094 -- ------------------------------------------------------------------------
1095 -- update the /keys wmii file with the list of all handlers
1096 local alphabet
="abcdefghijklmnopqrstuvwxyz"
1097 function update_active_keys ()
1100 for x
,y
in pairs(key_handlers
) do
1101 if x
:find("%w") then
1102 local i
= x
:find("#$")
1106 t
[#t
+ 1] = x
:sub(1,i
-1) .. j
1112 for j
=1,alphabet
:len() do
1113 local a
= alphabet
:sub(j
,j
)
1114 t
[#t
+ 1] = x
:sub(1,i
-1) .. a
1117 t
[#t
+ 1] = tostring(x
)
1122 local all_keys
= table.concat(t
, "\n")
1123 --log ("setting /keys to...\n" .. all_keys .. "\n");
1124 write ("/keys", all_keys
)
1127 -- ------------------------------------------------------------------------
1128 -- update the /lbar wmii file with the current tags
1129 function update_displayed_tags ()
1130 -- list of all screens
1131 local screens
= get_screens()
1133 update_displayed_tags_on_screen()
1138 for i
,s
in pairs(screens
) do
1139 update_displayed_tags_on_screen(s
)
1143 function tag_display(tag, selected
)
1147 function update_displayed_tags_on_screen(s
)
1148 local lbar
= "/lbar"
1150 lbar
= "/screen/" .. s
.. "/lbar"
1153 -- colours for screen
1154 local fc
= get_screen_ctl(s
, "focuscolors") or get_ctl("focuscolors") or ""
1155 local nc
= get_screen_ctl(s
, "normcolors") or get_ctl("normcolors") or ""
1157 -- build up a table of existing tags in the /lbar
1160 for ent
in wmixp
:idir (lbar
) do
1164 -- for all actual tags in use create any entries in /lbar we don't have
1165 -- clear the old table entries if we have them
1166 local cur
= get_view(s
)
1167 local all
= get_tags()
1169 for i
,v
in pairs(all
) do
1174 local str
= tag_display(v
,selected
)
1176 create (lbar
.. "/" .. v
, color
.. " " .. str
)
1178 write (lbar
.. "/" .. v
, color
.. " " .. str
)
1182 -- ignore widgets on the lbar
1183 for i
,v
in pairs(widgets
) do
1184 if v
.bar
== 'lbar' then
1189 -- anything left in the old table should be removed now
1190 for i
,v
in pairs(old
) do
1192 remove(lbar
.."/"..i
)
1196 -- this is a hack, and should brobably be rethought
1197 -- the intent is to distinguish the multiple screens
1199 create ("/screen/"..s
.."/lbar/000000000000000000", '-'..s
..'-')
1203 function create_tag_widget(name
)
1204 local nc
= get_ctl("normcolors") or ""
1205 local screens
= get_screens()
1207 create ("/lbar/" .. name
, nc
.. " " .. name
)
1211 for i
,s
in pairs(screens
) do
1212 create ("/screen/"..s
.."/lbar/" .. name
, nc
.. " " .. tag_display(name
))
1216 function destroy_tag_widget(name
)
1217 local screens
= get_screens()
1219 remove ("/lbar/" .. name
)
1223 for i
,s
in pairs(screens
) do
1224 remove ("/screen/"..s
.."/lbar/" .. name
)
1229 -- ========================================================================
1231 -- ========================================================================
1233 local widget_ev_handlers
= {
1239 =item _handle_widget_event (ev, arg)
1241 Top-level event handler for redispatching events to widgets. This event
1242 handler is added for any widget event that currently has a widget registered
1245 Valid widget events are currently
1247 RightBarMouseDown <buttonnumber> <widgetname>
1248 RightBarClick <buttonnumber> <widgetname>
1250 the "Click" event is sent on mouseup.
1252 The callbacks are given only the button number as their argument, to avoid the
1258 local function _handle_widget_event (ev
, arg
)
1260 log("_handle_widget_event: " .. tostring(ev
) .. " - " .. tostring(arg
))
1262 -- parse arg to strip out our widget name
1263 local number,wname
= string.match(arg
, "(%d+)%s+(.+)")
1265 -- check our dispatch table for that widget
1267 log("Didn't find wname")
1271 local wtable
= widget_ev_handlers
[wname
]
1273 log("No widget cares about" .. wname
)
1277 local fn
= wtable
[ev
] or wtable
["*"]
1279 success
, err
= pcall( fn
, ev
, tonumber(number) )
1281 log("Callback had an error in _handle_widget_event: " .. tostring(err
) )
1285 log("no function found for " .. ev
)
1289 local ev_handlers
= {
1290 ["*"] = function (ev
, arg
)
1291 log ("ev: " .. tostring(ev
) .. " - " .. tostring(arg
))
1294 RightBarClick
= _handle_widget_event
,
1296 -- process timer events
1297 ProcessTimerEvents
= function (ev
, arg
)
1301 -- exit if another wmiirc started up
1302 Start
= function (ev
, arg
)
1304 if arg
== "wmiirc" then
1305 -- backwards compatibility with bash version
1306 log (" exiting; pid=" .. tostring(myid
))
1310 -- ignore if it came from us
1311 local pid
= string.match(arg
, "wmiirc (%d+)")
1313 local pid
= tonumber (pid
)
1314 if not (pid
== myid
) then
1315 log (" exiting; pid=" .. tostring(myid
))
1325 CreateTag
= function (ev
, arg
)
1326 log ("CreateTag: " .. arg
)
1327 create_tag_widget(arg
)
1329 DestroyTag
= function (ev
, arg
)
1330 log ("DestroyTag: " .. arg
)
1331 destroy_tag_widget(arg
)
1333 -- remove the tag from history
1335 for i
=#view_hist
,1,-1 do
1338 table.remove(view_hist
,i
)
1343 FocusTag
= function (ev
, arg
)
1344 log ("FocusTag: " .. arg
)
1346 local tag,scrn
= arg
:match("(%w+)%s*(%w*)")
1351 local file
= "/lbar/" .. tag
1352 if scrn
and scrn
:len() > 0 then
1353 file
= "/screen/" .. scrn
.. file
1356 local fc
= get_screen_ctl(scrn
, "focuscolors") or get_ctl("focuscolors") or ""
1357 log ("# echo " .. fc
.. " " .. tag .. " | wmiir write " .. file
)
1359 str
= tag_display(tag,true)
1360 create (file
, fc
.. " " .. str
)
1361 write (file
, fc
.. " " .. str
)
1363 UnfocusTag
= function (ev
, arg
)
1364 log ("UnfocusTag: " .. arg
)
1366 local tag,scrn
= arg
:match("(%w+)%s*(%w*)")
1371 local file
= "/lbar/" .. tag
1372 if scrn
and scrn
:len() > 0 then
1373 file
= "/screen/" .. scrn
.. file
1376 local nc
= get_screen_ctl(scrn
, "normcolors") or get_ctl("normcolors") or ""
1377 log ("# echo " .. nc
.. " " .. tag .. " | wmiir write " .. file
)
1379 str
= tag_display(tag,true)
1380 create (file
, nc
.. " " .. str
)
1381 write (file
, nc
.. " " .. str
)
1383 -- don't duplicate the last entry
1384 if not (tag == view_hist
[#view_hist
]) then
1385 view_hist
[#view_hist
+1] = tag
1387 -- limit to view_hist_max
1388 if #view_hist
> view_hist_max
then
1389 table.remove(view_hist
, 1)
1394 -- key event handling
1395 Key
= function (ev
, arg
)
1396 log ("Key: " .. arg
)
1398 -- can we find an exact match?
1399 local fn
= key_handlers
[arg
]
1401 local key
= arg
:gsub("-%d$", "-#")
1402 -- can we find a match with a # wild card for the number
1403 fn
= key_handlers
[key
]
1405 -- convert the trailing number to a number
1406 magic
= tonumber(arg
:match("-(%d)$"))
1410 local key
= arg
:gsub("-%a$", "-@")
1411 -- can we find a match with a @ wild card for a letter
1412 fn
= key_handlers
[key
]
1414 -- split off the trailing letter
1415 magic
= arg
:match("-(%a)$")
1419 -- everything else failed, try default match
1420 fn
= key_handlers
["*"]
1423 local r
, err
= pcall (fn
, arg
, magic
)
1425 log ("WARNING: " .. tostring(err
))
1430 -- mouse handling on the lbar
1431 LeftBarClick
= function (ev
, arg
)
1432 local button
,tag = string.match(arg
, "(%w+)%s+(%S+)")
1437 ClientFocus
= function (ev
, arg
)
1438 log ("ClientFocus: " .. arg
)
1439 client_focused (arg
)
1441 ColumnFocus
= function (ev
, arg
)
1442 log ("ColumnFocus: " .. arg
)
1446 CreateClient
= function (ev
, arg
)
1447 if next_client_goes_to_tag
then
1448 local tag = next_client_goes_to_tag
1450 next_client_goes_to_tag
= nil
1451 write ("/client/" .. cli
.. "/tags", tag)
1454 client_created (arg
)
1456 DestroyClient
= function (ev
, arg
)
1457 client_destoryed (arg
)
1461 UrgentTag
= function (ev
, arg
)
1462 log ("UrgentTag: " .. arg
)
1463 write ("/lbar/" .. arg
, "*" .. arg
);
1465 NotUrgentTag
= function (ev
, arg
)
1466 log ("NotUrgentTag: " .. arg
)
1467 write ("/lbar/" .. arg
, arg
);
1471 Unresponsive
= function (ev
, arg
)
1472 log ("Unresponsive: " .. arg
)
1473 -- TODO ask the user if it shoudl be killed off
1476 Notice
= function (ev
, arg
)
1477 log ("Notice: " .. arg
)
1478 -- TODO send to the message plugin (or implement there)
1487 =item add_widget_event_handler (wname, ev, fn)
1489 Add an event handler callback for the I<ev> event on the widget named I<wname>
1494 function add_widget_event_handler (wname
, ev
, fn
)
1495 if type(wname
) ~= "string" or type(ev
) ~= "string" or type(fn
) ~= "function" then
1496 error ("expecting string for widget name, string for event name and a function callback")
1499 -- Make sure the widget event handler is present
1500 if not ev_handlers
[ev
] then
1501 ev_handlers
[ev
] = _handle_widget_event
1504 if not widget_ev_handlers
[wname
] then
1505 widget_ev_handlers
[wname
] = { }
1508 if widget_ev_handlers
[wname
][ev
] then
1509 -- TODO: we may wish to allow multiple handlers for one event
1510 error ("event handler already exists on widget '" .. wname
.. "' for '" .. ev
.. "'")
1513 widget_ev_handlers
[wname
][ev
] = fn
1519 =item remove_widget_event_handler (wname, ev)
1521 Remove an event handler callback function for the I<ev> on the widget named I<wname>.
1525 function remove_event_handler (wname
, ev
)
1527 if not widget_ev_handlers
[wname
] then
1531 widget_ev_handlers
[wname
][ev
] = nil
1537 =item add_event_handler (ev, fn)
1539 Add an event handler callback function, I<fn>, for the given event I<ev>.
1543 -- TODO: Need to allow registering widgets for RightBar* events. Should probably be done with its own event table, though
1544 function add_event_handler (ev
, fn
)
1545 if type(ev
) ~= "string" or type(fn
) ~= "function" then
1546 error ("expecting a string and a function")
1549 if ev_handlers
[ev
] then
1550 -- TODO: we may wish to allow multiple handlers for one event
1551 error ("event handler already exists for '" .. ev
.. "'")
1555 ev_handlers
[ev
] = fn
1561 =item remove_event_handler (ev)
1563 Remove an event handler callback function for the given event I<ev>.
1567 function remove_event_handler (ev
)
1569 ev_handlers
[ev
] = nil
1573 -- ========================================================================
1574 -- MAIN INTERFACE FUNCTIONS
1575 -- ========================================================================
1578 xterm
= 'x-terminal-emulator',
1579 xlock
= "xscreensaver-command --lock",
1583 -- ------------------------------------------------------------------------
1584 -- write configuration to /ctl wmii file
1585 -- wmii.set_ctl({ "var" = "val", ...})
1586 -- wmii.set_ctl("var, "val")
1587 function set_ctl (first
,second
)
1588 if type(first
) == "table" and second
== nil then
1590 for x
, y
in pairs(first
) do
1591 write ("/ctl", x
.. " " .. y
)
1594 elseif type(first
) == "string" and type(second
) == "string" then
1595 write ("/ctl", first
.. " " .. second
)
1598 error ("expecting a table or two string arguments")
1602 -- ------------------------------------------------------------------------
1603 -- read a value from /ctl wmii file
1604 -- table = wmii.get_ctl()
1605 -- value = wmii.get_ctl("variable")
1606 function get_ctl (name
)
1609 for s
in iread("/ctl") do
1610 local var
,val
= s
:match("(%w+)%s+(.+)")
1622 -- ------------------------------------------------------------------------
1623 -- write configuration to /screen/*/ctl wmii file
1624 -- wmii.set_screen_ctl("screen", { "var" = "val", ...})
1625 -- wmii.set_screen_ctl("screen", "var, "val")
1626 function set_screen_ctl (screen
, first
, second
)
1627 local ctl
= "/screen/" .. tostring(screen
) .. "/ctl"
1629 error ("screen is not set")
1630 elseif type(first
) == "table" and second
== nil then
1632 for x
, y
in pairs(first
) do
1633 write (ctl
, x
.. " " .. y
)
1636 elseif type(first
) == "string" and type(second
) == "string" then
1637 write (ctl
, first
.. " " .. second
)
1640 error ("expecting a screen name, followed by a table or two string arguments")
1644 -- ------------------------------------------------------------------------
1645 -- read a value from /screen/*/ctl wmii file
1646 -- table = wmii.get_screen_ctl("screen")
1647 -- value = wmii.get_screen_ctl("screen", "variable")
1648 function get_screen_ctl (screen
, name
)
1654 local ctl
= "/screen/" .. tostring(screen
) .. "/ctl"
1655 for s
in iread(ctl
) do
1656 local var
,val
= s
:match("(%w+)%s+(.+)")
1660 -- sometimes first line is the name of the entry
1661 -- in which case there will be no space
1670 -- ------------------------------------------------------------------------
1671 -- set an internal wmiirc.lua variable
1672 -- wmii.set_conf({ "var" = "val", ...})
1673 -- wmii.set_conf("var, "val")
1674 function set_conf (first
,second
)
1675 if type(first
) == "table" and second
== nil then
1677 for x
, y
in pairs(first
) do
1681 elseif type(first
) == "string"
1682 and (type(second
) == "string"
1683 or type(second
) == "number"
1684 or type(second
) == "boolean") then
1685 config
[first
] = second
1688 error ("expecting a table, or string and string/number as arguments")
1692 -- ------------------------------------------------------------------------
1693 -- read an internal wmiirc.lua variable
1694 function get_conf (name
)
1701 -- ========================================================================
1703 -- ========================================================================
1705 -- the event loop instance
1706 local el
= eventloop
.new()
1707 local event_read_fd
= -1
1708 local wmiirc_running
= false
1709 local event_read_start
= 0
1711 -- ------------------------------------------------------------------------
1712 -- start/restart the core event reading process
1713 local function start_event_reader ()
1714 -- prevent adding two readers
1715 if event_read_fd
~= -1 then
1716 if el
:check_exec(event_read_fd
) then
1720 -- prevert rapid restarts
1721 local now
= os
.time()
1722 if os
.difftime(now
, event_read_start
) < 5 then
1723 log("wmii: detected rapid restart of /event reader")
1724 local cmd
= wmiir
.. " ls /ctl"
1725 if os
.execute(cmd
) ~= 0 then
1726 log("wmii: cannot confirm communication with wmii, shutting down!")
1727 wmiirc_running
= false
1730 log("wmii: but things look ok, so we will restart it")
1732 event_read_start
= now
1734 -- start a new event reader
1735 log("wmii: starting /event reading process")
1736 event_read_fd
= el
:add_exec (wmiir
.. " read /event",
1738 local line
= line
or "nil"
1740 -- try to split off the argument(s)
1741 local ev
,arg
= string.match(line
, "(%S+)%s+(.+)")
1746 -- now locate the handler function and call it
1747 local fn
= ev_handlers
[ev
] or ev_handlers
["*"]
1749 local r
, err
= pcall (fn
, ev
, arg
)
1751 log ("WARNING: " .. tostring(err
))
1756 log("wmii: ... fd=" .. tostring(event_read_fd
))
1759 -- ------------------------------------------------------------------------
1760 -- run the event loop and process events, this function does not exit
1761 function run_event_loop ()
1762 -- stop any other instance of wmiirc
1763 wmixp
:write ("/event", "Start wmiirc " .. tostring(myid
))
1765 log("wmii: updating lbar")
1767 update_displayed_tags ()
1769 log("wmii: updating rbar")
1771 update_displayed_widgets ()
1773 log("wmii: updating active keys")
1775 update_active_keys ()
1777 log("wmii: starting event loop")
1778 wmiirc_running
= true
1779 while wmiirc_running
do
1780 start_event_reader()
1781 local sleep_for
= process_timers()
1782 el
:run_loop(sleep_for
)
1784 log ("wmii: exiting")
1787 -- ========================================================================
1789 -- ========================================================================
1791 api_version
= 0.1 -- the API version we export
1793 plugins
= {} -- all plugins that were loaded
1795 -- ------------------------------------------------------------------------
1796 -- plugin loader which also verifies the version of the api the plugin needs
1798 -- here is what it does
1799 -- - does a manual locate on the file using package.path
1800 -- - reads in the file w/o using the lua interpreter
1801 -- - locates api_version=X.Y string
1802 -- - makes sure that api_version requested can be satisfied
1803 -- - if the plugins is available it will set variables passed in
1804 -- - it then loads the plugin
1806 -- TODO: currently the api_version must be in an X.Y format, but we may want
1807 -- to expend this so plugins can say they want '0.1 | 1.3 | 2.0' etc
1809 function load_plugin(name
, vars
)
1810 local backup_path
= package
.path
or "./?.lua"
1812 log ("loading " .. name
)
1814 -- this is the version we want to find
1815 local api_major
, api_minor
= tostring(api_version
):match("(%d+)%.0*(%d+)")
1816 if (not api_major
) or (not api_minor
) then
1817 log ("WARNING: could not parse api_version in core/wmii.lua")
1821 -- first find the plugin file
1822 local s
, path_match
, full_name
, file
1823 for i
,s
in pairs(plugin_paths
) do
1824 -- try to locate the files locally
1825 local fn
= s
:gsub("%?", name
)
1826 file
= io
.open(fn
, "r")
1837 txt
= file
:read("*all")
1842 log ("WARNING: could not load plugin '" .. name
.. "'")
1846 -- find the api_version line
1847 local line
, plugin_version
1848 for line
in string.gmatch(txt
, "%s*api_version%s*=%s*%d+%.%d+%s*") do
1849 plugin_version
= line
:match("api_version%s*=%s*(%d+%.%d+)%s*")
1850 if plugin_version
then
1855 if not plugin_version
then
1856 log ("WARNING: could not find api_version string in plugin '" .. name
.. "'")
1860 -- decompose the version string
1861 local plugin_major
, plugin_minor
= plugin_version
:match("(%d+)%.0*(%d+)")
1862 if (not plugin_major
) or (not plugin_minor
) then
1863 log ("WARNING: could not parse api_version for '" .. name
.. "' plugin")
1867 -- make a version test
1868 if plugin_major
~= api_major
then
1869 log ("WARNING: " .. name
.. " plugin major version missmatch, is " .. plugin_version
1870 .. " (api " .. tonumber(api_version
) .. ")")
1874 if plugin_minor
> api_minor
then
1875 log ("WARNING: '" .. name
.. "' plugin minor version missmatch, is " .. plugin_version
1876 .. " (api " .. tonumber(api_version
) .. ")")
1880 -- the configuration parameters before loading
1881 if type(vars
) == "table" then
1883 for var
,val
in pairs(vars
) do
1884 local success
= pcall (set_conf
, name
.. "." .. var
, val
)
1886 log ("WARNING: bad variable {" .. tostring(var
) .. ", " .. tostring(val
) .. "} "
1887 .. "given; loading '" .. name
.. "' plugin failed.")
1893 -- actually load the module, but use only the path where we though it should be
1894 package
.path
= path_match
1895 local success
,what
= pcall (require
, name
)
1896 package
.path
= backup_path
1898 log ("WARNING: failed to load '" .. name
.. "' plugin")
1899 log (" - path: " .. tostring(path_match
))
1900 log (" - file: " .. tostring(full_name
))
1901 log (" - plugin's api_version: " .. tostring(plugin_version
))
1902 log (" - reason: " .. tostring(what
))
1907 log ("OK, plugin " .. name
.. " loaded, requested api v" .. plugin_version
)
1908 plugins
[name
] = what
1912 -- ------------------------------------------------------------------------
1917 -- ------------------------------------------------------------------------
1918 -- create a widget object and add it to the wmii /rbar
1921 -- widget = wmii.widget:new ("999_clock")
1922 -- widget = wmii.widget:new ("999_clock", clock_event_handler)
1923 function widget
:new (name
, fn
, bar
)
1926 if type(name
) == "string" then
1928 if type(fn
) == "function" then
1931 o
.bar
= bar
or "rbar"
1933 error ("expected name followed by an optional function as arguments")
1936 setmetatable (o
,self
)
1938 self
.__gc
= function (o
) o
:hide() end
1946 -- ------------------------------------------------------------------------
1947 -- stop and destroy the timer
1948 function widget
:delete ()
1949 widgets
[self
.name
] = nil
1953 -- ------------------------------------------------------------------------
1954 -- displays or updates the widget text
1958 -- w:show("foo", "#888888 #222222 #333333")
1959 -- w:show("foo", cell_fg .. " " .. cell_bg .. " " .. border)
1961 function widget
:show (txt
, colors
)
1962 local colors
= colors
or get_ctl("normcolors") or ""
1963 local txt
= txt
or self
.txt
or ""
1966 towrite
= colors
.. " " .. towrite
1968 if not self
.txt
then
1969 create ('/'..self
.bar
..'/'.. self
.name
, towrite
)
1971 write ('/'..self
.bar
..'/'.. self
.name
, towrite
)
1976 -- ------------------------------------------------------------------------
1977 -- hides a widget and removes it from the bar
1978 function widget
:hide ()
1980 remove ('/'..self
.bar
..'/'.. self
.name
)
1988 =item widget:add_event_handler (ev, fn)
1990 Add an event handler callback for this widget, using I<fn> for event I<ev>
1995 function widget
:add_event_handler (ev
, fn
)
1996 add_widget_event_handler( self
.name
, ev
, fn
)
2000 -- ------------------------------------------------------------------------
2001 -- remove all /rbar entries that we don't have widget objects for
2002 function update_displayed_widgets ()
2003 -- colours for /rbar
2004 local nc
= get_ctl("normcolors") or ""
2006 -- build up a table of existing tags in the /lbar
2009 for s
in wmixp
:idir ("/rbar") do
2013 -- for all actual widgets in use we want to remove them from the old list
2015 for i
,v
in pairs(widgets
) do
2019 -- anything left in the old table should be removed now
2020 for i
,v
in pairs(old
) do
2027 -- ------------------------------------------------------------------------
2028 -- create a new program and for each line it generates call the callback function
2029 -- returns fd which can be passed to kill_exec()
2030 function add_exec (command
, callback
)
2031 return el
:add_exec (command
, callback
)
2034 -- ------------------------------------------------------------------------
2035 -- terminates a program spawned off by add_exec()
2036 function kill_exec (fd
)
2037 return el
:kill_exec (fd
)
2040 -- ------------------------------------------------------------------------
2045 -- ------------------------------------------------------------------------
2046 -- create a timer object and add it to the event loop
2049 -- timer:new (my_timer_fn)
2050 -- timer:new (my_timer_fn, 15)
2051 function timer
:new (fn
, seconds
)
2054 if type(fn
) == "function" then
2057 error ("expected function followed by an optional number as arguments")
2060 setmetatable (o
,self
)
2062 self
.__gc
= function (o
) o
:stop() end
2065 timers
[#timers
+1] = o
2073 -- ------------------------------------------------------------------------
2074 -- stop and destroy the timer
2075 function timer
:delete ()
2078 for i
,t
in pairs(timers
) do
2080 table.remove (timers
,i
)
2086 -- ------------------------------------------------------------------------
2087 -- run the timer given new interval
2088 function timer
:resched (seconds
)
2089 local seconds
= seconds
or self
.interval
2090 if not (type(seconds
) == "number") then
2091 error ("timer:resched expected number as argument")
2094 local now
= tonumber(os
.date("%s"))
2096 self
.interval
= seconds
2097 self
.next_time
= now
+ seconds
2099 -- resort the timer list
2100 table.sort (timers
, timer
.is_less_then
)
2103 -- helper for sorting timers
2104 function timer
:is_less_then(another
)
2105 if not self
.next_time
then
2106 return false -- another is smaller, nil means infinity
2108 elseif not another
.next_time
then
2109 return true -- self is smaller, nil means infinity
2111 elseif self
.next_time
< another
.next_time
then
2112 return true -- self is smaller than another
2115 return false -- another is smaller then self
2118 -- ------------------------------------------------------------------------
2120 function timer
:stop ()
2121 self
.next_time
= nil
2123 -- resort the timer list
2124 table.sort (timers
, timer
.is_less_then
)
2127 -- ------------------------------------------------------------------------
2128 -- figure out how long before the next event
2129 function time_before_next_timer_event()
2130 local tmr
= timers
[1]
2131 if tmr
and tmr
.next_time
then
2132 local now
= tonumber(os
.date("%s"))
2133 local seconds
= tmr
.next_time
- now
2138 return 0 -- sleep for ever
2141 -- ------------------------------------------------------------------------
2142 -- handle outstanding events
2143 function process_timers ()
2144 local now
= tonumber(os
.date("%s"))
2148 for i
,tmr
in pairs (timers
) do
2150 -- prune out removed timers
2151 table.remove(timers
,i
)
2154 elseif not tmr
.next_time
then
2155 -- break out once we find a timer that is stopped
2158 elseif tmr
.next_time
> now
then
2159 -- break out once we get to the future
2163 -- this one is good to go
2164 torun
[#torun
+1] = tmr
2167 for i
,tmr
in pairs (torun
) do
2169 local status
,new_interval
= pcall (tmr
.fn
, tmr
)
2171 new_interval
= new_interval
or self
.interval
2172 if new_interval
and (new_interval
~= -1) then
2173 tmr
:resched(new_interval
)
2176 log ("ERROR: " .. tostring(new_interval
))
2180 local sleep_for
= time_before_next_timer_event()
2184 -- ------------------------------------------------------------------------
2185 -- cleanup everything in preparation for exit() or exec()
2190 log ("wmii: stopping timer events")
2192 for i
,tmr
in pairs (timers
) do
2193 pcall (tmr
.delete
, tmr
)
2197 log ("wmii: terminating eventloop")
2199 pcall(el
.kill_all
,el
)
2201 log ("wmii: disposing of widgets")
2203 -- dispose of all widgets
2204 for i
,v
in pairs(widgets
) do
2209 -- FIXME: it doesn't seem to do what I want
2211 log ("wmii: releasing plugins")
2213 for i,p in pairs(plugins) do
2215 pcall (p.cleanup, p)
2221 log ("wmii: dormant")
2222 wmiirc_running
= false
2225 -- ========================================================================
2227 -- ========================================================================
2230 -- Notes on client tracking
2232 -- When a client is created wmii sends us a CreateClient message, and
2233 -- we in turn create a 'client' object and store it in the 'clients'
2234 -- table indexed by the client's ID.
2236 -- Each client object stores the following:
2237 -- .xid - the X client ID
2238 -- .pid - the process ID
2239 -- .prog - program object representing the process
2241 -- The client and program objects track the following modes for each program:
2244 -- - for each client window
2245 -- - Mod4-space toggles the state between normal and raw
2246 -- - Mod1-f raw also toggles the state
2247 -- - in raw mode all input goes to the client, except for Mod4-space
2248 -- - a focused client with raw mode enabled is put into raw mode
2251 -- - for each program
2252 -- - Mod1-f suspend toggles the state for current client's program
2253 -- - a focused client, whose program was previous suspended is resumed
2254 -- - an unfocused client, with suspend enabled, will be suspended
2255 -- - suspend/resume is done by sending the STOP/CONT signals to the PID
2258 function xid_to_pid (xid
)
2259 local cmd
= "xprop -id " .. tostring(xid
) .. " _NET_WM_PID"
2260 local file
= io
.popen (cmd
)
2261 local out
= file
:read("*a")
2263 local pid
= out
:match("^_NET_WM_PID.*%s+=%s+(%d+)%s+$")
2264 return tonumber(pid
)
2267 local focused_xid
= nil
2268 local clients
= {} -- table of client objects indexed by xid
2269 local programs
= {} -- table of program objects indexed by pid
2270 local mode_widget
= widget
:new ("999_client_mode")
2272 -- make programs table have weak values
2273 -- programs go away as soon as no clients point to it
2274 local programs_mt
= {}
2275 setmetatable(programs
, programs_mt
)
2276 programs_mt
.__mode
= 'v'
2280 function program
:new (pid
)
2283 setmetatable (o
,self
)
2285 self
.__gc
= function (old
) old
:cont() end
2286 -- initialize the new object
2290 o
.suspend
.toggle
= function (prog
)
2291 prog
.suspend
.enabled
= not prog
.suspend
.enabled
2293 o
.suspend
.enabled
= false -- if true, defocusing suspends (SIGSTOP)
2294 o
.suspend
.active
= true -- if true, focusing resumes (SIGCONT)
2298 function program
:stop ()
2299 if not self
.suspend
.active
then
2300 local cmd
= "kill -STOP " .. tostring(self
.pid
)
2301 log (" executing: " .. cmd
)
2303 self
.suspend
.active
= true
2307 function program
:cont ()
2308 if self
.suspend
.active
then
2309 local cmd
= "kill -CONT " .. tostring(self
.pid
)
2310 log (" executing: " .. cmd
)
2312 self
.suspend
.active
= false
2316 function get_program (pid
)
2317 local prog
= programs
[pid
]
2318 if pid
and not prog
then
2319 prog
= program
:new (pid
)
2320 programs
[pid
] = prog
2327 function client
:new (xid
)
2328 local pid
= xid_to_pid(xid
)
2330 log ("WARNING: failed to convert XID " .. tostring(xid
) .. " to a PID")
2335 setmetatable (o
,self
)
2336 self
.__index
= function (t
,k
)
2337 if k
== 'suspend' then -- suspend mode is tracked per program
2338 return t
.prog
.suspend
2342 self
.__gc
= function (old
) old
.prog
=nil end
2343 -- initialize the new object
2346 o
.prog
= get_program (pid
)
2349 o
.raw
.toggle
= function (cli
)
2350 cli
.raw
.enabled
= not cli
.raw
.enabled
2353 o
.raw
.enabled
= false -- if true, raw mode enabled when client is focused
2357 function client
:stop ()
2358 if self
.suspend
.enabled
then
2363 function client
:cont ()
2367 function client
:set_raw_mode()
2368 if not self
or not self
.raw
.enabled
then -- normal mode
2369 update_active_keys ()
2371 write ("/keys", "Mod4-space")
2375 function client
:toggle(what
)
2376 if what
and self
[what
] then
2377 local ctl
= self
[what
]
2381 log ("xid=" .. tostring (xid
)
2382 .. " pid=" .. tostring (self
.pid
) .. " (" .. tostring (self
.prog
.pid
) .. ")"
2383 .. " what=" .. tostring (what
)
2384 .. " enabled=" .. tostring(ctl
["enabled"]))
2386 mode_widget
:show (self
:flags_string())
2389 function client
:flags_string()
2391 if self
.suspend
.enabled
then ret
= ret
.. "s" else ret
= ret
.. "-" end
2392 if self
.raw
.enabled
then ret
= ret
.. "r" else ret
= ret
.. "-" end
2396 function get_client (xid
)
2397 local xid
= xid
or wmixp
:read("/client/sel/ctl")
2398 local cli
= clients
[xid
]
2400 cli
= client
:new (xid
)
2406 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
2407 function client_created (xid
)
2408 log ("-client_created " .. tostring(xid
))
2409 return get_client(xid
)
2412 function client_destoryed (xid
)
2413 log ("-client_destoryed " .. tostring(xid
))
2414 if clients
[xid
] then
2415 local cli
= clients
[xid
]
2417 log (" del pid: " .. tostring(cli
.pid
))
2420 if focused_xid
== xid
then
2425 function client_focused (xid
)
2426 log ("-client_focused " .. tostring(xid
))
2427 -- return the current focused xid if nil is passed
2428 if type(xid
) ~= 'string' or not xid
:match("0x%x*$") then
2431 -- do nothing if the same xid
2432 if focused_xid
== xid
then
2436 local old
= clients
[focused_xid
]
2437 local new
= get_client(xid
)
2439 -- handle raw mode switch
2440 if not old
or ( old
and new
and old
.raw
.enabled
~= new
.raw
.enabled
) then
2444 -- do nothing if the same pid
2445 if old
and new
and old
.pid
== new
.pid
then
2446 mode_widget
:show (new
:flags_string())
2452 log (" old pid: " .. tostring(old.pid)
2453 .. " xid: " .. tostring(old.xid)
2454 .. " flags: " .. old:flags_string())
2461 log (" new pid: " .. tostring(new.pid)
2462 .. " xid: " .. tostring(new.xid)
2463 .. " flags: " .. new:flags_string())
2468 mode_widget
:show (new
:flags_string())
2474 -- ========================================================================
2476 -- ========================================================================
2489 Used to determine location of wmii's listen socket.
2495 L<wmii(1)>, L<lua(1)>
2499 Bart Trojanowski B<< <bart@jukie.net> >>
2501 =head1 COPYRIGHT AND LICENSE
2503 Copyright (c) 2007, Bart Trojanowski <bart@jukie.net>
2505 This is free software. You may redistribute copies of it under the terms of
2506 the GNU General Public License L<http://www.gnu.org/licenses/gpl.html>. There
2507 is NO WARRANTY, to the extent permitted by law.
2511 -- vim: set noet ts=8 sw=8 :