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 wmiirc
= os
.getenv("HOME") .. "/.wmii-3.5/wmiirc"
60 package
.path
= package
.path
61 .. ";" .. os
.getenv("HOME") .. "/.wmii-3.5/plugins/?.lua"
62 package
.cpath
= package
.cpath
63 .. ";" .. os
.getenv("HOME") .. "/.wmii-3.5/core/?.so"
64 .. ";" .. os
.getenv("HOME") .. "/.wmii-3.5/plugins/?.so"
66 local ixp
= require
"ixp"
67 local eventloop
= require
"eventloop"
70 local io
= require("io")
71 local os
= require("os")
72 local posix
= require("posix")
73 local string = require("string")
74 local table = require("table")
75 local math
= require("math")
81 local package
= package
82 local require
= require
83 local tostring = tostring
84 local tonumber = tonumber
85 local setmetatable
= setmetatable
90 local mypid
= posix
.getprocessid("pid")
92 -- ========================================================================
94 -- ========================================================================
96 -- wmiir points to the wmiir executable
97 -- TODO: need to make sure that wmiir is in path, and if not find it
100 -- wmii_adr is the address we use when connecting using ixp
101 local wmii_adr
= os
.getenv("WMII_ADDRESS")
102 or ("unix!/tmp/ns." .. os
.getenv("USER") .. "."
103 .. os
.getenv("DISPLAY"):match("(:%d+)") .. "/wmii")
105 -- wmixp is the ixp context we use to talk to wmii
106 local wmixp
= ixp
.new(wmii_adr
)
108 -- history of previous views, view_hist[#view_hist] is the last one
109 local view_hist
= {} -- sorted with 1 being the oldest
110 local view_hist_max
= 10 -- max number to keep track of
112 -- allow for a client to be forced to a tag
113 local next_client_goes_to_tag
= nil
115 -- ========================================================================
117 -- ========================================================================
124 Log the message provided in C<str>
126 Currently just writes to io.stderr
131 io
.stderr
:write (str
.. "\n")
134 -- ========================================================================
135 -- MAIN ACCESS FUNCTIONS
136 -- ========================================================================
141 =item ls ( dir, fmt )
143 List the wmii filesystem directory provided in C<dir>, in the format specified
146 Returns an iterator of TODO
150 function ls (dir
, fmt
)
151 local verbose
= fmt
and fmt
:match("l")
153 local s
= wmixp
:stat(dir
)
155 return function () return nil end
157 if s
.modestr
:match("^[^d]") then
159 return stat2str(verbose
, s
)
163 local itr
= wmixp
:idir (dir
)
174 return stat2str(verbose
, s
)
180 local function stat2str(verbose
, stat
)
182 return string.format("%s %s %s %5d %s %s", stat
.modestr
, stat
.uid
, stat
.gid
, stat
.length
, stat
.timestr
, stat
.name
)
184 if stat
.modestr
:match("^d") then
185 return stat
.name
.. "/"
192 -- ------------------------------------------------------------------------
193 -- read all contents of a wmii virtual file
195 return wmixp
:read (file
)
198 -- ------------------------------------------------------------------------
199 -- return an iterator which walks all the lines in the file
202 -- for event in wmii.iread("/ctl")
206 -- NOTE: don't use iread for files that could block, as this will interfere
207 -- with timer processing and event delivery. Instead fork off a process to
208 -- execute wmiir and read back the responses via callback.
209 function iread (file
)
210 return wmixp
:iread(file
)
213 -- ------------------------------------------------------------------------
214 -- create a wmii file, optionally write data to it
215 function create (file
, data
)
216 wmixp
:create(file
, data
)
219 -- ------------------------------------------------------------------------
220 -- remove a wmii file
221 function remove (file
)
225 -- ------------------------------------------------------------------------
226 -- write a value to a wmii virtual file system
227 function write (file
, value
)
228 wmixp
:write (file
, value
)
231 -- ------------------------------------------------------------------------
232 -- setup a table describing dmenu command
233 local function dmenu_cmd (prompt
)
234 local cmdt
= { "dmenu", "-b" }
235 local fn
= get_ctl("font")
237 cmdt
[#cmdt
+1] = "-fn"
240 local normcolors
= get_ctl("normcolors")
242 local nf
, nb
= normcolors
:match("(#%x+)%s+(#%x+)%s#%x+")
244 cmdt
[#cmdt
+1] = "-nf"
245 cmdt
[#cmdt
+1] = "'" .. nf
.. "'"
248 cmdt
[#cmdt
+1] = "-nb"
249 cmdt
[#cmdt
+1] = "'" .. nb
.. "'"
252 local focuscolors
= get_ctl("focuscolors")
254 local sf
, sb
= focuscolors
:match("(#%x+)%s+(#%x+)%s#%x+")
256 cmdt
[#cmdt
+1] = "-sf"
257 cmdt
[#cmdt
+1] = "'" .. sf
.. "'"
260 cmdt
[#cmdt
+1] = "-sb"
261 cmdt
[#cmdt
+1] = "'" .. sb
.. "'"
266 cmdt
[#cmdt
+1] = "'" .. prompt
.. "'"
272 -- ------------------------------------------------------------------------
273 -- displays the menu given an table of entires, returns selected text
274 function menu (tbl
, prompt
)
275 local dmenu
= dmenu_cmd(prompt
)
277 local infile
= os
.tmpname()
278 local fh
= io
.open (infile
, "w+")
281 for i
,v
in pairs(tbl
) do
282 if type(i
) == 'number' and type(v
) == 'string' then
291 local outfile
= os
.tmpname()
293 dmenu
[#dmenu
+1] = "<"
294 dmenu
[#dmenu
+1] = infile
295 dmenu
[#dmenu
+1] = ">"
296 dmenu
[#dmenu
+1] = outfile
298 local cmd
= table.concat(dmenu
," ")
301 fh
= io
.open (outfile
, "r")
304 local sel
= fh
:read("*l")
310 -- ------------------------------------------------------------------------
311 -- displays the a tag selection menu, returns selected tag
313 local tags
= get_tags()
315 return menu(tags
, "tag:")
318 -- ------------------------------------------------------------------------
319 -- displays the a program menu, returns selected program
320 function prog_menu ()
321 local dmenu
= dmenu_cmd("cmd:")
323 local outfile
= os
.tmpname()
325 dmenu
[#dmenu
+1] = ">"
326 dmenu
[#dmenu
+1] = outfile
328 local cmd
= "dmenu_path |" .. table.concat(dmenu
," ")
331 local fh
= io
.open (outfile
, "rb")
334 local prog
= fh
:read("*l")
340 -- ------------------------------------------------------------------------
341 -- displays the a program menu, returns selected program
345 for s
in wmixp
:idir ("/tag") do
346 if s
.name
and not (s
.name
== "sel") then
354 -- ------------------------------------------------------------------------
355 -- displays the a program menu, returns selected program
357 local v
= wmixp
:read("/ctl") or ""
358 return v
:match("view%s+(%S+)")
361 -- ------------------------------------------------------------------------
362 -- changes the current view to the name given
363 function set_view(sel
)
364 local cur
= get_view()
365 local all
= get_tags()
367 if #all
< 2 or sel
== cur
then
368 -- nothing to do if we have less then 2 tags
372 if not (type(sel
) == "string") then
373 error ("string argument expected")
377 write ("/ctl", "view " .. sel
)
380 -- ------------------------------------------------------------------------
381 -- changes the current view to the index given
382 function set_view_index(sel
)
383 local cur
= get_view()
384 local all
= get_tags()
387 -- nothing to do if we have less then 2 tags
391 local num
= tonumber (sel
)
393 error ("number argument expected")
396 local name
= all
[sel
]
397 if not name
or name
== cur
then
402 write ("/ctl", "view " .. name
)
405 -- ------------------------------------------------------------------------
406 -- chnages to current view by offset given
407 function set_view_ofs(jump
)
408 local cur
= get_view()
409 local all
= get_tags()
412 -- nothing to do if we have less then 2 tags
417 if (jump
< - #all
) or (jump
> #all
) then
418 error ("view selector is out of range")
421 -- find the one that's selected index
424 for i
,v
in pairs (all
) do
425 if v
== cur
then curi
= i
end
429 local newi
= math
.fmod(#all
+ curi
+ jump
- 1, #all
) + 1
430 if (newi
< - #all
) or (newi
> #all
) then
431 error ("error computng new view")
434 write ("/ctl", "view " .. all
[newi
])
437 -- ------------------------------------------------------------------------
438 -- toggle between last view and current view
439 function toggle_view()
440 local last
= view_hist
[#view_hist
]
446 -- ========================================================================
448 -- ========================================================================
450 local action_handlers
= {
452 write ("/ctl", "quit")
455 exec
= function (act
, args
)
456 local what
= args
or wmiirc
458 write ("/ctl", "exec " .. what
)
463 posix
.exec ("lua", wmiirc
)
467 -- TODO: consider storing list of executables around, and
468 -- this will then reinitialize that list
469 log (" TODO: rehash")
473 -- TODO: this should eventually update something on the /rbar
474 log (" TODO: status")
481 =item add_action_handler (action, fn)
483 Add an Alt-a action handler callback function, I<fn>, for the given action string I<action>.
487 function add_action_handler (action
, fn
)
489 if type(action
) ~= "string" or type(fn
) ~= "function" then
490 error ("expecting a string and a function")
493 if action_handlers
[action
] then
494 error ("action handler already exists for '" .. action
.. "'")
497 action_handlers
[action
] = fn
503 =item remove_action_handler (action)
505 Remove an action handler callback function for the given action string I<action>.
509 function remove_action_handler (action
)
511 action_handlers
[action
] = nil
514 -- ========================================================================
516 -- ========================================================================
518 local key_handlers
= {
519 ["*"] = function (key
)
523 -- execution and actions
524 ["Mod1-Return"] = function (key
)
525 local xterm
= get_conf("xterm") or "xterm"
526 log (" executing: " .. xterm
)
527 os
.execute (xterm
.. " &")
529 ["Mod1-Shift-Return"] = function (key
)
530 local tag = tag_menu()
532 local xterm
= get_conf("xterm") or "xterm"
533 log (" executing: " .. xterm
.. " on: " .. tag)
534 next_client_goes_to_tag
= tag
535 os
.execute (xterm
.. " &")
538 ["Mod1-a"] = function (key
)
539 local text
= menu(action_handlers
, "action:")
543 local si
= text
:find("%s")
545 act
,args
= string.match(text
.. " ", "(%w+)%s(.+)")
548 local fn
= action_handlers
[act
]
555 ["Mod1-p"] = function (key
)
556 local prog
= prog_menu()
558 log (" executing: " .. prog
)
559 os
.execute (prog
.. " &")
562 ["Mod1-Shift-p"] = function (key
)
563 local tag = tag_menu()
565 local prog
= prog_menu()
567 log (" executing: " .. prog
.. " on: " .. tag)
568 next_client_goes_to_tag
= tag
569 os
.execute (prog
.. " &")
573 ["Mod1-Shift-c"] = function (key
)
574 write ("/client/sel/ctl", "kill")
577 -- HJKL active selection
578 ["Mod1-h"] = function (key
)
579 write ("/tag/sel/ctl", "select left")
581 ["Mod1-l"] = function (key
)
582 write ("/tag/sel/ctl", "select right")
584 ["Mod1-j"] = function (key
)
585 write ("/tag/sel/ctl", "select down")
587 ["Mod1-k"] = function (key
)
588 write ("/tag/sel/ctl", "select up")
592 ["Mod1-Shift-h"] = function (key
)
593 write ("/tag/sel/ctl", "send sel left")
595 ["Mod1-Shift-l"] = function (key
)
596 write ("/tag/sel/ctl", "send sel right")
598 ["Mod1-Shift-j"] = function (key
)
599 write ("/tag/sel/ctl", "send sel down")
601 ["Mod1-Shift-k"] = function (key
)
602 write ("/tag/sel/ctl", "send sel up")
606 ["Mod1-space"] = function (key
)
607 write ("/tag/sel/ctl", "select toggle")
609 ["Mod1-Shift-space"] = function (key
)
610 write ("/tag/sel/ctl", "send sel toggle")
614 ["Mod4-#"] = function (key
, num
)
617 ["Mod4-Shift-#"] = function (key
, num
)
618 write ("/client/sel/tags", tostring(num
))
620 ["Mod1-comma"] = function (key
)
623 ["Mod1-period"] = function (key
)
626 ["Mod1-r"] = function (key
)
627 -- got to the last view
631 -- switching views and retagging
632 ["Mod1-t"] = function (key
)
634 local tag = tag_menu()
639 ["Mod1-Shift-t"] = function (key
)
640 -- move selected client to a tag
641 local tag = tag_menu()
643 write ("/client/sel/tags", tag)
646 ["Mod1-Shift-r"] = function (key
)
647 -- move selected client to a tag, and follow
648 local tag = tag_menu()
650 write ("/client/sel/tags", tag)
654 ["Mod1-Control-t"] = function (key
)
655 log (" TODO: Mod1-Control-t: " .. key
)
659 ["Mod1-d"] = function (key
)
660 write("/tag/sel/ctl", "colmode sel default")
662 ["Mod1-s"] = function (key
)
663 write("/tag/sel/ctl", "colmode sel stack")
665 ["Mod1-m"] = function (key
)
666 write("/tag/sel/ctl", "colmode sel max")
673 =item add_key_handler (key, fn)
675 Add a keypress handler callback function, I<fn>, for the given key sequence I<key>.
679 function add_key_handler (key
, fn
)
681 if type(key
) ~= "string" or type(fn
) ~= "function" then
682 error ("expecting a string and a function")
685 if key_handlers
[key
] then
686 -- TODO: we may wish to allow multiple handlers for one keypress
687 error ("key handler already exists for '" .. key
.. "'")
690 key_handlers
[key
] = fn
696 =item remove_key_handler (key)
698 Remove an key handler callback function for the given key I<key>.
702 function remove_key_handler (key
)
704 key_handlers
[key
] = nil
707 -- ------------------------------------------------------------------------
708 -- update the /keys wmii file with the list of all handlers
709 function update_active_keys ()
712 for x
,y
in pairs(key_handlers
) do
714 local i
= x
:find("#")
727 local all_keys
= table.concat(t
, "\n")
728 --log ("setting /keys to...\n" .. all_keys .. "\n");
729 write ("/keys", all_keys
)
732 -- ------------------------------------------------------------------------
733 -- update the /lbar wmii file with the current tags
734 function update_displayed_tags ()
736 local fc
= get_ctl("focuscolors") or ""
737 local nc
= get_ctl("normcolors") or ""
739 -- build up a table of existing tags in the /lbar
742 for s
in wmixp
:idir ("/lbar") do
746 -- for all actual tags in use create any entries in /lbar we don't have
747 -- clear the old table entries if we have them
748 local cur
= get_view()
749 local all
= get_tags()
751 for i
,v
in pairs(all
) do
757 create ("/lbar/" .. v
, color
.. " " .. v
)
759 write ("/lbar/" .. v
, color
.. " " .. v
)
763 -- anything left in the old table should be removed now
764 for i
,v
in pairs(old
) do
771 -- ========================================================================
773 -- ========================================================================
775 local ev_handlers
= {
776 ["*"] = function (ev
, arg
)
777 log ("ev: " .. tostring(ev
) .. " - " .. tostring(arg
))
780 -- process timer events
781 ProcessTimerEvents
= function (ev
, arg
)
785 -- exit if another wmiirc started up
786 Start
= function (ev
, arg
)
788 if arg
== "wmiirc" then
789 -- backwards compatibility with bash version
793 -- ignore if it came from us
794 local pid
= string.match(arg
, "wmiirc (%d+)")
796 local pid
= tonumber (pid
)
797 if not (pid
== mypid
) then
807 CreateTag
= function (ev
, arg
)
808 local nc
= get_ctl("normcolors") or ""
809 create ("/lbar/" .. arg
, nc
.. " " .. arg
)
811 DestroyTag
= function (ev
, arg
)
812 remove ("/lbar/" .. arg
)
815 FocusTag
= function (ev
, arg
)
816 local fc
= get_ctl("focuscolors") or ""
817 create ("/lbar/" .. arg
, fc
.. " " .. arg
)
818 write ("/lbar/" .. arg
, fc
.. " " .. arg
)
820 UnfocusTag
= function (ev
, arg
)
821 local nc
= get_ctl("normcolors") or ""
822 create ("/lbar/" .. arg
, nc
.. " " .. arg
)
823 write ("/lbar/" .. arg
, nc
.. " " .. arg
)
825 -- don't duplicate the last entry
826 if not (arg
== view_hist
[#view_hist
]) then
827 view_hist
[#view_hist
+1] = arg
829 -- limit to view_hist_max
830 if #view_hist
> view_hist_max
then
831 table.remove(view_hist
, 1)
836 -- key event handling
837 Key
= function (ev
, arg
)
840 -- can we find an exact match?
841 local fn
= key_handlers
[arg
]
843 local key
= arg
:gsub("-%d+", "-#")
844 -- can we find a match with a # wild card for the number
845 fn
= key_handlers
[key
]
847 -- convert the trailing number to a number
848 num
= tonumber(arg
:match("-(%d+)"))
850 -- everything else failed, try default match
851 fn
= key_handlers
["*"]
859 -- mouse handling on the lbar
860 LeftBarClick
= function (ev
, arg
)
861 local button
,tag = string.match(arg
, "(%w+)%s+(%w+)")
866 ClientFocus
= function (ev
, arg
)
867 log ("ClientFocus: " .. arg
)
869 ColumnFocus
= function (ev
, arg
)
870 log ("ColumnFocus: " .. arg
)
874 CreateClient
= function (ev
, arg
)
875 if next_client_goes_to_tag
then
876 local tag = next_client_goes_to_tag
878 next_client_goes_to_tag
= nil
879 write ("/client/" .. cli
.. "/tags", tag)
885 UrgentTag
= function (ev
, arg
)
886 log ("UrgentTag: " .. arg
)
887 -- wmiir xwrite "/lbar/$@" "*$@"
889 NotUrgentTag
= function (ev
, arg
)
890 log ("NotUrgentTag: " .. arg
)
891 -- wmiir xwrite "/lbar/$@" "$@"
896 local widget_ev_handlers
= {
902 =item _handle_widget_event (ev, arg)
904 Top-level event handler for redispatching events to widgets. This event
905 handler is added for any widget event that currently has a widget registered
908 Valid widget events are currently
910 RightBarMouseDown <buttonnumber> <widgetname>
911 RightBarClick <buttonnumber> <widgetname>
913 the "Click" event is sent on mouseup.
915 The callbacks are given only the button number as their argument, to avoid the
921 function _handle_widget_event (ev
, arg
)
922 -- parse arg to strip out our widget name
923 local number,wname
= string.match(arg
, "(%d+)%s+(.+)")
925 -- check our dispatch table for that widget
930 local wtable
= widget_ev_handlers
[wname
]
935 local fn
= wtable
[ev
] or wtable
["*"]
937 fn (ev
, tonumber(number))
944 =item add_widget_event_handler (wname, ev, fn)
946 Add an event handler callback for the I<ev> event on the widget named I<wname>
951 function add_widget_event_handler (wname
, ev
, fn
)
952 if type(wname
) ~= "string" or type(ev
) ~= "string" or type(fn
) ~= "function" then
953 error ("expecting string for widget name, string for event name and a function callback")
956 -- Make sure the widget event handler is present
957 if not ev_handlers
[ev
] then
958 ev_handlers
[ev
] = _handle_widget_event
961 if not widget_ev_handlers
[wname
] then
962 widget_ev_handlers
[wname
] = { }
965 if widget_ev_handlers
[wname
][ev
] then
966 -- TODO: we may wish to allow multiple handlers for one event
967 error ("event handler already exists on widget '" .. wname
.. "' for '" .. ev
.. "'")
970 widget_ev_handlers
[wname
][ev
] = fn
976 =item remove_widget_event_handler (wname, ev)
978 Remove an event handler callback function for the I<ev> on the widget named I<wname>.
982 function remove_event_handler (wname
, ev
)
984 if not widget_ev_handlers
[wname
] then
988 widget_ev_handlers
[wname
][ev
] = nil
994 =item add_event_handler (ev, fn)
996 Add an event handler callback function, I<fn>, for the given event I<ev>.
1000 -- TODO: Need to allow registering widgets for RightBar* events. Should probably be done with its own event table, though
1001 function add_event_handler (ev
, fn
)
1002 if type(ev
) ~= "string" or type(fn
) ~= "function" then
1003 error ("expecting a string and a function")
1006 if ev_handlers
[ev
] then
1007 -- TODO: we may wish to allow multiple handlers for one event
1008 error ("event handler already exists for '" .. ev
.. "'")
1012 ev_handlers
[ev
] = fn
1018 =item remove_event_handler (ev)
1020 Remove an event handler callback function for the given event I<ev>.
1024 function remove_event_handler (ev
)
1026 ev_handlers
[ev
] = nil
1030 -- ========================================================================
1031 -- MAIN INTERFACE FUNCTIONS
1032 -- ========================================================================
1035 xterm
= 'x-terminal-emulator'
1038 -- ------------------------------------------------------------------------
1039 -- write configuration to /ctl wmii file
1040 -- wmii.set_ctl({ "var" = "val", ...})
1041 -- wmii.set_ctl("var, "val")
1042 function set_ctl (first
,second
)
1043 if type(first
) == "table" and second
== nil then
1045 for x
, y
in pairs(first
) do
1046 write ("/ctl", x
.. " " .. y
)
1049 elseif type(first
) == "string" and type(second
) == "string" then
1050 write ("/ctl", first
.. " " .. second
)
1053 error ("expecting a table or two string arguments")
1057 -- ------------------------------------------------------------------------
1058 -- read a value from /ctl wmii file
1059 function get_ctl (name
)
1061 for s
in iread("/ctl") do
1062 local var
,val
= s
:match("(%w+)%s+(.+)")
1070 -- ------------------------------------------------------------------------
1071 -- set an internal wmiirc.lua variable
1072 -- wmii.set_conf({ "var" = "val", ...})
1073 -- wmii.set_conf("var, "val")
1074 function set_conf (first
,second
)
1075 if type(first
) == "table" and second
== nil then
1077 for x
, y
in pairs(first
) do
1081 elseif type(first
) == "string"
1082 and (type(second
) == "string"
1083 or type(second
) == "number") then
1084 config
[first
] = second
1087 error ("expecting a table, or string and string/number as arguments")
1091 -- ------------------------------------------------------------------------
1092 -- read an internal wmiirc.lua variable
1093 function get_conf (name
)
1097 -- ========================================================================
1099 -- ========================================================================
1101 -- the event loop instance
1102 local el
= eventloop
.new()
1104 -- add the core event handler for events
1105 el
:add_exec (wmiir
.. " read /event",
1107 local line
= line
or "nil"
1109 -- try to split off the argument(s)
1110 local ev
,arg
= string.match(line
, "(%S+)%s+(.+)")
1115 -- now locate the handler function and call it
1116 local fn
= ev_handlers
[ev
] or ev_handlers
["*"]
1122 -- ------------------------------------------------------------------------
1123 -- run the event loop and process events, this function does not exit
1124 function run_event_loop ()
1125 -- stop any other instance of wmiirc
1126 wmixp
:write ("/event", "Start wmiirc " .. tostring(mypid
))
1128 log("wmii: updating lbar")
1130 update_displayed_tags ()
1132 log("wmii: updating rbar")
1134 update_displayed_widgets ()
1136 log("wmii: updating active keys")
1138 update_active_keys ()
1140 log("wmii: starting event loop")
1142 local sleep_for
= process_timers()
1143 el
:run_loop(sleep_for
)
1147 -- ========================================================================
1149 -- ========================================================================
1151 api_version
= 0.1 -- the API version we export
1153 plugins
= {} -- all plugins that were loaded
1155 -- ------------------------------------------------------------------------
1156 -- plugin loader which also verifies the version of the api the plugin needs
1158 -- here is what it does
1159 -- - does a manual locate on the file using package.path
1160 -- - reads in the file w/o using the lua interpreter
1161 -- - locates api_version=X.Y string
1162 -- - makes sure that api_version requested can be satisfied
1164 -- TODO: currently the api_version must be in an X.Y format, but we may want
1165 -- to expend this so plugins can say they want '0.1 | 1.3 | 2.0' etc
1167 function load_plugin(name
)
1168 local backup_path
= package
.path
or "./?.lua"
1170 log ("loading " .. name
)
1172 -- this is the version we want to find
1173 local api_major
, api_minor
= tostring(api_version
):match("(%d+)%.0*(%d+)")
1174 if (not api_major
) or (not api_minor
) then
1175 log ("WARNING: could not parse api_version in core/wmii.lua")
1179 -- first find the plugin file
1180 local s
, path_match
, full_name
, file
1181 for s
in string.gmatch(package
.path
, "[^;]+") do
1182 local fn
= s
:gsub("%?", name
)
1183 file
= io
.open(fn
, "r")
1194 txt
= file
:read("*all")
1199 log ("WARNING: could not load plugin '" .. name
.. "'")
1203 -- find the api_version line
1204 local line
, plugin_version
1205 for line
in string.gmatch(txt
, "%s*api_version%s*=%s*%d+%.%d+%s*") do
1206 plugin_version
= line
:match("api_version%s*=%s*(%d+%.%d+)%s*")
1212 -- decompose the version string
1213 local plugin_major
, plugin_minor
= plugin_version
:match("(%d+)%.0*(%d+)")
1214 if (not plugin_major
) or (not plugin_minor
) then
1215 log ("WARNING: could not parse api_version for '" .. name
.. "' plugin")
1219 -- make a version test
1220 if plugin_major
~= api_major
then
1221 log ("WARNING: " .. name
.. " plugin major version missmatch, is " .. plugin_version
1222 .. " (api " .. tonumber(api_version
) .. ")")
1226 if plugin_minor
> api_minor
then
1227 log ("WARNING: '" .. name
.. "' plugin minor version missmatch, is " .. plugin_version
1228 .. " (api " .. tonumber(api_version
) .. ")")
1232 -- actually load the module, but use only the path where we though it should be
1233 package
.path
= path_match
1234 local p
,e
,n
= pcall (require
, name
)
1235 package
.path
= backup_path
1237 log ("WARNING: failed to load '" .. name
.. "' plugin")
1238 log (" - path: " .. tostring(path_match
))
1239 log (" - file: " .. tostring(full_name
))
1240 log (" - plugin's api_version: " .. tostring(plugin_version
))
1241 log (" - reason: " .. tostring(e
) .. " (" .. tostring(n
) .. ")")
1246 log ("OK, plugin " .. name
.. " loaded, requested api v" .. plugin_version
)
1250 -- ------------------------------------------------------------------------
1255 -- ------------------------------------------------------------------------
1256 -- create a widget object and add it to the wmii /rbar
1259 -- widget = wmii.widget:new ("999_clock")
1260 -- widget = wmii.widget:new ("999_clock", clock_event_handler)
1261 function widget
:new (name
, fn
)
1264 if type(name
) == "string" then
1266 if type(fn
) == "function" then
1270 error ("expected name followed by an optional function as arguments")
1273 setmetatable (o
,self
)
1275 self
.__gc
= function (o
) o
:hide() end
1283 -- ------------------------------------------------------------------------
1284 -- stop and destroy the timer
1285 function widget
:delete ()
1286 widgets
[self
.name
] = nil
1290 -- ------------------------------------------------------------------------
1291 -- displays or updates the widget text
1295 -- w:show("foo", "#888888 #222222 #333333")
1296 -- w:show("foo", cell_fg .. " " .. cell_bg .. " " .. border)
1298 function widget
:show (txt
, colors
)
1299 local txt
= txt
or ""
1300 local colors
= colors
or get_ctl("normcolors") or ""
1301 if not self
.txt
then
1302 create ("/rbar/" .. self
.name
, colors
.. " " .. txt
)
1304 write ("/rbar/" .. self
.name
, txt
)
1309 -- ------------------------------------------------------------------------
1310 -- hides a widget and removes it from the bar
1311 function widget
:hide ()
1313 remove ("/lbar/" .. self
.name
)
1321 =item widget:add_event_handler (ev, fn)
1323 Add an event handler callback for this widget, using I<fn> for event I<ev>
1328 function widget
:add_event_handler (ev
, fn
)
1329 add_widget_event_handler( self
.name
, ev
, fn
)
1333 -- ------------------------------------------------------------------------
1334 -- remove all /rbar entries that we don't have widget objects for
1335 function update_displayed_widgets ()
1336 -- colours for /rbar
1337 local nc
= get_ctl("normcolors") or ""
1339 -- build up a table of existing tags in the /lbar
1342 for s
in wmixp
:idir ("/rbar") do
1346 -- for all actual widgets in use we want to remove them from the old list
1348 for i
,v
in pairs(widgets
) do
1352 -- anything left in the old table should be removed now
1353 for i
,v
in pairs(old
) do
1360 -- ------------------------------------------------------------------------
1361 -- create a new program and for each line it generates call the callback function
1362 -- returns fd which can be passed to kill_exec()
1363 function add_exec (command
, callback
)
1364 return el
:add_exec (command
, callback
)
1367 -- ------------------------------------------------------------------------
1368 -- terminates a program spawned off by add_exec()
1369 function kill_exec (fd
)
1370 return el
:kill_exec (fd
)
1373 -- ------------------------------------------------------------------------
1378 -- ------------------------------------------------------------------------
1379 -- create a timer object and add it to the event loop
1382 -- timer:new (my_timer_fn)
1383 -- timer:new (my_timer_fn, 15)
1384 function timer
:new (fn
, seconds
)
1387 if type(fn
) == "function" then
1390 error ("expected function followed by an optional number as arguments")
1393 setmetatable (o
,self
)
1395 self
.__gc
= function (o
) o
:stop() end
1398 timers
[#timers
+1] = o
1406 -- ------------------------------------------------------------------------
1407 -- stop and destroy the timer
1408 function timer
:delete ()
1411 for i
,t
in pairs(timers
) do
1413 table.remove (timers
,i
)
1419 -- ------------------------------------------------------------------------
1420 -- run the timer given new interval
1421 function timer
:resched (seconds
)
1422 local seconds
= seconds
or self
.interval
1423 if not (type(seconds
) == "number") then
1424 error ("expected number as argument")
1427 local now
= tonumber(os
.date("%s"))
1429 self
.interval
= seconds
1430 self
.next_time
= now
+ seconds
1432 -- resort the timer list
1433 table.sort (timers
, timer
.is_less_then
)
1436 -- helper for sorting timers
1437 function timer
:is_less_then(another
)
1438 if not self
.next_time
then
1439 return false -- another is smaller, nil means infinity
1441 elseif not another
.next_time
then
1442 return true -- self is smaller, nil means infinity
1444 elseif self
.next_time
< another
.next_time
then
1445 return true -- self is smaller than another
1448 return false -- another is smaller then self
1451 -- ------------------------------------------------------------------------
1453 function timer
:stop ()
1454 self
.next_time
= nil
1456 -- resort the timer list
1457 table.sort (timers
, timer
.is_less_then
)
1460 -- ------------------------------------------------------------------------
1461 -- figure out how long before the next event
1462 function time_before_next_timer_event()
1463 local tmr
= timers
[1]
1464 if tmr
and tmr
.next_time
then
1465 local now
= tonumber(os
.date("%s"))
1466 local seconds
= tmr
.next_time
- now
1471 return 0 -- sleep for ever
1474 -- ------------------------------------------------------------------------
1475 -- handle outstanding events
1476 function process_timers ()
1477 local now
= tonumber(os
.date("%s"))
1481 for i
,tmr
in pairs (timers
) do
1482 if (not tmr
) or (not tmr
.next_time
) then
1483 table.remove(timers
,i
)
1487 if tmr
.next_time
> now
then
1488 return tmr
.next_time
- now
1491 torun
[#torun
+1] = tmr
1494 for i
,tmr
in pairs (torun
) do
1496 local new_interval
= pcall (tmr
.fn
, tmr
)
1497 if new_interval
~= -1 then
1502 local sleep_for
= time_before_next_timer_event()
1506 -- ------------------------------------------------------------------------
1507 -- cleanup everything in preparation for exit() or exec()
1512 log ("wmii: stopping timer events")
1514 for i
,tmr
in pairs (timers
) do
1515 pcall (tmr
.delete
, tmr
)
1519 log ("wmii: terminating eventloop")
1521 pcall(el
.kill_all
,el
)
1523 log ("wmii: disposing of widgets")
1525 -- dispose of all widgets
1526 for i
,v
in pairs(widgets
) do
1531 log ("wmii: releasing plugins")
1533 for i
,p
in pairs(plugins
) do
1534 pcall (p
.cleanup
, p
)
1538 log ("wmii: dormant")
1541 -- ========================================================================
1543 -- ========================================================================
1556 Used to determine location of wmii's listen socket.
1562 L<wmii(1)>, L<lua(1)>
1566 Bart Trojanowski B<< <bart@jukie.net> >>
1568 =head1 COPYRIGHT AND LICENSE
1570 Copyright (c) 2007, Bart Trojanowski <bart@jukie.net>
1572 This is free software. You may redistribute copies of it under the terms of
1573 the GNU General Public License L<http://www.gnu.org/licenses/gpl.html>. There
1574 is NO WARRANTY, to the extent permitted by law.